gis-view-webgl2 0.1.1 → 0.1.3

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 (66) hide show
  1. package/README.md +23 -7
  2. package/dist/gis-view.css +1 -1
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +563 -61
  5. package/dist/index.js +1 -1
  6. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +182 -0
  7. package/docs/GisView.md +2 -2
  8. 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 +165 -0
  9. package/docs/api/index.html +1 -1
  10. package/docs/demos/ApiCoverageDemo.vue +644 -0
  11. package/docs/demos/ChannelDemo.vue +580 -18
  12. package/docs/demos/ClusterDemo.vue +251 -0
  13. package/docs/demos/CopyMergeDemo.vue +60 -10
  14. package/docs/demos/EventDemo.vue +3 -3
  15. package/docs/demos/GisViewDemo.vue +64 -8809
  16. package/docs/demos/LabelDemo.vue +67 -11
  17. package/docs/demos/api-index.js +58 -41
  18. package/docs/demos/demos.js +33 -16
  19. package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +28 -0
  20. package/docs/demos/gis-view-demo/GisViewDemoDialogs.vue +82 -0
  21. package/docs/demos/gis-view-demo/GisViewDemoFooter.vue +16 -0
  22. package/docs/demos/gis-view-demo/GisViewDemoHeading.vue +16 -0
  23. package/docs/demos/gis-view-demo/GisViewDemoPanelStack.vue +71 -0
  24. package/docs/demos/gis-view-demo/GisViewDemoStage.vue +219 -0
  25. package/docs/demos/gis-view-demo/demo.css +1230 -0
  26. package/docs/demos/gis-view-demo/demoContext.js +12 -0
  27. package/docs/demos/gis-view-demo/panels/DataPanels.vue +303 -0
  28. package/docs/demos/gis-view-demo/panels/OpsPanels.vue +174 -0
  29. package/docs/demos/gis-view-demo/panels/StylePanels.vue +561 -0
  30. package/docs/demos/gis-view-demo/panels/ViewPanels.vue +396 -0
  31. package/docs/demos/gis-view-demo/useGisViewDemo.js +6188 -0
  32. 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 +4 -2
  33. package/docs/format/04-/351/200/232/351/201/223/344/270/216/346/240/207/347/255/276.md +5 -4
  34. 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 +3 -2
  35. package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +1 -1
  36. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +10 -4
  37. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +105 -16
  38. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +8 -8
  39. 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 +100 -0
  40. 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 +78 -3
  41. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +31 -5
  42. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +64 -10
  43. 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 +65 -8
  44. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +54 -1
  45. package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +3 -0
  46. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +35 -19
  47. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +5 -0
  48. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +18 -6
  49. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +47 -22
  50. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +80 -13
  51. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +27 -0
  52. 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 +5 -5
  53. package/docs/steps/README.md +18 -10
  54. package/docs/steps/selftest-actions.html +3 -5
  55. package/docs/steps/selftest-demo.html +27 -18
  56. package/docs/steps/selftest-demos.html +578 -53
  57. package/docs/steps/selftest-docs.html +7 -6
  58. package/docs/steps/selftest-effects.html +200 -0
  59. package/docs/steps/selftest-features.html +725 -12
  60. package/docs/steps/selftest-labels.html +323 -4
  61. package/docs/steps/selftest-perf.html +293 -20
  62. package/docs/steps/selftest-scale.html +52 -29
  63. package/docs/steps/step-10-full.js +1 -1
  64. package/docs/steps/step-12-events.js +1 -1
  65. package/docs//346/216/245/345/217/243/345/215/217/350/256/256.md +17 -11
  66. package/package.json +2 -2
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  不依赖任何图形库的 WebGL2 矢量视图:点、线、背景层(图片 / GeoJSON)与首屏落位(`initialView`)都通过 `createGisView(dom, options)` 创建,宽高默认跟随传入的 DOM 容器;跨项目复用的视图级逻辑用 `plugins` 挂载。
4
4
 
5
+ 本轮补齐六件事:新增「接口能力」演示并把演示页扩到 22 个;`fitSpacing` 只提高适应比例、保持普通适应包围盒中心;参数速查补齐调用 / 响应口径与关联章节跳转;节点间距测量去掉并列中点比较;单组通道宽度未变时跳过整套车道重算;文档、自测与 API 清单同步收口。
6
+
5
7
  - 渲染层:原生 WebGL2(GLSL ES 3.00 + 实例化绘制),没有任何运行时依赖,也不需要 Vue。
6
8
  - 交互层:拖拽平移、滚轮缩放、按关键字搜索并定位(节点 / 线路 / 通道)、点击 / 双击拾取、点拖拽(相连线路同步移动)、Shift 拖动框选与 Ctrl 点击累加多选(多选后整组同步移动;`selectionKinds` 可限定只选节点 / 线路 / 通道或任意组合)、复制只带选中要素、节点合并可指定保留端并改写最终信息与样式、线路拐点编辑与通道多回线,全部走配置与回调 —— **这些能力缺省全关**,要哪一项显式打开(`features` 预设 / 开关表,或 `interactive: true` / `clickable: true` / `draggable: true` 这类详细配置,见 `docs/GisView.md` 的「功能开关」)。
7
9
  - 数据层:按属性筛选(点被过滤时绑在它身上的线路与通道一起消失,大数据量走 Web Worker)、按属性批量配色 / 换符号、节点标签(内容、贴边方式、HTML 定制、数量与显隐可配,十万级数据下有候选上限兜底)与点 / 线 / 通道的悬浮提示,筛选、规则与标签共用同一套条件语言;节点标签与线路标签分开配置、都支持 `auto`(智能隐藏)/ `always` / `hidden` 三种显隐策略,线路标签的位置沿线走(比例 / 百分比 / 绝对距离)、候选还会按屏幕小格摊开(通道里的并行回线不会把扫描名额占光),线路上还能按同样的位置写法挂图片或 SVG 装饰(`decorations`,通道回线可以用 `linePropsByMember` 逐条配;装饰缺省 `auto` —— 线路上没有拐点时按比例跟随(挂在中点就一直保持中点),有了拐点就跟拐点一样钉在线路上(位置不再沿路径挪动,线路几何一变就把锚点投回线身最近的点),写 `anchor: 'follow'` 强制一直跟随、`anchor: 'fixed'` + `at: [x, y]` 则固定在写死的坐标上;装饰与线路标签都能写 `parallel: true` 跟着线路方向转,通道里的每条回线也能各显示各的标签)。节点也能用 `points[].decorations` 挂图片 / SVG:`offset` 或 `distance` + `direction` 是相对节点的屏幕像素偏移(缩放时保持像素距离),`anchor: 'fixed'` / `at` 是固定世界坐标,`connector` 可从节点中心画连接线。平移 / 缩放 / 拖拽期间两类标签都能各自配 `hideOnInteract`(`true` 或者「标签数超过 N 才收」),收起时只切一个 class、不写标签 DOM,停手自动补齐;线路标签另有 `followOnInteract`(默认开)—— 没收起时拖节点 / 拖拐点期间跟着线路走,关掉则停手后统一归位;通道回线的提示范围用 `tooltip.channelScope` 在整条通道 / 所属分组 / 单条回线之间切。
@@ -11,6 +13,8 @@
11
13
  - 数据切换与加载信号:`updateData(points, lines, channels)` 一次换完整个场景(也收 `{ points, lines, channels }`;接口列名不一样时先在调用方映射成这三列,见 [06 数据与场景更新](docs/manual/06-数据与场景更新.md#接口列名不一样映射放在调用方)),场站 / 回线按批补用 `addPoints` / `addChannels`(批量通道只重排一次、只记一步历史),`onDataRendered` / `whenRendered` / `whenDataRendered` 给出「这一份数据已经画到屏幕上」的信号 —— loading 遮罩在这之后收,不会闪一帧旧画面。
12
14
  - 整包只有一个组件:`gis-view`,只从根入口导出;扩展点集中在插件、自定义图元 / 条件运算符与自定义导出格式(`defineExportFormat` / `registerExportFormat`),不需要改组件源码。
13
15
 
16
+ - 点聚合:`pointCluster` 用屏幕半径把密集点合成带数量文字的圆,支持半径 / 最少点数 / `maxZoom` 配置;平移不重算,缩放、数据与编辑状态变化才重排。只改展示、拾取、快照与导出,不改原始 `points` / `getData()`;`getClusters()` 读当前展示组(含单成员),`getPointCluster()` 读 `{ enabled, radius, minPoints, maxZoom, active }`,数量统计在 `getStats().clusterCount` / `clusterPointCount` / `clusterMs`;选中、拖动、合并与复制预览中的点强制保持单成员,点击聚合圆只发 `clusterClick`。
17
+
14
18
  ## 开发
15
19
 
16
20
  ```bash
@@ -19,15 +23,15 @@ npm run dev
19
23
  ```
20
24
 
21
25
  打开终端提示的地址,页面就是 `docs/demos/GisViewDemo.vue`:下拉第一项是仓库根目录 `new.json` 的真实数据(2164 个站点 / 3669 条线路 / 2629 条通道,点位与走向直接照它渲染),也可以切到同在根目录的样例数据 `test.json`(320 个站点 / 449 条线路,按 `channel_id` 聚合成 107 条多回线通道),或切回 900 / 5000 / 10 万的合成数据;真实与样例数据的线色、线宽和站点符号照 `test.js` 的 `getGisStyle` / `getSymbolPath` 分档,转成组件的批量规则表(`docs/demos/gisStyle.js`),关掉「按属性批量配色 / 换符号」就能对比同一份数据的两套外观。演示点击、双击、悬停、拖拽节点、Shift 框选与 Ctrl 点击多选、拐点编辑(加过 / 拖过拐点的通道与线路会归到「新增拐点集合」,读数栏下面直接列出来,点「导出拐点集合」把 `getParallelGroups()` 的结果复制到剪贴板),两端锚在节点上的二 / 三 / 四 / 五回线通道(可切换偏移方式、通道宽度与内收距离,也可以勾「隐藏通道端点节点」看通道与回线一起消失),以及新增的筛选下拉框(电压、站点类型、名称关键字、联动方式与「线路也按电压过滤」)、按属性配色换符号开关(站点符号可选原 SVG 路径或内置方块,路径描边粗细可调)、按电压分层(`zIndex`,高电压等级压在上面)、节点形状(圆 / 正方形 / 圆角方形)、标签位置 / 内容 / HTML 渲染 / 数量 / 候选上限与悬浮提示开关;筛选读数会显示隐藏了多少点 / 线、这次判定走的是 Web Worker 还是主线程、耗时多少。右下角的鹰眼图现在由组件自带(`minimap`):demo 只给尺寸、线路显隐、回线根数、拖动动作、步进、三份样式,以及这一轮新增的规则配色 / 分层开关 / 只看某几层(`levels`)/ 节点与线路跟随小图放大(`pointZoomScale` / `lineZoomScale`)(「可缩放」打开后滚轮与 - / + 按钮按 `zoomStep` 整体放大缩小鹰眼图自身,Ctrl / ⌘ + 滚轮留给主图;空白处拖动按 `dragAction` 平移小图或直接框选,按住 Ctrl / ⌘ 是另一个动作),缩略图画的是筛选后仍可见的节点与线路(含通道回线,关掉「回线」一个通道只画中间那一根),在视口框上按下直接拖框、空白处长按后视角跟随、单击不再改动视角。通道面板下面还有「通道分组」与「定向更新」两行:前者把选中的通道按每组的回线数拆开或合并回去(读数里能看到每个分组几回线、有没有拐点),后者改一个节点 / 线路 / 通道就立刻重渲染相关的视图,演示 `splitChannelGroups` / `mergeChannelGroups` / `moveChannelLine` / `updatePoint` / `updateLine` / `updateChannel` 这几个接口(右键框里每条回线都能单独分配:拆成新分组、或并进第 n 组,两回线拆成两个分组各一条);界面(按钮、滑块、读数浮层)仍然全部写在 demo 里。这一轮把 demo 重新排了一遍:说明段落收进「展开说明」、配置按组折进可折叠面板,画布右上角多了「全屏」按钮(`requestFullscreen` + `resize()`);节点标签默认只标最高电压那一档、线路标签默认不显示(面板上各有一份「显示范围」),新增的「规则更新」面板可以把规则写成 JSON 再点一下重新渲染。这一轮再把面板收进「视图 / 样式 / 数据」三个分页(整块面板栈独立滚动、鹰眼图设置默认折叠),画布默认垫一层由 `docs/demos/demoBackground.js` 现生成的 GeoJSON 底图(街区 + 环线 + 经纬网,可切三档底图与透明度),并新增「初始视图与背景」面板:首屏不再铺满整屏,默认聚焦数据中心放大 3 倍,也能切「适应全部 / 2× / 3×」或自己填中心点与倍数;demo 里还内置了一份插件示例 `focusSelectionPlugin`(按 F 聚焦当前选中),演示 `plugins` 的挂载与随视图销毁。
22
- 通道面板这一轮又多了一块:分组按整条通道的车道错开,拆分 / 合并不会挪动任何一条回线(两回线拆成两组各一条时两根线一左一右分居骨架两侧,四回线拆成两组时四条线还停在原来的位置上),想把某一组拐到别的走廊上就用「加拐点分开分组 / 去掉分开的拐点」按组挪拐点(`addChannelGroupWaypoint` / `removeChannelGroupWaypoint`);右键点通道回线弹出交互框,`onChannelContextMenu` 里给出这条通道有几组、当前是哪一组、每组几条回线与可用交互(点空白处收到 `null` 收框);「新增通道」点两次节点、草稿线跟着光标走,第二个节点点完在框里选「新建通道 / 变成新分组 / 并入某一组」,也可以勾自动提交或直接调 `addChannel`。右键框这一轮又加了「线路分配」:当前分组的每条回线各有一个下拉(拆成新分组 / 并入第 n 组)+「分配」按钮,走 `moveChannelLine`,组数 / 回线数到上限会被拒绝并在读数里给出原因。这一轮右键框再补齐:分组列表点一下就是 `setCurrentChannelGroup`(顺带选中这一组的回线、当前组高亮),下面一排「选中该分组 / 加拐点 / 删拐点 / 拆成多组 / 合并回一组」走 `runChannelAction`(灰掉的按钮悬停能看到原因),Esc / × / 点空白处收框;这两件事都要显式开开关:`features: { drag: true, channelMenu: true }` 与 `waypointEditable: true`(开关缺省全关;通道右键框还要 `contextMenu` 开着,通常给 `features: 'view'` 再加 `channelMenu: true`)。
26
+ 通道面板这一轮又多了一块:分组按整条通道的车道错开,拆分 / 合并不会挪动任何一条回线(两回线拆成两组各一条时两根线一左一右分居骨架两侧,四回线拆成两组时四条线还停在原来的位置上),想把某一组拐到别的走廊上就用「加拐点分开分组 / 去掉分开的拐点」按组挪拐点(`addChannelGroupWaypoint` / `removeChannelGroupWaypoint`);右键点通道回线弹出交互框,`onContextMenu` 里给出这条通道有几组、当前是哪一组、每组几条回线与可用交互(点空白处或节点时载荷没有 `channelContext`,据此收框);「新增通道」点两次节点、草稿线跟着光标走,第二个节点点完在框里选「新建通道 / 变成新分组 / 并入某一组」,也可以勾自动提交或直接调 `addChannel`。右键框这一轮又加了「线路分配」:当前分组的每条回线各有一个下拉(拆成新分组 / 并入第 n 组)+「分配」按钮,走 `moveChannelLine`,组数 / 回线数到上限会被拒绝并在读数里给出原因。这一轮右键框再补齐:分组列表点一下就是 `setCurrentChannelGroup`(顺带选中这一组的回线、当前组高亮),下面一排「选中该分组 / 加拐点 / 删拐点 / 拆成多组 / 合并回一组」走 `runChannelAction`(灰掉的按钮悬停能看到原因),Esc / × / 点空白处收框;这两件事都要显式开开关:`features: { drag: true, contextMenu: true }` 与 `waypointEditable: true`(开关缺省全关;通道右键框还要 `contextMenu` 开着,通常给 `features: 'view'` 再加 `contextMenu: true`)。
23
27
 
24
- 这一轮的 demo 拆成了「一个壳 + 多个标签页」:入口是 `docs/demos/DemoShell.vue` 配 `docs/demos/demos.js`(id / 标题 / 一句说明 / 懒加载的 `component`),切标签页会把 `?demo=<id>` 用 `replaceState` 写回地址栏,收藏一个链接就能直接打开某一页;顶部按基础 / 交互 / 样式 / 数据 / 性能 / 综合分组,一次只展开当前组。页签各自只演示一件事(现在是 19 个功能页 + 综合示例):`basic` 最小画布与拾取、`labels` 节点与线路标签(拖动 / 点击 / 右键 / 双击 / 批量加标签 API)、`label-display` 标签内容与显示策略、`minimap` 鹰眼图配置(尺寸 / 位置 / 范围 / 规则 / 层级 / 备份倍率 / 小图缩放 / 数据前后分段绘制 / 手势回调)、`style` 规则配色与符号、`hover-tooltip` 悬停高亮与提示模板、`channel` 通道与分组、`events` 事件日志、`copy-merge` 框选类型过滤(节点 / 线路 / 通道任意组合)、只复制选中要素、手动 `addPoint` 落点与自动副本二选一、指定保留来源 / 目标并改写最终信息与样式的合并、剪贴板、`batch-add` 多节点 / 线路 / 通道 / 分组 / 拐点的批量新增、`search-filter` 同步 / Worker 检索与筛选、`drag` 拖拽落点、`connect` 可视化连线(无通道也能只用点与线;直线 / 正交折线、箭头、颜色与线宽,取消 / 默认提交 / `addLines` / `addChannels` 三种落地)、`decoration` 动态装饰器(线路 / 节点分开开关或同时开启、悬停预览、节点偏移 / 固定 / 鼠标吸附、白名单与重复策略、确认才写数据)、`extension` 四类扩展点(插件 / 图元 / 运算符 / 导出格式,图元支持同名换画笔并当场重烘焙)、`backend` 编辑与保存、`perf` 大数据与画质档位、`effects` 动画效果、`graphic` 扩展图形(SVG 路径 / 图片 / Canvas,相机联动与运行时增删改),`full` 保留原来那份全功能 demo(缺省页签);共用数据与样式抽在 `docs/demos/shared/demoData.js` / `docs/demos/shared/demoStyle.css`,新增自测页 `docs/steps/selftest-demos.html` 逐个页签挂载一遍。
28
+ 这一轮的 demo 拆成了「一个壳 + 多个标签页」:入口是 `docs/demos/DemoShell.vue` 配 `docs/demos/demos.js`(id / 标题 / 一句说明 / 懒加载的 `component`),切标签页会把 `?demo=<id>` 用 `replaceState` 写回地址栏,收藏一个链接就能直接打开某一页;顶部按基础 / 交互 / 样式 / 数据 / 性能 / 综合分组,一次只展开当前组。页签各自只演示一件事(现在是 21 个功能页 + 综合示例):`basic` 最小画布与拾取、`labels` 节点与线路标签(拖动 / 点击 / 右键 / 双击 / 批量加标签 API)、`label-display` 标签内容与显示策略、`minimap` 鹰眼图配置(尺寸 / 位置 / 范围 / 规则 / 层级 / 备份倍率 / 小图缩放 / 数据前后分段绘制 / 手势回调)、`style` 规则配色与符号、`hover-tooltip` 悬停高亮与提示模板、`channel` 通道与分组、`events` 事件日志、`api-coverage` 接口能力(级联三种触发、程序化选中、四类 `focusOn`、装饰 / 通道写入、背景投影、历史与运行时清理)、`copy-merge` 框选类型过滤(节点 / 线路 / 通道任意组合)、只复制选中要素、手动 `addPoint` 落点与自动副本二选一、指定保留来源 / 目标并改写最终信息与样式的合并、剪贴板、`batch-add` 多节点 / 线路 / 通道 / 分组 / 拐点的批量新增、`search-filter` 同步 / Worker 检索与筛选、`drag` 拖拽落点、`connect` 可视化连线(无通道也能只用点与线;直线 / 正交折线、箭头、颜色与线宽,取消 / 默认提交 / `addLines` / `addChannels` 三种落地)、`decoration` 动态装饰器(线路 / 节点分开开关或同时开启、悬停预览、节点偏移 / 固定 / 鼠标吸附、白名单与重复策略、确认才写数据)、`extension` 四类扩展点(插件 / 图元 / 运算符 / 导出格式,图元支持同名换画笔并当场重烘焙)、`backend` 编辑与保存、`perf` 大数据与画质档位、`cluster` 点聚合(2 千 / 1.2 万 / 6 万 / 10 万点现场切,调半径 / 最少点数 / `maxZoom`,看聚合耗时与点击载荷)、`effects` 动画效果、`graphic` 扩展图形(SVG 路径 / 图片 / Canvas,相机联动与运行时增删改),`full` 保留原来那份全功能 demo(缺省页签);共用数据与样式抽在 `docs/demos/shared/demoData.js` / `docs/demos/shared/demoStyle.css`,新增自测页 `docs/steps/selftest-demos.html` 逐个页签挂载一遍。
25
29
 
26
30
  演示外壳会缓存已经加载过的模块;当前演示渲染完成后,利用空闲时间预取前后两个页签,失败时显示错误与「重新加载」,切页签不再重复下载。新增的 `extension` 页把 `view.use` / `registerSymbol` / `registerOperator` / `defineExportFormat` 四个扩展点放在同一块画布里,并给出注册表、插件挂载状态与业务 DTO 的同屏读数。
27
31
 
28
32
  源码本身就是 JS:`src/gis-view/*.js` 逐文件带中文注释,对外类型集中在 `src/gis-view/types.d.ts`(构建时复制成 `dist/index.d.ts`),不用先编译就能直接读。想照着从零写一遍看 [从 0 到完整的实现](docs/从0到完整的实现.md),里面有一份可以照抄的 mini 实现与验收清单。
29
33
 
30
- 想知道组件到底有哪些属性和方法:[GisView 文档](docs/GisView.md) 的「实例方法」一节把 241 个实例成员(方法 + `el` / `canvas` / `gl` 这类属性)与 90 个 `options` 键逐个列全;demo 的「视图 · 交互」页签里有同一份清单(`?demo=full`,可搜索、点一条就对当前视图求值);`node docs/steps/check-doc-coverage.mjs` 负责 10 项双向对账,改完源码或文档先跑 `node docs/steps/gen-api-index.mjs` 重生成 demo 读的那一份(三类查询的口径见 [docs/steps/README.md](docs/steps/README.md) 的「怎么查全部属性与方法」)。
34
+ 想知道组件到底有哪些属性和方法:[GisView 文档](docs/GisView.md) 的「实例方法」一节把 253 个实例成员(方法 + `el` / `canvas` / `gl` 这类属性)与 95 个 `options` 键逐个列全;demo 的「视图 · 交互」页签里有同一份清单(`?demo=full`,可搜索、点一条就对当前视图求值);`node docs/steps/check-doc-coverage.mjs` 负责 10 项双向对账,改完源码或文档先跑 `node docs/steps/gen-api-index.mjs` 重生成 demo 读的那一份(三类查询的口径见 [docs/steps/README.md](docs/steps/README.md) 的「怎么查全部属性与方法」)。
31
35
 
32
36
  ## 构建 npm 包
33
37
 
@@ -121,8 +125,8 @@ const view = createGisView('#map', {
121
125
  // 把某一组拉走:拆分之后各组已经错开,想把某一组拐到别的走廊上就给第 2…n 组加一个拐点(第 1 组原地不动)
122
126
  // view.addChannelGroupWaypoint('corridor-1', 'corridor-1::2')
123
127
  // view.removeChannelGroupWaypoint('corridor-1', 'corridor-1::2')
124
- // 右键通道回线:onChannelContextMenu 给出「几组 / 当前哪组 / 每组几条回线与可用交互」,点空白处收到 null,用它收框
125
- // onChannelContextMenu: (context, event) => { if (context) view.setCurrentChannelGroup(context.channelId, context.groups[1].id) }
128
+ // 右键通道回线:onContextMenu 在 payload.channelContext 里给出「几组 / 当前哪组 / 每组几条回线与可用交互」,点空白 / 节点时没有这份上下文,用它收框
129
+ // onContextMenu: (payload, event) => { const context = payload.channelContext; if (context) view.setCurrentChannelGroup(context.channelId, context.groups[1].id) }
126
130
  // view.runChannelAction('add-waypoint', { channelId: 'corridor-1', groupId: 'corridor-1::2' })
127
131
  // 新增通道:点两个节点出草稿(onChannelDraft 给两个节点的信息与可选方案),或绕开交互直接加一条
128
132
  // view.beginAddChannel({ autoCommit: true }); view.addChannel({ from: 'station-a', to: 'station-b' })
@@ -207,7 +211,7 @@ view.setLabelStates(view.getLabelStates()) // 持久化结果整份灌回来(
207
211
  // 点标签的载荷:label 是这一条标签自己(id / content / source / 合并后的位置与样式),parent 是它挂在哪条要素
208
212
  // 线路在通道里时 parent.channel 给 { id, groupId, index, slot, item } ——「点的是哪条通道的哪一回线」直接读
209
213
  view.on('labelClick', payload => console.log(payload.label.id, payload.label.content, payload.parent.channel?.groupId))
210
- // 右键默认把浏览器的系统菜单收掉(onContextMenu / onChannelContextMenu 照常发);想留系统菜单写 false
214
+ // 右键默认把浏览器的系统菜单收掉(onContextMenu 照常发);想留系统菜单写 false
211
215
  view.updateOptions({ preventContextMenu: false })
212
216
 
213
217
  // 导出带 labels 列(缺省 'auto' 只给有标签要说的记录带):记录原样喂回 points / lines 就能回显
@@ -215,6 +219,8 @@ const rows = view.exportData({ parts: ['station'], labels: true }).points
215
219
  view.setFilter(null)
216
220
  ```
217
221
 
222
+ 关于 `html: false`:它只关闭字符串 `content` 的 HTML 解析,标签容器仍然是逐条的 `.gis-view__label`(默认 `div`),所以单靠它不会减少 DOM —— 几万条纯文本标签仍会建几万个节点。顶层 `labelRender: 'auto' | 'canvas' | 'dom'` 控制这个容器,缺省 `'auto'`:在强制显示档(`labelOptimize` 未开、`denseMode` 未激活)下,节点层或线路层里能画布的纯文本标签达到 1024 条时,该层自动改画到一张 Canvas;要求优化档也画布时写 `'canvas'`,它不设数量阈值,`maxCount` / 避让仍照常执行。`html: true`、函数返回元素、可拖拽 / 可编辑及 Canvas 画不全的样式仍留在 DOM。要求保持旧行为可写 `labelRender: 'dom'`,运行时用 `updateOptions({ labelRender })` 切换。
223
+
218
224
  搜索与相机定位(搜索只找不藏,筛选才负责隐藏):
219
225
 
220
226
  ```js
@@ -291,7 +297,7 @@ view.zoomTo(12).zoomToBounds({ minX: 116, minY: 39, maxX: 117, maxY: 40 }, 60)
291
297
 
292
298
  - 这一轮(2 条需求:继续优化大数据性能并按场景把 WebGPU 做成开关 / 统一接收与导出格式并给一份包含全部数据的 JSON):① **标准数据文档**(`src/gis-view/document.js`)—— 顶层固定八列(`format` / `version` / `points` / `lines` / `channels` / `labelStates` / `view` / `filter`),节点 / 线路 / 通道 / 拐点(`path` 中间点)/ 标记(`decorations`)/ 标签(`labels`)与标签的运行时状态、相机、筛选全在里面;`normalizeDocument()` 做深拷贝 + JSON 安全(函数 / DOM / 画布 / `Map` / 循环引用跳过并记提醒,SVG → outerHTML、图片 → `{ type: 'image', src }`、`NaN` → null)+ 标准写法(坐标一律 `[x, y]`、`labels` / `decorations` 一律数组、通道一律展开 `groups`)+ 键序保留。整份文档可以直接当 `createGisView` 的入参(`view.camera` → `initialView`),`exportData({ format: 'full' })` 出的就是文档本身(没有 `ok` / `counts` / `warnings`),新增实例方法 `importData(input, options)` 整份 / 分列灌回(顶层出哪列应用哪列,返回 `{ ok, reason, applied, counts, warnings }`);`overlay.js` 的标签定位在要素被筛选隐藏时回落到原始数据,带筛选的文档回灌不丢标签状态。仓库根目录新增 `sample-document.json`(`scripts/build-sample-document.mjs` 从 `sample-full.json` 生成:10 点 / 22 线 / 3 通道 / 39 拐点 / 23 标记 / 44 标签 / 6 条标签状态),演示页「完整样本」多一枚「标准文档(整份)」芯片:与文件逐字节对账、导出 → 重建 → 再导出逐字节相同。② **WebGPU 做成开关**(`src/gis-view/renderer.js`):`renderer: 'auto' | 'webgl2' | 'webgpu'`(布尔值 `true` / `false` 等价于 `'webgpu'` / `'webgl2'`)加一份只碰 `navigator.gpu` 的能力探测(结果缓存、不创建画布)与实例方法 `getRendererInfo()`;当前绘制后端仍是 WebGL2(第 18 章的结论不变:十万级下 WebGPU 省不到填充率与 DOM 覆盖层的开销),`'auto'` 后台探一次不打扰使用方、`'webgl2'` 连探测都不跑、`'webgpu'` 明确报「已回退 WebGL2 + 原因」。③ **网格顶点缓存窗口**:`drawGrid` 原来每帧重建顶点并 `bufferData`,现在按「步长对齐的缓存窗口」(`GRID_WINDOW_PAD` 0.25)复用,真时间实测小步平移 40 帧只有 1 次上传(改前每帧一次)、大跳变 59 次,画面逐像素一致(16.19 已完成)。验证:新增 `docs/steps/selftest-data.html` 41 条断言全绿,十个老自测页真时间逐页全绿,`npx tsc --noEmit` 0 错、`npm run build` 通过、`node docs/steps/check-doc-coverage.mjs` 实例成员 193 / 数据字段 219 全部命中。
293
299
 
294
- - 这一轮(2 条需求:EventDemo 的合并 / 复制事件弹框 / ChannelDemo 的分组右键交互框):① **合并 / 复制弹框并解释「拖到一起鼠标为什么变」**(`docs/demos/EventDemo.vue`)—— 「拖到一起鼠标为什么会变」现在在面板上看得见:新增的光标读数直接读 `canvas.style.cursor`(事件触发 + 250ms 轮询),拖一个节点压到另一个节点上松手进「待确认合并」、Alt 点击出复制预览、`beginCopyPoint()` 等选点时都走 `cursorStyle.copy` 这一档(按住 Ctrl / ⌘ 还能用 `cursorStyle.ctrl.copy` 再换一份),读数下面写明引擎是在 `updateCursor()` 里切的档;合并 / 复制的中间态各挂一条确认条(与 `DragDropDemo` 的落点草稿同一套写法):`onPointMerge` 的 `pending`、`onPointCopy` 的 `start` / `move` 载荷存进 `shallowRef`,条上的按钮就是载荷给的裁决入口 —— 合并调 `payload.confirm()` / `payload.cancel()`,复制调 `payload.commit()` / `payload.cancel()`(Esc / 右键 / 再点一下也能收)。② **`copy.autoCommit` 真正接到交互入口**(`src/gis-view/engine.js`)—— 原先只有 `beginCopyPoint({ autoCommit: false })` 这条 API 路能停在预览态,Alt 点击与「先点按钮再选节点」两条交互路恒落地;现在 `beginPointCopy` / `insertCopyPreview` 都跟着 `settings.copy.autoCommit` 走,`copy: { autoCommit: false }` 时松开 Alt 的预览停在原地,等确认条上的 `payload.commit()` 或 `commitPointCopy()`(EventDemo 演示的就是这一条)。③ **分组右键交互框**(`docs/demos/ChannelDemo.vue`,参考 teap-gis 的 `ParallelGroupEditorDialog`)—— 显式开 `features: { drag: true, channelMenu: true }` 与 `waypointEditable: true` 之后右键通道回线出框(贴指针位置、按 `getStats()` 的 CSS 尺寸夹取,Esc / × / 点空白处收框),三段式:分组列表点一下 = `setCurrentChannelGroup`(顺带选中这一组的回线、当前组高亮)、每条回线一个下拉(拆成新分组 / 并入第 n 组)+「分配」= `moveChannelLine`(被拒的原因写进日志)、下面一排按钮 = `runChannelAction` 的五个动作(`enabled` / `reason` 由引擎算好,灰按钮的 `title` 就是原因)。验证:`npx tsc --noEmit` 0 错、`npm run build` 通过、`node docs/steps/check-doc-coverage.mjs` 实例成员 193 / 数据字段 219 全部命中;`selftest-demos.html` 新增第 4 节(合并 / 复制确认条 8 条)与第 5 节(右键交互框 7 条)断言、`selftest-features.html` 新增 5 条 `copy.autoCommit: false` 断言全绿(CDP 真时间:`selftest-demos` 74 通过 / `selftest-features` 264 通过);`selftest-demos` 里「标签交互」那条删除断言(删完标签数 13 → 14)在未改动的 HEAD 上同样失败,是既有问题,与本轮无关。
300
+ - 这一轮(2 条需求:EventDemo 的合并 / 复制事件弹框 / ChannelDemo 的分组右键交互框):① **合并 / 复制弹框并解释「拖到一起鼠标为什么变」**(`docs/demos/EventDemo.vue`)—— 「拖到一起鼠标为什么会变」现在在面板上看得见:新增的光标读数直接读 `canvas.style.cursor`(事件触发 + 250ms 轮询),拖一个节点压到另一个节点上松手进「待确认合并」、Alt 点击出复制预览、`beginCopyPoint()` 等选点时都走 `cursorStyle.copy` 这一档(按住 Ctrl / ⌘ 还能用 `cursorStyle.ctrl.copy` 再换一份),读数下面写明引擎是在 `updateCursor()` 里切的档;合并 / 复制的中间态各挂一条确认条(与 `DragDropDemo` 的落点草稿同一套写法):`onPointMerge` 的 `pending`、`onPointCopy` 的 `start` / `move` 载荷存进 `shallowRef`,条上的按钮就是载荷给的裁决入口 —— 合并调 `payload.confirm()` / `payload.cancel()`,复制调 `payload.commit()` / `payload.cancel()`(Esc / 右键 / 再点一下也能收)。② **`copy.autoCommit` 真正接到交互入口**(`src/gis-view/engine.js`)—— 原先只有 `beginCopyPoint({ autoCommit: false })` 这条 API 路能停在预览态,Alt 点击与「先点按钮再选节点」两条交互路恒落地;现在 `beginPointCopy` / `insertCopyPreview` 都跟着 `settings.copy.autoCommit` 走,`copy: { autoCommit: false }` 时松开 Alt 的预览停在原地,等确认条上的 `payload.commit()` 或 `commitPointCopy()`(EventDemo 演示的就是这一条)。③ **分组右键交互框**(`docs/demos/ChannelDemo.vue`,参考 teap-gis 的 `ParallelGroupEditorDialog`)—— 显式开 `features: { drag: true, contextMenu: true }` 与 `waypointEditable: true` 之后右键通道回线出框(贴指针位置、按 `getStats()` 的 CSS 尺寸夹取,Esc / × / 点空白处收框),三段式:分组列表点一下 = `setCurrentChannelGroup`(顺带选中这一组的回线、当前组高亮)、每条回线一个下拉(拆成新分组 / 并入第 n 组)+「分配」= `moveChannelLine`(被拒的原因写进日志)、下面一排按钮 = `runChannelAction` 的五个动作(`enabled` / `reason` 由引擎算好,灰按钮的 `title` 就是原因)。验证:`npx tsc --noEmit` 0 错、`npm run build` 通过、`node docs/steps/check-doc-coverage.mjs` 实例成员 193 / 数据字段 219 全部命中;`selftest-demos.html` 新增第 4 节(合并 / 复制确认条 8 条)与第 5 节(右键交互框 7 条)断言、`selftest-features.html` 新增 5 条 `copy.autoCommit: false` 断言全绿(CDP 真时间:`selftest-demos` 74 通过 / `selftest-features` 264 通过);`selftest-demos` 里「标签交互」那条删除断言(删完标签数 13 → 14)在未改动的 HEAD 上同样失败,是既有问题,与本轮无关。
295
301
 
296
302
  - 这一轮(5 条需求:选中抬层 / 直流标签 svg 图标 / Delete 删除(带确认框、开关、快捷键与事件)/ 通道选中模式 / 复制改为松开左键才结束):① **选中抬层**复核是引擎原有能力(`selectionOnTop` + `raisedPointIndexes` / `raisedLineIndexes`),没有代码改动。② **直流标签 svg**:`docs/demos/gisStyle.js` 的 `dcLineDecorationSymbol` 去掉 `tint`(白底被逐实例染成线路色 → 实心色块),装饰图标恢复成「白底 + 彩色图案」。③ **删除**:`engine.js` 新增 `normalizeRemoveOptions`(`enabled` / `confirm` / `scope`),`features.js` 补第 18 个开关 `remove`,`events.js` 补 `remove` 事件;`create.js` 抽出 `removeRefs` 并新增 `buildRemovePlan` / `removeImpactOfPoint` / `removeLineIdsOfScope` / `executeRemove` / `settleRemove` / `requestRemove` 与实例方法 `requestRemove` / `getRemoveRequest` / `resolveRemove` / `cancelRemove` —— Delete 与接口先出一份载荷(删什么 / 连带什么 / 「只删线路 / 整个分组 / 整条通道」三档范围 / counts 读数),`remove.confirm` 三档裁决(缺省停在 pending 由使用方弹框),一次删除只落一条历史、只发一条 `removeSelected`,确认时数据跟载荷对不上就整条作废;开关关着退回老行为(只删选中的拐点),`shortcuts.remove` 单独可配。④ **通道选中模式**:`channelSelectMode`(`'line'` 缺省 / `'group'` / `'channel'`)与引擎的 `expandChannelSelection`,点击 / Ctrl 点击按模式摊开与整批取反,`updateOptions` 只重画一帧。⑤ **复制落地口径**:松开左键才落地(`handlePointerUp` + `via: 'shortcut'` + `autoCommit`),松开 Alt 不再落地,`EventDemo.vue` 的确认条改成落地之后(`phase: 'end'`)才弹。验证:`npx tsc --noEmit` 0 错、`npm run build` 通过、`node docs/steps/check-doc-coverage.mjs` 实例成员 197 / 数据字段 221 全命中;`selftest-features.html` 284 条断言全绿,`selftest-demos.html` 80 PASS(唯一 FAIL 是 LabelDemo 既有的标签计数问题,与本轮无关)。
297
303
 
@@ -375,3 +381,13 @@ view.zoomTo(12).zoomToBounds({ minX: 116, minY: 39, maxX: 117, maxY: 40 }, 60)
375
381
  - 这一轮(「修改.txt」两条:装饰吸附分级 / 继续简化优化):① 动态装饰器的节点吸附改成两级:先选「跟随 / 固定」,固定档再选「固定位置 / 鼠标位置」;API 仍对应 `placement: offset / fixed / pointer`,鼠标位置档不传 `at`。② 合并 13 个重复的单项 / 清空快捷入口:`setEffects` / `setPointSize` / `setPointSymbol` / `setPointColor` / `setLineWidth` / `setLineColor` / `setChannelWidth` / `setChannelNodeWidth` / `setChannelInset` / `setChannelTangentInset` / `setChannelName` / `clearFilter` / `clearDropPreview` 从实例删除,分别统一到 `updateOptions({ effects })` / `updatePoint` / `updateLine` / `updateChannel` / `setFilter(null)` / `setDropPreview(null)`。③ `getChannel()` 改走引擎窄查询 `getChannel(channelId, index)`,不再复制全部通道再查找。实例成员 254 → **241**(方法 251 → **238**,属性仍为 3),数据字段 / options / 功能开关 / 分组保持 **238 / 90 / 18 / 16**。验证:14 个自测页 **2022** 条 0 FAIL(`selftest-features` 582、`selftest-demos` 283、`selftest-docs` 130);`npx tsc --noEmit`、`npm run check:docs`、`node docs/steps/gen-api-index.mjs --check`、`npm run build` 与 `git diff --check` 全部通过。
376
382
 
377
383
  - 这一轮(「修改.txt」两条:装饰 Demo 的吸附方式与位置解耦 / 继续简化优化):① 节点草稿的 `placement` 收窄为**位置来源**(`offset` 相对节点 / `fixed` 固定坐标 / `pointer` 鼠标位置),新增 `anchor` 表示**落地关系**(`follow` 跟随节点 / `fixed` 钉在世界坐标);两者自由组合,所以「跟随 / 固定」与三种位置来源的笛卡尔积都可用,缺省仍是 `offset → follow`、`fixed / pointer → fixed`。`DecorationDraftDemo.vue` 的「吸附方式」只留跟随 / 固定,「位置来源」三档始终显示,日志与确认文案同时显示当前组合。② 删除 `GisViewPointDecoration.placement` 这个与 `anchor` 重叠的旧别名,节点数据只保留 `anchor: 'offset' | 'fixed'`;草稿候选对象去掉未使用的 `placement` / `at` 临时字段,只留 `anchor` / `offset` / `world` / `screen`。实例成员 / 数据字段 / options / 功能开关 / 分组计数保持 **241 / 238 / 90 / 18 / 16**。验证:14 个自测页 **2031** 条 0 FAIL(`selftest-decoration` 75、`selftest-demos` 286,新增 9 条正交组合断言);`npx tsc --noEmit`、`npm run check:docs`、`node docs/steps/gen-api-index.mjs --check`、`npm run build` 与 `git diff --check` 全部通过。
384
+
385
+ - 这一轮(「修改.txt」三条:标签 HTML 为什么总按 HTML 渲染 / 继续排查隐藏性能问题与 bug / 可以破坏原有语义换性能):① HTML 开关并不是永远为真:默认 `false`,全局 `label.html` / `lineLabel.html`、数据 `labels[].html` 的逐条覆盖与 `updateLabel` 双向切换都生效;观察到「回灌后总变 HTML」的直接原因是导出只写 `html: true`,全局开、某条显式 `false` 被导出再导入后丢掉了覆盖 —— 现在显式 `true / false` 都保留,`labelClick` / `labelDrag` 载荷也带 `html`,HTML 内容回报 `innerHTML`,编辑面板不会再把 `<b>` / `<span>` 洗成纯文本。② 模板 `{field}` 在 HTML 模式按纯文本转义,`field` 与默认名称也转义,只有 `content` 模板和函数返回内容由使用方掌控;`getLabels()` 改为优先回报运行时拖出来的偏移,不再被默认 `[0, 0]` 洗掉。③ 渲染缓存拆成 class / 样式 / 内容 / 尺寸四份签名:只改样式不重写 `innerHTML`,只换位置不重写内容与样式,尺寸缓存去掉 index / labelId / 锚点,同内容标签在同批量测中直接复用;120 条同内容 HTML 标签首轮布局读取 **127 → 7** 次,只改样式 **121 次布局读 + 120 次 `innerHTML` → 3 次 + 0 次**,改内容仍逐条写 `innerHTML`,这是内容真的全变时的必要成本。
386
+
387
+ - 这一轮(「问题.txt」四条:要素显隐 / 节点拆分 / 标签逐条交互 / 通道变化对账):① 新增 `setFeatureVisible(kind, refs, visible)` / `hideFeatures` / `showFeatures`,统一处理 `point` / `line` / `channel` 的 `visible`;`refs` 收单个 / 批量 / 下标 / `{ id }` / `{ index }` / `'selection'`,整次调用只记一条历史、只发一条 `batch`。通道收起后成员、分组、骨架与编辑状态都保留;节点收起时绑定线路退回自身 `path` / `start` / `end`,以它为端点的通道整条退场,恢复后重新接上。② 新增 `splitPoint(source, parts, options?)`:至少两份,第一份沿用原下标与 id、其余追加;`lines` 分配线路,未分配的跟第一份;通道端点默认跟第一条被分配过的成员回线,也可用 `channels` / `channelFrom` / `channelTo` 显式指定;拆完删除自环 / 零长度线路、两端相同的通道与成员全空的通道,整次一步历史、一条 `splitPoint` 数据变更,`changes` 可直接给后端事务。③ 逐条标签新增 `draggable` / `editable`,并补上 `avoidOverlap` 类型声明;每条标签都能单独覆盖全局交互与避让开关。④ 新增 `getChangedChannels()` / `clearChangedChannels(refs?)`,按分组 / 成员 / 拐点 / 端点 / 可见性 / 其它字段报最近变化:新通道只补基准、删除通道清账、整份换声明重设基准,读取本身不重置。三个 demo 同步:`ChannelDemo` 演节点 / 线路 / 通道显隐与通道对账,`CopyMergeDemo` 演节点拆分,`LabelDemo` 演逐条标签交互。实例成员 / 方法 / 数据字段 / options 更新为 **247 / 244 / 242 / 91**。验证:4 个相关自测页 **1178** 条 0 失败(features 595、demos 292、labels 161、docs 130);`npx tsc --noEmit`、`npm run build`、`npm run check:docs`、`node docs/steps/gen-api-index.mjs --check`、`node --check` 与 `git diff --check` 全部通过。
388
+
389
+ - 这一轮(「修改.txt」三条:通道筛选 / 线路对账 / 继续优化):① `getChangedChannels(options?)` 加 `reasons` 白名单与 `ignoreGroups`。`reasons: ['waypoints']` 只返回拐点变化并同步收窄 `*Changed` / `fields`,空数组直接返回空表、不扫数据;`ignoreGroups: true` 忽略分组新增 / 删除 / 重排,只比较基准与当前都存在的分组 `path`,已有分组真改拐点仍报 `waypoints`。缺省 `path` 与空数组统一成「没有拐点」,分组声明刚落数据不会误报。② 新增 `getChangedLines(options?)` / `clearChangedLines(refs?)`,给只有节点和线路、没有通道的场景独立对账:原因分 `path`(`path` / `start` / `end`)、`endpoints`(`from` / `to`)、`visibility`、`fields`;新线路只补账、删除线路同步清账,通道骨架变化不重复算进线路账本。③ 通道 / 线路删除都会清账:`removeChannel`、`removeLines` 与线路删除带走的通道 / 回线不会再出现在下一次查询里。实例成员 / 方法更新为 **249 / 246**,数据字段 / options 保持 **242 / 91**。验证:4 个相关自测页 **1186** 条 0 失败(features 603、demos 292、labels 161、docs 130);`node --check`、`npx tsc --noEmit`、`npm run check:docs`、`node docs/steps/gen-api-index.mjs --check`、`npm run build` 与 `git diff --check` 全部通过。
390
+
391
+ - 这一轮(「修改.txt」:channelDemo 分组弹框参考图片改为整组编辑):`docs/demos/ChannelDemo.vue` 新增居中的「线路分组」弹框,按分组卡片列出回线与「分组 N / N 条线路」,入口在通道分组面板和右键交互框;每行可连续选择目标组并「确认移入」,行内操作只改本地草稿,取消 / Esc / 点遮罩都不写组件数据;底部「确定分组」才调用一次 `splitChannelGroups(channelId, groups)`,整份 groups 一次落地,避免逐条请求与多步历史。右键框原有的逐条分配 / `moveChannelLine` 保留。`selftest-demos.html` 补 4 条断言(弹框分组与副标题、行内移入只改草稿、取消不写数据、确定只调用一次整组方法),292 → **296** 条;四页共 **1190** 条 0 失败,`npx tsc --noEmit`、`npm run check:docs`、`node docs/steps/gen-api-index.mjs --check`、`npm run build` 与 `git diff --check` 全通过;实例成员 / 方法 / 数据字段 / options 保持 **249 / 246 / 242 / 91**。
392
+
393
+ - 这一轮(「修改.txt」两条:Ctrl 加点高亮范围 / 分组拐点拖动类型):① `channelSelectMode` 现在同时管普通点选和 Ctrl / ⌘ + 点击线路加点后的选中集,`line` 只高亮被加点回线、`group` 连所在分组、`channel` 连整条通道;切换仍只重画、不重排。② 新增 `channelWaypointDragMode`(缺省 `parallel`,可选 `vertex`):`vertex` 拖动只把当前分组同一 `pathIndex` 的拐点按相同世界增量平移,相邻顶点与其它分组不动,结果写进 `groups[].waypointOffsets`(键按含首尾端点的骨架序号,值支持 `[dx, dy]` / `{ x, y }`)。`getChannelGroups()` / `getChannelGroupWaypoints()` / `getChannels()` 带偏移,`getParallelGroups()` 与 `exportData` 的 `group_route` 已叠成实际路由;增删拐点时偏移序号同步平移,切回 `parallel` 不会清空已有偏移。③ `ChannelDemo` 加模式下拉与「准备两分组拐点」,拐点后端请求同时带 `path` 和 `waypointOffsets`。自测 `selftest-features` 603 → **611**、`selftest-demos` 296 → **297**,四页共 **1199** 条 0 失败;`node --check`、`npx tsc --noEmit`、`npm run check:docs`(249 个成员 / 246 个方法 / 245 个数据字段 / 92 个 options)、`node docs/steps/gen-api-index.mjs --check`、`npm run build` 与 `git diff --check` 全部通过。
package/dist/gis-view.css CHANGED
@@ -1 +1 @@
1
- .gis-view{box-sizing:border-box;--gis-view-cursor-grab:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path d='M12 2 16 6 13 6 13 11 18 11 18 8 22 12 18 16 18 13 13 13 13 18 16 18 12 22 8 18 11 18 11 13 6 13 6 16 2 12 6 8 6 11 11 11 11 6 8 6Z' fill='%23ffffff' stroke='%23ffffff' stroke-width='3' stroke-linejoin='round'/><path d='M12 2 16 6 13 6 13 11 18 11 18 8 22 12 18 16 18 13 13 13 13 18 16 18 12 22 8 18 11 18 11 13 6 13 6 16 2 12 6 8 6 11 11 11 11 6 8 6Z' fill='%2325313f'/></svg>") 12 12, grab;--gis-view-cursor-grabbing:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path d='M12 2 16 6 13 6 13 11 18 11 18 8 22 12 18 16 18 13 13 13 13 18 16 18 12 22 8 18 11 18 11 13 6 13 6 16 2 12 6 8 6 11 11 11 11 6 8 6Z' fill='%23ffffff' stroke='%23ffffff' stroke-width='3' stroke-linejoin='round'/><path d='M12 2 16 6 13 6 13 11 18 11 18 8 22 12 18 16 18 13 13 13 13 18 16 18 12 22 8 18 11 18 11 13 6 13 6 16 2 12 6 8 6 11 11 11 11 6 8 6Z' fill='%232f6fd0'/></svg>") 12 12, grabbing;--gis-view-cursor-move:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path d='M12 2 16 6 13 6 13 11 18 11 18 8 22 12 18 16 18 13 13 13 13 18 16 18 12 22 8 18 11 18 11 13 6 13 6 16 2 12 6 8 6 11 11 11 11 6 8 6Z' fill='%23ffffff' stroke='%23ffffff' stroke-width='3' stroke-linejoin='round'/><path d='M12 2 16 6 13 6 13 11 18 11 18 8 22 12 18 16 18 13 13 13 13 18 16 18 12 22 8 18 11 18 11 13 6 13 6 16 2 12 6 8 6 11 11 11 11 6 8 6Z' fill='%23c2761b'/></svg>") 12 12, move;--gis-view-cursor-box:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path d='M12 3.5V9M12 15v5.5M3.5 12H9M15 12h5.5' fill='none' stroke='%23ffffff' stroke-width='4.6' stroke-linecap='round'/><circle cx='12' cy='12' r='2.8' fill='%23ffffff'/><path d='M12 3.5V9M12 15v5.5M3.5 12H9M15 12h5.5' fill='none' stroke='%2325313f' stroke-width='1.8' stroke-linecap='round'/><circle cx='12' cy='12' r='1.4' fill='%2325313f'/></svg>") 12 12, crosshair;--gis-view-cursor-copy:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><g fill='none' stroke-linejoin='round'><rect x='4' y='4' width='11' height='11' rx='2' stroke='%23ffffff' stroke-width='5.4'/><rect x='9' y='9' width='11' height='11' rx='2' stroke='%23ffffff' stroke-width='5.4'/><rect x='4' y='4' width='11' height='11' rx='2' stroke='%2325313f' stroke-width='2'/><rect x='9' y='9' width='11' height='11' rx='2' stroke='%2325313f' stroke-width='2'/></g></svg>") 12 12, copy;--gis-view-cursor-point:pointer;--gis-view-cursor-line:pointer;--gis-view-cursor-channel:pointer;--gis-view-cursor-waypoint:pointer;outline:none;width:100%;height:100%;position:relative;overflow:hidden}.gis-view__canvas{touch-action:none;width:100%;height:100%;display:block}.gis-view__graphics{pointer-events:none;z-index:1;isolation:isolate;position:absolute;inset:0;overflow:hidden}.gis-view__graphic{pointer-events:none;will-change:transform, left, top, width, height;position:absolute;top:0;left:0}.gis-view__graphic-image{pointer-events:none;-webkit-user-select:none;user-select:none}.gis-view__labels{pointer-events:none;z-index:2;contain:layout style paint;isolation:isolate;transform-origin:0 0;will-change:transform;position:absolute;inset:0;overflow:hidden}.gis-view__label-layer{position:absolute;inset:0}.gis-view__label-layer--hidden{visibility:hidden}.gis-view__label{white-space:nowrap;color:#1f3550;background:#ffffffe0;border-radius:4px;padding:1px 6px;font-size:12px;line-height:16px;position:absolute;top:0;left:0}.gis-view__label--draggable,.gis-view__label--editable{pointer-events:auto;touch-action:none}.gis-view__label--draggable{cursor:grab}.gis-view__label--editable{cursor:pointer}.gis-view__label--dragging{cursor:grabbing;z-index:2;-webkit-user-select:none;user-select:none}.gis-view__background-label{white-space:nowrap;pointer-events:none;text-shadow:0 1px 2px #ffffffe6,0 0 2px #fffc;position:absolute;top:0;left:0}.gis-view__tooltip{color:#f3f7fc;white-space:pre-line;pointer-events:none;will-change:transform;background:#172436eb;border-radius:6px;max-width:280px;padding:6px 10px;font-size:12px;line-height:18px;position:absolute;top:0;left:0}.gis-view__tooltip[hidden]{display:none}.gis-view__minimap{z-index:2;box-sizing:border-box;contain:layout paint;isolation:isolate;touch-action:none;cursor:default;position:absolute;overflow:hidden}.gis-view__minimap[hidden]{display:none}.gis-view__minimap-canvas{width:100%;height:100%;display:block}.gis-view__minimap-box{box-sizing:border-box;pointer-events:none;will-change:transform;position:absolute;top:0;left:0}.gis-view__minimap-select{box-sizing:border-box;pointer-events:none;will-change:transform;background:#3678bf1f;border:1px dashed #3678bf;display:none;position:absolute;top:0;left:0}
1
+ .gis-view{box-sizing:border-box;--gis-view-cursor-grab:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path d='M12 2 16 6 13 6 13 11 18 11 18 8 22 12 18 16 18 13 13 13 13 18 16 18 12 22 8 18 11 18 11 13 6 13 6 16 2 12 6 8 6 11 11 11 11 6 8 6Z' fill='%23ffffff' stroke='%23ffffff' stroke-width='3' stroke-linejoin='round'/><path d='M12 2 16 6 13 6 13 11 18 11 18 8 22 12 18 16 18 13 13 13 13 18 16 18 12 22 8 18 11 18 11 13 6 13 6 16 2 12 6 8 6 11 11 11 11 6 8 6Z' fill='%2325313f'/></svg>") 12 12, grab;--gis-view-cursor-grabbing:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path d='M12 2 16 6 13 6 13 11 18 11 18 8 22 12 18 16 18 13 13 13 13 18 16 18 12 22 8 18 11 18 11 13 6 13 6 16 2 12 6 8 6 11 11 11 11 6 8 6Z' fill='%23ffffff' stroke='%23ffffff' stroke-width='3' stroke-linejoin='round'/><path d='M12 2 16 6 13 6 13 11 18 11 18 8 22 12 18 16 18 13 13 13 13 18 16 18 12 22 8 18 11 18 11 13 6 13 6 16 2 12 6 8 6 11 11 11 11 6 8 6Z' fill='%232f6fd0'/></svg>") 12 12, grabbing;--gis-view-cursor-move:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path d='M12 2 16 6 13 6 13 11 18 11 18 8 22 12 18 16 18 13 13 13 13 18 16 18 12 22 8 18 11 18 11 13 6 13 6 16 2 12 6 8 6 11 11 11 11 6 8 6Z' fill='%23ffffff' stroke='%23ffffff' stroke-width='3' stroke-linejoin='round'/><path d='M12 2 16 6 13 6 13 11 18 11 18 8 22 12 18 16 18 13 13 13 13 18 16 18 12 22 8 18 11 18 11 13 6 13 6 16 2 12 6 8 6 11 11 11 11 6 8 6Z' fill='%23c2761b'/></svg>") 12 12, move;--gis-view-cursor-box:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path d='M12 3.5V9M12 15v5.5M3.5 12H9M15 12h5.5' fill='none' stroke='%23ffffff' stroke-width='4.6' stroke-linecap='round'/><circle cx='12' cy='12' r='2.8' fill='%23ffffff'/><path d='M12 3.5V9M12 15v5.5M3.5 12H9M15 12h5.5' fill='none' stroke='%2325313f' stroke-width='1.8' stroke-linecap='round'/><circle cx='12' cy='12' r='1.4' fill='%2325313f'/></svg>") 12 12, crosshair;--gis-view-cursor-copy:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><g fill='none' stroke-linejoin='round'><rect x='4' y='4' width='11' height='11' rx='2' stroke='%23ffffff' stroke-width='5.4'/><rect x='9' y='9' width='11' height='11' rx='2' stroke='%23ffffff' stroke-width='5.4'/><rect x='4' y='4' width='11' height='11' rx='2' stroke='%2325313f' stroke-width='2'/><rect x='9' y='9' width='11' height='11' rx='2' stroke='%2325313f' stroke-width='2'/></g></svg>") 12 12, copy;--gis-view-cursor-point:pointer;--gis-view-cursor-line:pointer;--gis-view-cursor-channel:pointer;--gis-view-cursor-waypoint:pointer;outline:none;width:100%;height:100%;position:relative;overflow:hidden}.gis-view__canvas{touch-action:none;width:100%;height:100%;display:block}.gis-view__graphics{pointer-events:none;z-index:1;isolation:isolate;position:absolute;inset:0;overflow:hidden}.gis-view__graphic{pointer-events:none;will-change:transform, left, top, width, height;position:absolute;top:0;left:0}.gis-view__graphic-image{pointer-events:none;-webkit-user-select:none;user-select:none}.gis-view__labels{pointer-events:none;z-index:2;contain:layout style paint;isolation:isolate;transform-origin:0 0;will-change:transform;position:absolute;inset:0;overflow:hidden}.gis-view__label-layer{position:absolute;inset:0}.gis-view__label-layer--hidden{visibility:hidden}.gis-view__label-canvas{pointer-events:none;position:absolute;top:0;left:0}.gis-view__label{white-space:nowrap;color:#1f3550;background:#ffffffe0;border-radius:4px;padding:1px 6px;font-size:12px;line-height:16px;position:absolute;top:0;left:0}.gis-view__cluster-label{color:#fff;letter-spacing:.2px;pointer-events:none;-webkit-user-select:none;user-select:none;text-shadow:0 1px 1px #102a5059;white-space:nowrap;justify-content:center;align-items:center;min-width:16px;height:16px;font-size:12px;font-weight:600;line-height:16px;display:flex;position:absolute;top:0;left:0}.gis-view__label--draggable,.gis-view__label--editable{pointer-events:auto;touch-action:none}.gis-view__label--draggable{cursor:grab}.gis-view__label--editable{cursor:pointer}.gis-view__label--dragging{cursor:grabbing;z-index:2;-webkit-user-select:none;user-select:none}.gis-view__background-label{white-space:nowrap;pointer-events:none;text-shadow:0 1px 2px #ffffffe6,0 0 2px #fffc;position:absolute;top:0;left:0}.gis-view__tooltip{color:#f3f7fc;white-space:pre-line;pointer-events:none;will-change:transform;background:#172436eb;border-radius:6px;max-width:280px;padding:6px 10px;font-size:12px;line-height:18px;position:absolute;top:0;left:0}.gis-view__tooltip[hidden]{display:none}.gis-view__minimap{z-index:2;box-sizing:border-box;contain:layout paint;isolation:isolate;touch-action:none;cursor:default;position:absolute;overflow:hidden}.gis-view__minimap[hidden]{display:none}.gis-view__minimap-canvas{width:100%;height:100%;display:block}.gis-view__minimap-box{box-sizing:border-box;pointer-events:none;will-change:transform;position:absolute;top:0;left:0}.gis-view__minimap-select{box-sizing:border-box;pointer-events:none;will-change:transform;background:#3678bf1f;border:1px dashed #3678bf;display:none;position:absolute;top:0;left:0}