gis-view-webgl2 0.1.7 → 0.1.8
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 +9 -8
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +232 -228
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +49 -0
- package/docs/GisView.md +1 -1
- 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 +48 -0
- package/docs/demos/BasicDemo.vue +2 -2
- package/docs/demos/BatchAddDemo.vue +6 -6
- package/docs/demos/ConnectDemo.vue +164 -33
- package/docs/demos/CopyMergeDemo.vue +27 -14
- package/docs/demos/EventDemo.vue +17 -20
- package/docs/demos/LabelDemo.vue +5 -5
- package/docs/demos/api-index.js +31 -29
- package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +1 -1
- package/docs/demos/gis-view-demo/GisViewDemoStage.vue +1 -1
- package/docs/demos/gis-view-demo/panels/DataPanels.vue +1 -1
- package/docs/demos/gis-view-demo/useGisViewDemo.js +31 -33
- 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 +8 -7
- package/docs/from0/05-/347/233/270/346/234/272/344/270/216/344/272/244/344/272/222.md +1 -1
- package/docs/from0/07-/351/200/232/351/201/223/345/210/206/347/273/204/344/270/216/345/244/232/345/233/236/347/272/277.md +26 -10
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +1 -1
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +21 -16
- 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 +9 -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 +54 -31
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +3 -3
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +9 -6
- 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 +226 -493
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +2 -2
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +12 -13
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +23 -21
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +19 -17
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +125 -78
- 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 +1 -1
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +2 -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 +4 -4
- package/docs/steps/README.md +9 -7
- package/docs/steps/bench-scale.html +1 -1
- package/docs/steps/index.html +6 -6
- package/docs/steps/selftest-actions.html +26 -25
- package/docs/steps/selftest-data.html +1 -1
- package/docs/steps/selftest-demos.html +102 -6
- package/docs/steps/selftest-docs.html +3 -3
- package/docs/steps/selftest-features.html +283 -51
- package/docs/steps/selftest-full.html +3 -3
- package/docs/steps/selftest-labels.html +1 -1
- package/docs/steps/selftest-perf.html +31 -4
- package/docs/steps/selftest-select.html +20 -5
- package/docs/steps/step-05-pick.js +4 -8
- package/docs/steps/step-07-channels.js +3 -5
- package/docs/steps/step-08-labels.js +3 -3
- package/docs/steps/step-10-full.js +3 -3
- package/docs/steps/step-12-events.js +41 -24
- package/package.json +1 -1
|
@@ -530,3 +530,52 @@
|
|
|
530
530
|
- **冗余与性能**:通道重建时按 `channelIndex` 同步收好分组状态数组,并把 `groupOrdinal` 直接记在通道状态上;`channelSiblings()` / `channelGroupOrdinal()` 从扫整张通道表降为 O(1),构造一条通道的全部分组读数从 O(组数 × 通道数) 降为 O(组数)。选中上下文按已记录的组序号取当前组,不再 `findIndex`;删掉已无引用的 `state.order` 和建通道时的重复 `channels.filter(...)`。单通道选中不再构造随后丢弃的 `lineIndexes` / `lineIds`,多通道才在 `channelContexts` 里按选中顺序补一次。
|
|
531
531
|
- **核心文件**:`src/gis-view/engine.js`、`src/gis-view/create.js`、`src/gis-view/types.d.ts`、`docs/steps/selftest-select.html`、`docs/steps/README.md`、`docs/manual/04-事件与回调.md`、`07-指针与要素编辑.md`、`09-通道与多回线.md`、`20-性能与大数据.md`、`src/gis-view/README.md` 与本轮 aimd。
|
|
532
532
|
- **验证**:`node --check`(engine / create)、`npx tsc --noEmit`、`npm run build` 通过;五页定向自测 **1438** 条通过 / 0 失败(select 84 / features 672 / docs 134 / demos 365 / perf 183);`node docs/steps/gen-api-index.mjs --check` PASS(索引最新)、`npm run check:docs` 全 PASS(265 个实例成员 / 250 个数据字段 / 96 个 options / 43 个事件 / 17 个功能开关)、`git diff --check` 通过(仅有 CRLF 提示)。公开实例成员与配置计数未变。
|
|
533
|
+
|
|
534
|
+
## 2026-10-26 增量(「修改.txt」:选中 items 去重与选中通道缓存)
|
|
535
|
+
|
|
536
|
+
本轮仍是现有会话的继续增量,没有新建会话归档,因此不改上表的 39 条会话统计。需求原话来自仓库根目录最新的 `修改.txt`:「修改gis-view组件 1、onSelect和onSelectChange 参数最外层有通道信息,items里面的线路对象也有通道信息,是不是重复了,删减会不会提高性能 2、继续检查有没有类似重复参数会影响性能」。
|
|
537
|
+
|
|
538
|
+
- **载荷再次去重**:`onSelect` / `onSelectChange` 的 `items[]` 不再挂任何通道字段(`channelId` / `channelIndex` / `channel` / `groupId` / `groupIndex` / `group` / `groups` / `lineIndexes` / `lineIds`)。单通道把通道上下文与本次选中的 `lineIndexes` / `lineIds` 摊平到载荷顶层,多通道只在 `channelContexts[]` 的每项给同一套字段;`groups[].lineIds` 是整组成员,外层 `lineIndexes` / `lineIds` 是本次选中子集。通道归属只看外层或对单条线路调 `getLineChannel(index)`。
|
|
539
|
+
- **引擎窄路**:`describe()` / `describeAll()` 增加 `{ withChannel: false }`,`describeSelection()` 与点击选中都走它;新增 `selectionPickOf()` 剥掉单击拾取结果的通道字段。`selectionChannelContexts()` 对单通道 / 多通道都保留 `lineIndexes` / `lineIds`,不再为“没人读”的中间载荷省掉映射。
|
|
540
|
+
- **组件侧缓存**:`syncSelections()` 按载荷外层 `lineIndexes` / `channelContexts` 一次建立“线路下标 → 通道”缓存;`selectionInfo`、`selectedCopyElements`、`getSelectionData()` 直接查表,选中数组换新时按引用失效,其他修改路径用引擎 O(1) 的 `getLineChannel()` 补建,不再逐条 `describe()`。`refreshChannelLineReadouts()` 对选中项只刷新 `coordinate` / `screen` / `distance`,悬停仍保留完整通道字段。
|
|
541
|
+
- **类似重复审计**:`group` 与 `groups[groupIndex]` 是同一个对象引用,`onSelectChange.action.data` 与 `items` 是同一个数组引用,共享引用不产生第二份数据,故保留;指针 / 悬停载荷构造改为先展开拾取结果,再补 `action` 与光标读数,去掉 `type` / `index` / `item` 的重复赋值。悬停分组读数仍是单份构造,没有在载荷里复制。
|
|
542
|
+
- **公开行为变化**:`getSelections()` 与 `getSelectionData().items` 里的线路同样不再带通道字段;需要单条归属时调 `getLineChannel(index)`,事件回调里优先用顶层 `lineIndexes` / `lineIds` 或 `channelContexts`。自测已同步改为断言这些字段**不存在**,并验证实时归属仍可读。
|
|
543
|
+
- **核心文件**:`src/gis-view/engine.js`、`src/gis-view/create.js`、`src/gis-view/types.d.ts`、`docs/steps/selftest-select.html`、`docs/steps/selftest-features.html`、`docs/steps/README.md`、`docs/manual/04-事件与回调.md`、`07-指针与要素编辑.md`、`08-选中与拐点.md`、`09-通道与多回线.md`、`16-实例方法总表.md`、`17-参数速查.md`、`20-性能与大数据.md`、`src/gis-view/README.md`、`docs/demos/api-index.js`(生成物)与本轮 aimd。
|
|
544
|
+
- **验证**:`node --check`(engine / create)、`npx tsc --noEmit`、`npm run build` 通过;定向自测 `selftest-select.html` **85** 条、`selftest-features.html` **672** 条、`selftest-docs.html` **134** 条、`selftest-demos.html` **365** 条全部通过;`selftest-perf.html` **182 / 1**,唯一失败仍是既有的“改一条可见标签后的定向重排”读数断言(1313 次 / 重复 0 条),与选中链路无关。`node docs/steps/gen-api-index.mjs --check` 与 `npm run check:docs`(265 个实例成员 / 250 个数据字段 / 96 个 options / 43 个事件 / 17 个功能开关)全部通过;`git diff --check` 仅有 CRLF 提示。公开实例成员 / options / 数据字段 / 事件 / 功能开关计数均未变。
|
|
545
|
+
|
|
546
|
+
## 2026-10-27 增量(「修改.txt」:事件载荷去重与指针回调收口)
|
|
547
|
+
|
|
548
|
+
本轮仍是现有会话的继续增量,没有新建会话归档,因此不改上表的 39 条会话统计。需求来源是仓库根目录 `修改.txt`(当前落盘内容已损坏为问号,按本轮实际落实的 gis-view 事件载荷去重与性能优化记录)。
|
|
549
|
+
|
|
550
|
+
- **单参数回调**:`onPointerDown` / `onPointerUp` / `onContextMenu` / `onClusterClick` 的实例回调、事件总线与内部监听统一只给一个 `payload`;原始 DOM 事件、鼠标键、修饰键和浏览器坐标都读 `payload.originalEvent`,不再另传第二个 `event`。
|
|
551
|
+
- **载荷去重**:删除 `payload.selection`、`action.data`、`action.button` / `action.modifiers` / `action.left` / `action.right`,指针载荷也不再复制 `button` / `buttons` / `clientX` / `clientY`。命中数据直接放载荷顶层:`item` / `channel` / `channelContext` / `items` / `lineIndex` / `pathIndex` / `coordinate` / `line` / `label` / `parent` 与聚合字段;`selected` / `wasSelected` 保留为事件时刻的状态快照。
|
|
552
|
+
- **替代入口**:选中集总读数改调新增的 `getSelectionInfo()`(按选中集引用缓存 `{ ids, count, points, lines, channels }`);右键线路读 `payload.index` / `payload.item.id`,再用 `getLineChannel(index)` 查通道;标签右键 / 双击的光标位置读 `payload.originalEvent`;复制改读 `payload.item` / `item.x` / `item.y` / `items[0]`,回写用 `payload.update()`。
|
|
553
|
+
- **性能**:`withPointerCoordinates()` 对引擎刚交出的载荷就地补 `lngLat`,高频指针 / 悬停路径不再逐次展开一份对象;同时去掉重复包装和已可推导的派生字段,减少载荷构造与遍历成本。
|
|
554
|
+
- **计数与文档**:公开实例成员变为 **266**(新增 `getSelectionInfo`),数据字段 250、options 96、事件 43、功能开关 17 未变。两份 README、manual 04/07/10/12/16/17/18/20、format/from0 教程、Event/Label/Basic/GisView demos、第 5/7/8/10/12 步与 7 个自测页都同步到新口径;第 5 步教学实现也改为单参数回调,第 7 步草稿载荷不再重复挂 `clientX` / `clientY`;README 与 steps/README 的当前计数同步到 266 / 261。
|
|
555
|
+
- **核心文件**:`src/gis-view/engine.js`、`create.js`、`overlay.js`、`events.js`、`types.d.ts`、`README.md`、`src/gis-view/README.md`、`docs/manual/04-事件与回调.md`、`07-指针与要素编辑.md`、`10-通道编辑与新增.md`、`12-标签与装饰.md`、`16-实例方法总表.md`、`17-参数速查.md`、`18-逐方法示例.md`、`20-性能与大数据.md`、`docs/format/07-事件载荷与示例数据.md`、`docs/from0/05-相机与交互.md`、`07-通道分组与多回线.md`、`docs/demos/**`、`docs/steps/**` 与本轮 aimd。
|
|
556
|
+
- **验证**:`node --check`(engine / create / overlay / events / step-05-pick / step-07-channels / step-12-events)、`npx tsc --noEmit`、`npm run build`、`git diff --check` 通过;`node docs/steps/gen-api-index.mjs --check` PASS,`npm run check:docs` PASS(266 / 250 / 96 / 43 / 17);7 页定向自测 **1611** 条通过 / 0 失败(actions 96 / labels 165 / features 672 / select 85 / full 94 / docs 134 / demos 365)。复查入口见 [06 的增量记录](ai/06-会话索引与复查方法.md#2026-10-27-增量修改txt事件载荷去重与指针回调收口)。
|
|
557
|
+
|
|
558
|
+
## 2026-10-27 增量(「问题.txt」:新增线路拆分、单一通道策略与解除合并分线)
|
|
559
|
+
|
|
560
|
+
本轮仍是现有会话的继续增量,没有新建会话归档,因此不改上表的 39 条会话统计。需求来源是仓库根目录 `问题.txt`:① 优化 `beginAddChannel` 的方法规范,评估拆成 `beginAddLine` / `beginAddChannel`;② 支持单一通道模式,已有通道时可失败或转为新增回线,并可指定分组;③ 已有通道时新回线可建新分组或并进指定分组;④ 在 demo 中体现;⑤ 优化解除合并并在 demo 中体现。
|
|
561
|
+
|
|
562
|
+
- **入口拆分**:新增公开方法 `beginAddLine()`,与 `beginAddChannel()` 共用 `beginExtendedAddDraft()`;前者固定线路语义,后者保留新建通道语义,旧写法 `beginAddChannel({ kind: 'line' })` 继续兼容。`beginAddLine` 显式挂已有通道时,草稿 `kind` 仍是 `line`,落地成通道成员后结果 / `commit` / `blocked` 载荷的 `kind` 为 `channel`。
|
|
563
|
+
- **已有通道策略**:`singleChannel: true` 只约束 `beginAddChannel`,已有通道时等价于 `existingChannel: 'block'`;显式 `existingChannel` 支持 `block` / `newGroup` / `joinGroup` / `auto`,`joinGroup` 用 `groupId` / `groupIndex`(从 0 起、`groupId` 优先)指定分组。`beginAddLine` 不读 `singleChannel`,可显式把新回线挂进已有通道;没有通道时始终落成独立线路。plan 与草稿载荷补 `singleChannel` / `existingChannel` / `targetGroupId` / `targetGroupIndex`。
|
|
564
|
+
- **失败语义**:被策略阻断的草稿停在 `ready`(`blocked: true`、`preferred: null`);无参或 `'auto'` 提交直接返回失败并发 `phase: 'blocked'`,不会自动改道偷偷新建。显式传可用的 `resolution` 仍可改道。失败结果补齐 `kind` / `circuits` / `plan` 等字段。
|
|
565
|
+
- **解除合并**:`splitPoint()` 新增 `autoLines: 'alternate'`:没显式分配的线路按「同一条通道的成员成组、独立线路单条」轮流给负载最少的一份,返回 `autoLines` / `autoAssignedLines` / `lineAssignments`(每份 `index` / `pointId` / `lineIds` / `lineCount`);缺省 `first` 保持旧行为。
|
|
566
|
+
- **Demo**:`ConnectDemo` 拆分线路 / 通道入口,新增预置两组已有通道、`singleChannel`、`existingChannel`、目标分组控件与 blocked 读数;`CopyMergeDemo` 把拆分节点改成「解除合并」,用 `autoLines: 'alternate'` 并显示自动分线数、每份回线数与通道成组结果。
|
|
567
|
+
- **计数与文档**:公开实例成员变为 **267**(新增 `beginAddLine`,即 **262** 个方法 + 5 个属性),数据字段 250、options 96、事件 43、功能开关 17 未变。两份 README、manual 07/10/16/17/18、from0/07、GisView 索引、steps/README、selftest-docs 与生成的 `docs/demos/api-index.js` 同步到新口径。
|
|
568
|
+
- **核心文件**:`src/gis-view/engine.js`、`create.js`、`types.d.ts`、`docs/demos/ConnectDemo.vue`、`docs/demos/CopyMergeDemo.vue`、`docs/steps/selftest-features.html`、`docs/steps/selftest-demos.html`、`docs/steps/selftest-docs.html`、`README.md`、`src/gis-view/README.md`、`docs/GisView.md`、`docs/manual/07-指针与要素编辑.md`、`docs/manual/10-通道编辑与新增.md`、`docs/manual/16-实例方法总表.md`、`docs/manual/17-参数速查.md`、`docs/manual/18-逐方法示例.md`、`docs/from0/07-通道分组与多回线.md`、`docs/steps/README.md`、`docs/demos/api-index.js`(生成物)与本轮 aimd。
|
|
569
|
+
- **验证**:`node --check`(engine / create)、`npx tsc --noEmit`、`npm run build`、`node docs/steps/gen-api-index.mjs --check`、`npm run check:docs`(267 / 250 / 96 / 43 / 17)与 `git diff --check` 全部通过。定向自测 `selftest-features.html` **683** 条、`selftest-demos.html` **373** 条、`selftest-docs.html` **134** 条全部通过;全量 14 页 **2355** 条通过 / **3** 条失败,其余 12 页全过。失败为 `selftest-perf.html` 既有的标签定向重排读数 1 条,以及 `selftest-scale.html` 的筛选重算计数 2 条(单页复跑仍复现,均不涉及本轮修改链路)。复查入口见 [06 的增量记录](ai/06-会话索引与复查方法.md#2026-10-27-增量问题txt新增线路拆分单一通道策略与解除合并分线)。
|
|
570
|
+
|
|
571
|
+
## 2026-10-27 增量(「修改.txt」:新增默认选中参数化、删除旧兼容与 addLine 增量)
|
|
572
|
+
|
|
573
|
+
本轮仍是现有会话的继续增量,没有新建会话归档,因此不改上表的 39 条会话统计。需求来源是仓库根目录 `修改.txt` 三条(新增通道 / 节点 / 线路是否默认选中改为参数决定;取消所有方法的旧版兼容写法;继续优化性能)与 `问题.txt`(修改 gis-view 组件)。
|
|
574
|
+
|
|
575
|
+
- **默认选中参数化**:`addChannel` / `addChannelGroupLines` / `joinChannelGroupLines` / `addChannelGroups` / `addChannels` / `addLine` / `addPoint` / `addPoints` / `addLines` 缺省都不改变选中集,只有 `options.select: true` 才选中落地的新要素;草稿的 `select` 只控制点中的节点,`commitAddChannel(..., { select: true })` 单独控制新回线。
|
|
576
|
+
- **统一新签名**:第二个线路数据参数统一为 `lineData`(裸字符串 / 数字 id 等价于 `{ id }`),草稿配置 / 载荷同名;`addChannel` 只新建通道,已有通道改走 `addChannelGroupLines` / `joinChannelGroupLines`;`beginAddChannel` / `beginAddLine` 固定各自入口语义;`commitAddChannel` 只接受一个容器对象。
|
|
577
|
+
- **旧兼容删除**:旧 `circuits` / `kind` / `resolution` / `channelId` / `groupId` / `lineItems` 等入参命中即明确失败且不写数据;`focusOn` / `flyTo` 的 `center`、`selectInBox` 旧 Event 重载与 `GisViewChannelAddInput` / `GisViewChannelLineItems` 类型已删除。
|
|
578
|
+
- **性能与修复**:已有通道声明时 `addLine` 仍走追加式补线,无筛选的 `addItems()` 不再空跑筛选;批量 `addChannelGroups` 改为本地分组表落地,修复两组连续 `defer` 时后一组覆盖前一组;修复 `autoCommit` / 无参提交遗漏草稿 `lineData`。`selftest-perf` 新增 `addLine` 增量正确性与代价断言。
|
|
579
|
+
- **计数与文档**:公开实例成员保持 **267**(**262** 方法 + **5** 属性),数据字段 **250**、options **96**、事件 **43**、功能开关 **17**;两份 README、manual 02/06/10/14/16/17/18/19/21、from0/07 与生成的 `docs/demos/api-index.js` 同步。
|
|
580
|
+
- **核心文件**:`src/gis-view/create.js`、`src/gis-view/engine.js`、`src/gis-view/types.d.ts`、`docs/steps/selftest-features.html`、`docs/steps/selftest-demos.html`、`docs/steps/selftest-perf.html`、两份 README、`docs/manual/**`、`docs/from0/07-通道分组与多回线.md`、`docs/demos/api-index.js`(生成物)与本轮 aimd。
|
|
581
|
+
- **验证**:`node --check`(create / engine)、`npx tsc --noEmit`、`npm run build`、`node docs/steps/gen-api-index.mjs --check`、`npm run check:docs` 全部通过。全量 14 页自测 **2379** 条通过 / **2** 条失败(`selftest-features` 704、`selftest-demos` 373、`selftest-perf` 185 均 0 失败;仅 `selftest-scale` 既有 `onFilter 0 条` 2 条失败)。复查入口见 [06 的增量记录](ai/06-会话索引与复查方法.md#2026-10-27-增量修改txt新增默认选中参数化删除旧兼容与-addline-增量)。
|
package/docs/GisView.md
CHANGED
|
@@ -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) |
|
|
35
|
+
| [17 参数速查(逐个方法)](manual/17-参数速查.md) | 267 个成员逐个列全:参数、默认值与返回 |
|
|
36
36
|
| [18 逐方法示例(复制就能跑)](manual/18-逐方法示例.md) | 每个成员一段可复制的调用示例(API 速查页读的就是这一章) |
|
|
37
37
|
| [19 读数与拖拽预览](manual/19-读数与拖拽预览.md) | 自建界面常用的读数 / 定位方法;拖拽落点预览 |
|
|
38
38
|
| [20 性能与大数据](manual/20-性能与大数据.md) | 十万级数据的开关与实测、WebGPU 取舍、渲染后端开关 |
|
|
@@ -510,6 +510,54 @@
|
|
|
510
510
|
- **核心文件**:`src/gis-view/engine.js`、`src/gis-view/create.js`、`src/gis-view/types.d.ts`、`docs/steps/selftest-select.html`、`docs/steps/README.md`、`docs/manual/04-事件与回调.md`、`07-指针与要素编辑.md`、`09-通道与多回线.md`、`20-性能与大数据.md`、`src/gis-view/README.md` 与本轮 aimd。
|
|
511
511
|
- **验证**:`node --check src/gis-view/engine.js`、`node --check src/gis-view/create.js`、`npx tsc --noEmit`、`npm run build` 通过;五页定向自测 **1438** 条通过 / 0 失败(select 84 / features 672 / docs 134 / demos 365 / perf 183);`node docs/steps/gen-api-index.mjs --check` PASS(索引最新)、`npm run check:docs` 全 PASS(265 个实例成员 / 250 个数据字段 / 96 个 options / 43 个事件 / 17 个功能开关)、`git diff --check` 通过(仅有 CRLF 提示)。详细口径见 [AI 对话汇总的本轮增量](../AI对话汇总.md#2026-10-26-增量修改txt选中载荷去重与通道索引缓存)。
|
|
512
512
|
|
|
513
|
+
### 2026-10-26 增量(「修改.txt」:选中 items 去重与选中通道缓存)
|
|
514
|
+
|
|
515
|
+
本轮仍是现有会话的继续增量,没有独立会话归档,因此不改上表的 39 条统计。需求原话来自仓库根目录最新的 `修改.txt`:「修改gis-view组件 1、onSelect和onSelectChange 参数最外层有通道信息,items里面的线路对象也有通道信息,是不是重复了,删减会不会提高性能 2、继续检查有没有类似重复参数会影响性能」。
|
|
516
|
+
|
|
517
|
+
- **载荷**:`items[]` 里的线路不再挂任何通道字段。单通道只在顶层给 `channelId` / `channelIndex` / `channel` / `groupId` / `groupIndex` / `group` / `groups` 与本次选中的 `lineIndexes` / `lineIds`;多通道只在 `channelContexts[]` 每项给同一套字段。`groups[].lineIds` 是整组成员,外层 `lineIndexes` / `lineIds` 是本次选中子集。线路归属看外层或 `getLineChannel(index)`。
|
|
518
|
+
- **实现**:`describe()` / `describeAll()` 增加 `{ withChannel: false }`;`describeSelection()` 与点击选中走它,`selectionPickOf()` 剥掉单击拾取结果的通道字段;`selectionChannelContexts()` 始终维护 `lineIndexes` / `lineIds`。组件侧 `syncSelections()` 从外层上下文建立“线路下标 → 通道”缓存,`selectionInfo` / 复制 / `getSelectionData()` 直接查表,数组换新时按引用失效,其他路径用引擎 O(1) `getLineChannel()` 补建;`refreshChannelLineReadouts()` 对选中项只刷新坐标读数。指针 / 悬停载荷改为先展开拾取结果再补 `action`,去掉重复赋值。
|
|
519
|
+
- **行为变化**:`getSelections()`、`getSelectionData().items` 的线路同样不带通道字段;事件外层与 `getLineChannel()` 是新的统一归属口径。`group === groups[groupIndex]`、`action.data === items` 都是共享引用,不复制内存,故保留。
|
|
520
|
+
- **核心文件**:`src/gis-view/engine.js`、`src/gis-view/create.js`、`src/gis-view/types.d.ts`、`docs/steps/selftest-select.html`、`docs/steps/selftest-features.html`、`docs/steps/README.md`、`docs/manual/04-事件与回调.md`、`07-指针与要素编辑.md`、`08-选中与拐点.md`、`09-通道与多回线.md`、`16-实例方法总表.md`、`17-参数速查.md`、`20-性能与大数据.md`、`src/gis-view/README.md`、`docs/demos/api-index.js`(生成物)与本轮 aimd。
|
|
521
|
+
- **验证**:`node --check`(engine / create)、`npx tsc --noEmit`、`npm run build` 通过;`selftest-select.html` **85** 条、`selftest-features.html` **672** 条、`selftest-docs.html` **134** 条、`selftest-demos.html` **365** 条全部通过;`selftest-perf.html` **182 / 1**(既有标签定向重排读数断言,与选中链路无关)。`node docs/steps/gen-api-index.mjs --check`、`npm run check:docs`(265 个实例成员 / 250 个数据字段 / 96 个 options / 43 个事件 / 17 个功能开关)通过;`git diff --check` 仅有 CRLF 提示。详细口径见 [AI 对话汇总的本轮增量](../AI对话汇总.md#2026-10-26-增量修改txt选中-items-去重与选中通道缓存)。
|
|
522
|
+
|
|
523
|
+
### 2026-10-27 增量(「修改.txt」:事件载荷去重与指针回调收口)
|
|
524
|
+
|
|
525
|
+
本轮仍是现有会话的继续增量,没有新建会话归档,因此不改上表的 39 条会话统计。需求来源是仓库根目录 `修改.txt`(当前落盘内容已损坏为问号,按本轮实际落实的 gis-view 事件载荷去重与性能优化记录)。
|
|
526
|
+
|
|
527
|
+
- **单参数回调**:`onPointerDown` / `onPointerUp` / `onContextMenu` / `onClusterClick` 的实例回调、事件总线与内部监听统一只给一个 `payload`;原始 DOM 事件、鼠标键、修饰键和浏览器坐标都读 `payload.originalEvent`,不再另传第二个 `event`。
|
|
528
|
+
- **载荷去重**:删除 `payload.selection`、`action.data`、`action.button` / `action.modifiers` / `action.left` / `action.right`,指针载荷也不再复制 `button` / `buttons` / `clientX` / `clientY`。命中数据直接放载荷顶层:`item` / `channel` / `channelContext` / `items` / `lineIndex` / `pathIndex` / `coordinate` / `line` / `label` / `parent` 与聚合字段;`selected` / `wasSelected` 保留为事件时刻的状态快照。
|
|
529
|
+
- **替代入口**:选中集总读数改调新增的 `getSelectionInfo()`(按选中集引用缓存 `{ ids, count, points, lines, channels }`);右键线路读 `payload.index` / `payload.item.id`,再用 `getLineChannel(index)` 查通道;标签右键 / 双击的光标位置读 `payload.originalEvent`;复制改读 `payload.item` / `item.x` / `item.y` / `items[0]`,回写用 `payload.update()`。
|
|
530
|
+
- **性能**:`withPointerCoordinates()` 对引擎刚交出的载荷就地补 `lngLat`,高频指针 / 悬停路径不再逐次展开一份对象;同时去掉重复包装和已可推导的派生字段,减少载荷构造与遍历成本。
|
|
531
|
+
- **计数与文档**:公开实例成员变为 **266**(新增 `getSelectionInfo`),数据字段 250、options 96、事件 43、功能开关 17 未变。两份 README、manual 04/07/10/12/16/17/18/20、format/from0 教程、Event/Label/Basic/GisView demos、第 5/7/8/10/12 步与 7 个自测页都同步到新口径;第 5 步教学实现也改为单参数回调,第 7 步草稿载荷不再重复挂 `clientX` / `clientY`;README 与 steps/README 的当前计数同步到 266 / 261。
|
|
532
|
+
- **核心文件**:`src/gis-view/engine.js`、`create.js`、`overlay.js`、`events.js`、`types.d.ts`、`README.md`、`src/gis-view/README.md`、`docs/manual/04-事件与回调.md`、`07-指针与要素编辑.md`、`10-通道编辑与新增.md`、`12-标签与装饰.md`、`16-实例方法总表.md`、`17-参数速查.md`、`18-逐方法示例.md`、`20-性能与大数据.md`、`docs/format/07-事件载荷与示例数据.md`、`docs/from0/05-相机与交互.md`、`07-通道分组与多回线.md`、`docs/demos/**`、`docs/steps/**` 与本轮 aimd。
|
|
533
|
+
- **验证**:`node --check`(engine / create / overlay / events / step-05-pick / step-07-channels / step-12-events)、`npx tsc --noEmit`、`npm run build`、`git diff --check` 通过;`node docs/steps/gen-api-index.mjs --check` PASS,`npm run check:docs` PASS(266 / 250 / 96 / 43 / 17);7 页定向自测 **1611** 条通过 / 0 失败(actions 96 / labels 165 / features 672 / select 85 / full 94 / docs 134 / demos 365)。。
|
|
534
|
+
|
|
535
|
+
详细需求、替代入口与验证口径见 [AI 对话汇总的本轮增量](../AI对话汇总.md#2026-10-27-增量修改txt事件载荷去重与指针回调收口)。
|
|
536
|
+
|
|
537
|
+
### 2026-10-27 增量(「问题.txt」:新增线路拆分、单一通道策略与解除合并分线)
|
|
538
|
+
|
|
539
|
+
本轮仍是现有会话的继续增量,没有新建会话归档,因此不改上表的 39 条会话统计。需求来源是仓库根目录 `问题.txt` 的五条:把 `beginAddChannel` 拆成更规范的通道 / 线路入口;支持单一通道模式、已有通道失败事件与转为新增回线;已有通道时新回线可建新分组或并进指定分组;在 demo 中体现;优化解除合并并在 demo 中体现。
|
|
540
|
+
|
|
541
|
+
- **入口拆分**:新增公开方法 `beginAddLine()`,与 `beginAddChannel()` 共用 `beginExtendedAddDraft()`;前者固定线路语义,后者保留新建通道语义,旧写法 `beginAddChannel({ kind: 'line' })` 继续兼容。`beginAddLine` 在已有通道里加回线时,草稿 `kind` 仍是 `line`,真正落地成通道成员后结果 / `commit` / `blocked` 载荷的 `kind` 为 `channel`。
|
|
542
|
+
- **已有通道策略**:`singleChannel: true` 只约束 `beginAddChannel`,已有通道时等价于 `existingChannel: 'block'`;显式 `existingChannel` 支持 `block` / `newGroup` / `joinGroup` / `auto`,`joinGroup` 用 `groupId` / `groupIndex`(从 0 起、`groupId` 优先)指定分组。`beginAddLine` 不读 `singleChannel`,但可显式把新回线挂进已有通道;没有通道时始终落成独立线路。plan 与草稿载荷补 `singleChannel` / `existingChannel` / `targetGroupId` / `targetGroupIndex`。
|
|
543
|
+
- **失败语义**:被策略阻断的草稿停在 `ready`(`blocked: true`、`preferred: null`);无参或 `'auto'` 提交直接返回失败并发 `phase: 'blocked'`,不会自动改道偷偷新建。显式传可用的 `resolution` 仍可改道。失败结果补齐 `kind` / `circuits` / `plan` 等字段。
|
|
544
|
+
- **解除合并**:`splitPoint()` 新增 `autoLines: 'alternate'`:没显式分配的线路按「同一条通道的成员成组、独立线路单条」轮流给负载最少的一份,返回 `autoLines` / `autoAssignedLines` / `lineAssignments`(每份 `index` / `pointId` / `lineIds` / `lineCount`);缺省 `first` 保持旧行为。
|
|
545
|
+
- **Demo 与文档**:`ConnectDemo` 拆分线路 / 通道入口,新增预置两组已有通道、`singleChannel`、`existingChannel`、目标分组控件与 blocked 读数;`CopyMergeDemo` 把拆分节点改成「解除合并」,用 `autoLines: 'alternate'` 并显示自动分线数、每份回线数与通道成组结果。两份 README、manual 07/10/16/17/18、from0/07、GisView 索引、steps/README、selftest-docs 与生成的 `docs/demos/api-index.js` 同步。
|
|
546
|
+
- **计数**:公开实例成员 **267**(**262** 个方法 + 5 个属性),数据字段 250、options 96、事件 43、功能开关 17 未变。
|
|
547
|
+
- **核心文件**:`src/gis-view/engine.js`、`src/gis-view/create.js`、`src/gis-view/types.d.ts`、`docs/demos/ConnectDemo.vue`、`docs/demos/CopyMergeDemo.vue`、`docs/steps/selftest-features.html`、`docs/steps/selftest-demos.html`、`docs/steps/selftest-docs.html`、`README.md`、`src/gis-view/README.md`、`docs/GisView.md`、`docs/manual/07-指针与要素编辑.md`、`docs/manual/10-通道编辑与新增.md`、`docs/manual/16-实例方法总表.md`、`docs/manual/17-参数速查.md`、`docs/manual/18-逐方法示例.md`、`docs/from0/07-通道分组与多回线.md`、`docs/steps/README.md`、`docs/demos/api-index.js`(生成物)与本轮 aimd。
|
|
548
|
+
- **验证**:`node --check`(engine / create)、`npx tsc --noEmit`、`npm run build`、`node docs/steps/gen-api-index.mjs --check`、`npm run check:docs`(267 / 250 / 96 / 43 / 17)与 `git diff --check` 全部通过。定向自测 `selftest-features.html` **683** 条、`selftest-demos.html` **373** 条、`selftest-docs.html` **134** 条全部通过;全量 14 页 **2355** 条通过 / **3** 条失败,其余 12 页全过。失败为 `selftest-perf.html` 既有的标签定向重排读数 1 条,以及 `selftest-scale.html` 的筛选重算计数 2 条(单页复跑仍复现,均不涉及本轮修改链路)。
|
|
549
|
+
|
|
550
|
+
### 2026-10-27 增量(「修改.txt」:新增默认选中参数化、删除旧兼容与 addLine 增量)
|
|
551
|
+
|
|
552
|
+
本轮仍是现有会话的继续增量,没有新建会话归档,因此不改上表的 39 条统计。需求来源是仓库根目录 `修改.txt` 三条(新增通道 / 节点 / 线路是否默认选中改为参数决定;取消所有方法的旧版兼容写法;继续优化性能)与 `问题.txt`(修改 gis-view 组件)。
|
|
553
|
+
|
|
554
|
+
- **默认选中参数化**:`addChannel` / `addChannelGroupLines` / `joinChannelGroupLines` / `addChannelGroups` / `addChannels` / `addLine` / `addPoint` / `addPoints` / `addLines` 缺省都不改变选中集,只有 `options.select: true` 才选中落地的新要素。草稿的 `select` 仍只控制点中的节点,不等于“落地后选中”;`commitAddChannel` 的 `select` 也显式控制新回线。
|
|
555
|
+
- **统一新签名**:程序化新增的第二段统一为 `lineData`,裸字符串 / 数字 id 等价于 `{ id }`;草稿配置与载荷也改名 `lineData`。`addChannel(spec, lineData?, group?, options?)` 只负责新建通道,两端已有通道时明确失败;并组 / 新组分别走 `joinChannelGroupLines` / `addChannelGroupLines`。`beginAddChannel` 固定通道语义、`beginAddLine` 固定线路语义;`commitAddChannel` 只接受一个容器对象(`channel` / `line` / `lines` / `lineData` / `channelGroups` / `group` 等)。
|
|
556
|
+
- **旧兼容删除**:上述方法不再接受 `circuits` / `kind` / `resolution` / `channelId` / `groupId` / `lineItems` 等旧分派字段,命中时明确失败且不写数据;`focusOn` / `flyTo` 删除 `center: true`,`selectInBox` 删除旧 Event 重载,类型删除 `GisViewChannelAddInput` / `GisViewChannelLineItems`。旧 `lineItems` 在草稿入口只告警忽略,不再参与落地。
|
|
557
|
+
- **性能与回归修复**:`engine.canAppendLines()` 不再因存在通道声明直接拒绝,追加末尾独立线路时仍可走增量解析;无筛选时 `addItems()` 不再空跑 `applyFilter()`。批量 `addChannelGroups` 的 `defer` 路径改为从本地 `channels` 重写分组表,避免第二组用引擎旧快照覆盖第一组;同时修正草稿无参 / `autoCommit` 提交时 `lineData` 未转入落地参数的回归。`selftest-perf.html` 增加有通道声明时 `addLine` 的场景一致性、通道声明不变与“最快一次 × 5 < 整份重排”断言。
|
|
558
|
+
- **核心文件**:`src/gis-view/create.js`、`src/gis-view/engine.js`、`src/gis-view/types.d.ts`、`docs/steps/selftest-features.html`、`docs/steps/selftest-demos.html`、`docs/steps/selftest-perf.html`、`README.md`、`src/gis-view/README.md`、`docs/manual/02/06/10/14/16/17/18/19/21`、`docs/from0/07-通道分组与多回线.md`、`docs/demos/api-index.js`(生成物)与本轮 aimd。
|
|
559
|
+
- **验证**:`node --check`(create / engine)、`npx tsc --noEmit`、`npm run build`、`node docs/steps/gen-api-index.mjs --check`、`npm run check:docs` 全部通过;公开计数保持 **267**(**262** 方法 + **5** 属性)、数据字段 **250**、options **96**、事件 **43**、功能开关 **17**。全量 14 页自测 **2379** 条通过 / **2** 条失败;失败仅为 `selftest-scale.html` 既有的筛选重算计数 2 条(`onFilter 0 条`,单独复现,不涉及本轮新增链路)。`selftest-features` 704、`selftest-demos` 373、`selftest-perf` 185 均全过。
|
|
560
|
+
|
|
513
561
|
## 六、怎么自己复查
|
|
514
562
|
|
|
515
563
|
会话文件是逐行 JSON,不用整份读 —— 只挑两种行解析就够:`session_meta` 行里的 `payload.cwd` 认项目,`payload.type === 'user_message'` 行里的 `payload.message` 是用户原话:
|
package/docs/demos/BasicDemo.vue
CHANGED
|
@@ -25,14 +25,14 @@ let statsDrawnAt = Number.NEGATIVE_INFINITY
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* 事件载荷压成一行:`action.target` 说清点到了哪一类(point / line / channel / empty),
|
|
28
|
-
*
|
|
28
|
+
* 命中的数据在载荷顶层(点 / 线读 item,通道读 channel,标签读 label)—— 指针事件的载荷结构见 docs/数据格式.md。
|
|
29
29
|
*/
|
|
30
30
|
const describeTarget = (payload) => {
|
|
31
31
|
if (!payload) {
|
|
32
32
|
return '空白(清除选中)'
|
|
33
33
|
}
|
|
34
34
|
const target = payload.action?.target ?? payload.kind ?? payload.type ?? '—'
|
|
35
|
-
const data = payload.
|
|
35
|
+
const data = payload.item ?? payload.channel ?? payload.label ?? payload.line ?? null
|
|
36
36
|
const id = Array.isArray(data)
|
|
37
37
|
? `${data.length} 项`
|
|
38
38
|
: data?.id ?? data?.name ?? payload.id ?? payload.index ?? '—'
|
|
@@ -119,21 +119,21 @@ const addChannelBatch = () => runAction('addChannels', () => {
|
|
|
119
119
|
name: `批量通道 ${currentRound}-A`,
|
|
120
120
|
from: picked[0].id,
|
|
121
121
|
to: picked[2].id,
|
|
122
|
-
circuits: 2,
|
|
123
122
|
path: [[Number((middle[0] + normal[0]).toFixed(5)), Number((middle[1] + normal[1]).toFixed(5))]],
|
|
124
|
-
|
|
123
|
+
lineData: [
|
|
124
|
+
{ vn_kv: 525, max_loading_rate: 0.82 },
|
|
125
|
+
{ vn_kv: 525, max_loading_rate: 0.82 }
|
|
126
|
+
]
|
|
125
127
|
},
|
|
126
128
|
{
|
|
127
129
|
id: `batch-channel-${currentRound}-b`,
|
|
128
130
|
name: `批量通道 ${currentRound}-B`,
|
|
129
131
|
from: picked[1].id,
|
|
130
132
|
to: picked[3].id,
|
|
131
|
-
circuits: 1,
|
|
132
133
|
path: [[Number((middle[0] - normal[0]).toFixed(5)), Number((middle[1] - normal[1]).toFixed(5))]],
|
|
133
|
-
|
|
134
|
+
lineData: [{ vn_kv: 230, max_loading_rate: 0.61 }]
|
|
134
135
|
}
|
|
135
|
-
])
|
|
136
|
-
view.selectFeatures(result.lineIds)
|
|
136
|
+
], { select: true })
|
|
137
137
|
return `addChannels → ${result.count} 条通道 · ${result.channelIds.join(' / ')} · ${result.lineIds.length} 条回线`
|
|
138
138
|
})
|
|
139
139
|
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* 可视化连线:只靠 points + lines 也能独立成图,再把「新增一条线」的几种常见配置摆在一起。
|
|
4
4
|
*
|
|
5
5
|
* 这一页的初始数据明确是 channels: [] —— 没有任何通道,节点与线路照样能渲染、选中与新增。
|
|
6
|
-
*
|
|
6
|
+
* 「开始连线」按类型分流:独立线路走 beginAddLine,通道走 beginAddChannel,两个节点选完先出草稿:
|
|
7
7
|
* - routing: 'orthogonal' 让预览自动补一个先横后纵的 90° 拐点;
|
|
8
|
-
* - channelDraftStyle 直接交给 beginAddChannel,草稿期间用 updateAddChannelStyle 原地改箭头位置 / 间距 / 配色;
|
|
8
|
+
* - channelDraftStyle 直接交给 beginAddLine / beginAddChannel,草稿期间用 updateAddChannelStyle 原地改箭头位置 / 间距 / 配色;
|
|
9
|
+
* - 预置一条已有通道(两组)后,可演示 singleChannel 阻断、existingChannel 新分组或并入指定分组;
|
|
10
|
+
* - beginAddLine 显式写 existingChannel 时会把新回线挂进已有通道,不写则始终是独立线路;
|
|
9
11
|
* - 取消只收草稿,数据一个字节不动;
|
|
10
12
|
* - 选好起点后点节点以外的位置也会直接取消,事件里的 reason 是 click-outside-node;
|
|
11
13
|
* - 默认添加走 commitAddChannel;
|
|
@@ -29,6 +31,10 @@ const arrowSize = ref(12)
|
|
|
29
31
|
const arrowPositionsText = ref('0.5')
|
|
30
32
|
const circuitSpacing = ref(12)
|
|
31
33
|
const startPointId = ref('')
|
|
34
|
+
const singleChannel = ref(true)
|
|
35
|
+
const existingChannelPolicy = ref('none')
|
|
36
|
+
const groupTarget = ref('')
|
|
37
|
+
const existingGroups = ref([])
|
|
32
38
|
const dotsVisible = ref(true)
|
|
33
39
|
const dotSize = ref(5)
|
|
34
40
|
const startDotColor = ref('#2f6fd0')
|
|
@@ -38,6 +44,7 @@ const draftLayer = ref('above')
|
|
|
38
44
|
const draft = ref(null)
|
|
39
45
|
const actionText = ref('配置好线条之后点「开始连线」,依次点两个节点')
|
|
40
46
|
const dataText = ref('正在创建视图…')
|
|
47
|
+
const eventText = ref('channelDraft:尚未触发')
|
|
41
48
|
const cancelText = ref('尚未取消:选好起点后点一下节点以外的空白处,应收到 reason = click-outside-node')
|
|
42
49
|
const logLines = ref([])
|
|
43
50
|
const logText = computed(() => logLines.value.length
|
|
@@ -78,6 +85,15 @@ const createInitialLines = () => {
|
|
|
78
85
|
|
|
79
86
|
const isChannel = computed(() => linkKind.value !== 'line')
|
|
80
87
|
const circuits = computed(() => linkKind.value === 'channel-3' ? 3 : linkKind.value === 'channel-2' ? 2 : 1)
|
|
88
|
+
const addMethod = computed(() => isChannel.value ? 'beginAddChannel' : 'beginAddLine')
|
|
89
|
+
const groupLineCount = group => Number(group?.lineCount ?? group?.lineIndexes?.length ?? group?.lineIds?.length ?? 0)
|
|
90
|
+
const groupOptions = computed(() => existingGroups.value.map((group, index) => ({
|
|
91
|
+
value: String(group.id),
|
|
92
|
+
label: `第 ${index + 1} 组 · ${String(group.id)} · ${groupLineCount(group)} 回`
|
|
93
|
+
})))
|
|
94
|
+
const existingText = computed(() => existingGroups.value.length
|
|
95
|
+
? `已有通道 connect-existing-channel:${existingGroups.value.map((group, index) => `第 ${index + 1} 组 ${groupLineCount(group)} 回`).join(' / ')}`
|
|
96
|
+
: '已有通道:未预置(先点上方按钮创建一条两组的通道)')
|
|
81
97
|
const arrowPositions = computed(() => arrowPositionsText.value.split(',').map(value => Number(value.trim())).filter(value => Number.isFinite(value)))
|
|
82
98
|
const startPointLabel = computed(() => {
|
|
83
99
|
const point = points.find(item => String(item.id) === String(startPointId.value))
|
|
@@ -100,7 +116,12 @@ const draftText = computed(() => {
|
|
|
100
116
|
}
|
|
101
117
|
const to = current.to.item?.name ?? current.to.id
|
|
102
118
|
const bends = current.path?.length ?? 0
|
|
103
|
-
|
|
119
|
+
const policy = current.existingChannel
|
|
120
|
+
? ` · existingChannel=${current.existingChannel}${current.targetGroupId !== null ? ` → ${String(current.targetGroupId)}` : ''}`
|
|
121
|
+
: ''
|
|
122
|
+
const single = current.singleChannel ? ' · singleChannel' : ''
|
|
123
|
+
const blocked = current.blocked ? ` · 已阻断:${current.reason ?? '未知原因'}` : ''
|
|
124
|
+
return `${kind}${single}${policy} · ${from} → ${to} · ${current.routing === 'orthogonal' ? '正交折线' : '直线'} · 中间拐点 ${bends} · 箭头 ${current.style?.arrow ? current.style.arrowPositions.map(value => `${Math.round(value * 100)}%`).join('/') : '关'} · 回线间距 ${current.style?.circuitSpacing ?? 0}px${blocked}`
|
|
104
125
|
})
|
|
105
126
|
|
|
106
127
|
const pushLog = (name, text) => {
|
|
@@ -143,24 +164,91 @@ const currentLineProps = () => ({
|
|
|
143
164
|
direction: arrow.value ? { count: 1, size: arrowSize.value } : false
|
|
144
165
|
})
|
|
145
166
|
|
|
167
|
+
/** 读取预置通道的分组,供 joinGroup 下拉直接选择;预置前后 / 草稿 ready 时都刷新一次 */
|
|
168
|
+
const refreshExistingGroups = () => {
|
|
169
|
+
if (!view) {
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
const between = view.getChannelBetween('node-0', 'node-1')
|
|
173
|
+
existingGroups.value = between?.hasChannel ? [...(between.groups ?? [])] : []
|
|
174
|
+
if (!existingGroups.value.some(group => String(group.id) === String(groupTarget.value))) {
|
|
175
|
+
groupTarget.value = existingGroups.value[0]?.id ?? ''
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** 预置一条 node-0 ↔ node-1 的通道,带两个分组,专门演示单一通道和指定分组 */
|
|
180
|
+
const seedExistingChannel = () => {
|
|
181
|
+
if (!view) {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
view.endAddChannel()
|
|
185
|
+
draft.value = null
|
|
186
|
+
const between = view.getChannelBetween('node-0', 'node-1')
|
|
187
|
+
if (between?.hasChannel) {
|
|
188
|
+
refreshExistingGroups()
|
|
189
|
+
actionText.value = `已有通道 ${String(between.channelId)} 已存在,共 ${existingGroups.value.length} 组;可以直接开始连线`
|
|
190
|
+
pushLog('seed-existing', actionText.value)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
const lineA = 'connect-existing-line-a'
|
|
194
|
+
const lineB = 'connect-existing-line-b'
|
|
195
|
+
const result = view.addChannel({
|
|
196
|
+
id: 'connect-existing-channel',
|
|
197
|
+
name: '演示已有通道(两组)',
|
|
198
|
+
from: 'node-0',
|
|
199
|
+
to: 'node-1'
|
|
200
|
+
}, [
|
|
201
|
+
{ id: lineA, name: '已有通道 · A 组回线', color: '#2f6fd0', width: 2.2 },
|
|
202
|
+
{ id: lineB, name: '已有通道 · B 组回线', color: '#d9544d', width: 2.2 }
|
|
203
|
+
], [
|
|
204
|
+
{ id: 'connect-existing-group-a', name: 'A 组', lineIds: [lineA] },
|
|
205
|
+
{ id: 'connect-existing-group-b', name: 'B 组', lineIds: [lineB] }
|
|
206
|
+
], { select: true })
|
|
207
|
+
refreshExistingGroups()
|
|
208
|
+
refreshState()
|
|
209
|
+
if (!result.ok) {
|
|
210
|
+
actionText.value = `预置已有通道失败:${result.reason ?? '未知原因'}`
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
actionText.value = `已预置已有通道 ${String(result.channelId)}:node-0 ↔ node-1,共 ${existingGroups.value.length} 组;现在可用 singleChannel / newGroup / joinGroup 演示`
|
|
214
|
+
}
|
|
215
|
+
pushLog('addChannel(预置已有通道)', actionText.value)
|
|
216
|
+
emit('notice', actionText.value)
|
|
217
|
+
}
|
|
218
|
+
|
|
146
219
|
const startDraft = () => {
|
|
147
220
|
if (!view) {
|
|
148
221
|
return
|
|
149
222
|
}
|
|
150
223
|
view.endAddChannel()
|
|
151
|
-
|
|
224
|
+
const options = {
|
|
152
225
|
...(startPointId.value ? { from: startPointId.value } : {}),
|
|
153
|
-
kind: isChannel.value ? 'channel' : 'line',
|
|
154
226
|
circuits: circuits.value,
|
|
155
227
|
routing: routing.value,
|
|
156
228
|
select: true,
|
|
157
229
|
channelDraftStyle: currentDraftStyle(),
|
|
158
230
|
lineProps: currentLineProps()
|
|
159
|
-
}
|
|
231
|
+
}
|
|
232
|
+
if (existingChannelPolicy.value !== 'none') {
|
|
233
|
+
options.existingChannel = existingChannelPolicy.value
|
|
234
|
+
}
|
|
235
|
+
if (existingChannelPolicy.value === 'joinGroup' && groupTarget.value !== '') {
|
|
236
|
+
options.groupId = groupTarget.value
|
|
237
|
+
}
|
|
238
|
+
if (isChannel.value) {
|
|
239
|
+
// beginAddChannel 的单一通道模式:已有通道时按 existingChannel 处理,不写时默认 block
|
|
240
|
+
options.singleChannel = singleChannel.value
|
|
241
|
+
view.beginAddChannel(options)
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
// beginAddLine 不认 singleChannel;要挂进已有通道必须显式写 existingChannel
|
|
245
|
+
view.beginAddLine(options)
|
|
246
|
+
}
|
|
247
|
+
const modeText = existingChannelPolicy.value === 'none' ? '未指定已有通道策略' : `existingChannel=${existingChannelPolicy.value}`
|
|
160
248
|
actionText.value = startPointId.value
|
|
161
|
-
?
|
|
162
|
-
:
|
|
163
|
-
pushLog(
|
|
249
|
+
? `${addMethod.value}:起点已指定为 ${startPointLabel.value},下一次点节点只选终点(${isChannel.value ? `通道 ${circuits.value} 回` : '独立线路'} / ${modeText})`
|
|
250
|
+
: `${addMethod.value}:先点起点,再点终点(${isChannel.value ? `通道 ${circuits.value} 回` : '独立线路'} / ${modeText})`
|
|
251
|
+
pushLog(addMethod.value, actionText.value)
|
|
164
252
|
}
|
|
165
253
|
|
|
166
254
|
const cancelDraft = () => {
|
|
@@ -184,17 +272,15 @@ const commitDraft = () => {
|
|
|
184
272
|
if (!view || !canFinish.value) {
|
|
185
273
|
return
|
|
186
274
|
}
|
|
187
|
-
const result = view.commitAddChannel()
|
|
275
|
+
const result = view.commitAddChannel(undefined, { select: true })
|
|
188
276
|
refreshState()
|
|
189
277
|
if (!result.ok) {
|
|
190
|
-
actionText.value = `commitAddChannel
|
|
278
|
+
actionText.value = `commitAddChannel → blocked:${result.reason ?? '未知原因'}`
|
|
191
279
|
}
|
|
192
280
|
else {
|
|
193
281
|
const lines = result.lines?.length ?? (result.line ? 1 : 0)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
view.selectFeatures(result.lineIds)
|
|
197
|
-
}
|
|
282
|
+
const group = result.groupId !== undefined && result.groupId !== null ? ` · 分组 ${String(result.groupId)}` : ''
|
|
283
|
+
actionText.value = `commitAddChannel → ${result.kind === 'line' ? '独立线路' : '通道'} ${String(result.channelId ?? result.lineId ?? '')} · ${lines} 回线${group}`
|
|
198
284
|
}
|
|
199
285
|
pushLog('commitAddChannel', actionText.value)
|
|
200
286
|
emit('notice', actionText.value)
|
|
@@ -238,7 +324,7 @@ const commitWithObject = () => {
|
|
|
238
324
|
groups: [{ id: channelId, lineIds, ...(middle ? { path: middle } : {}) }]
|
|
239
325
|
}
|
|
240
326
|
}
|
|
241
|
-
const result = view.commitAddChannel(payload)
|
|
327
|
+
const result = view.commitAddChannel(current.kind === 'line' ? { line: payload } : { channel: payload }, { select: true })
|
|
242
328
|
refreshState()
|
|
243
329
|
if (!result.ok) {
|
|
244
330
|
actionText.value = `commitAddChannel(对象) 没有落地:${result.reason ?? '未知原因'}`
|
|
@@ -246,9 +332,6 @@ const commitWithObject = () => {
|
|
|
246
332
|
else {
|
|
247
333
|
const lines = result.lines?.length ?? (result.line ? 1 : 0)
|
|
248
334
|
actionText.value = `commitAddChannel(对象) → ${result.kind === 'line' ? '线路对象' : '通道对象'} ${String(result.channelId ?? result.lineId ?? '')} · ${lines} 回线`
|
|
249
|
-
if (result.lineIds?.length) {
|
|
250
|
-
view.selectFeatures(result.lineIds)
|
|
251
|
-
}
|
|
252
335
|
}
|
|
253
336
|
pushLog('commitAddChannel(对象)', actionText.value)
|
|
254
337
|
emit('notice', actionText.value)
|
|
@@ -303,23 +386,27 @@ const addWithChannels = () => {
|
|
|
303
386
|
sequence += 1
|
|
304
387
|
const middle = middlePathOf(current)
|
|
305
388
|
const kind = current.kind === 'line' ? 'line' : 'channel'
|
|
306
|
-
const result =
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
389
|
+
const result = kind === 'line'
|
|
390
|
+
? view.addLine({
|
|
391
|
+
id: `connect-line-${sequence}`,
|
|
392
|
+
name: `独立线路 ${sequence}`,
|
|
393
|
+
from: current.from.id,
|
|
394
|
+
to: current.to.id,
|
|
395
|
+
...(middle ? { path: middle } : {}),
|
|
396
|
+
lineProps: currentLineProps()
|
|
397
|
+
}, undefined, { select: true })
|
|
398
|
+
: view.addChannels([{
|
|
399
|
+
id: `connect-channel-${sequence}`,
|
|
400
|
+
name: `可视化通道 ${sequence}`,
|
|
401
|
+
from: current.from.id,
|
|
402
|
+
to: current.to.id,
|
|
403
|
+
...(middle ? { path: middle } : {}),
|
|
404
|
+
lineData: Array.from({ length: Math.max(Number(current.circuits) || 1, 1) }, () => ({ ...currentLineProps() }))
|
|
405
|
+
}], { select: true })
|
|
316
406
|
refreshState()
|
|
317
407
|
if (result.ok) {
|
|
318
408
|
view.endAddChannel()
|
|
319
409
|
draft.value = null
|
|
320
|
-
if (result.lineIds.length) {
|
|
321
|
-
view.selectFeatures(result.lineIds)
|
|
322
|
-
}
|
|
323
410
|
actionText.value = `addChannels → ${result.count} 条 · ${result.lineIds.length} 回线${result.channelIds.length ? ` · 通道 ${result.channelIds.join(' / ')}` : ' · 未新建通道'}${middle ? ` · path ${middle.length} 拐点` : ''}`
|
|
324
411
|
}
|
|
325
412
|
else {
|
|
@@ -338,6 +425,11 @@ const resetScene = () => {
|
|
|
338
425
|
view.clearHistory()
|
|
339
426
|
view.fitView(56)
|
|
340
427
|
draft.value = null
|
|
428
|
+
existingGroups.value = []
|
|
429
|
+
groupTarget.value = ''
|
|
430
|
+
existingChannelPolicy.value = 'none'
|
|
431
|
+
singleChannel.value = true
|
|
432
|
+
eventText.value = 'channelDraft:尚未触发'
|
|
341
433
|
sequence = 0
|
|
342
434
|
actionText.value = '已恢复为 channels: [] 的纯点 + 线场景'
|
|
343
435
|
refreshState()
|
|
@@ -366,6 +458,15 @@ onMounted(() => {
|
|
|
366
458
|
},
|
|
367
459
|
onChannelDraft: payload => {
|
|
368
460
|
draft.value = payload
|
|
461
|
+
const target = payload.targetGroupId !== undefined && payload.targetGroupId !== null ? ` · targetGroup=${String(payload.targetGroupId)}` : ''
|
|
462
|
+
eventText.value = `channelDraft:phase=${payload.phase} · kind=${payload.kind ?? '—'} · blocked=${payload.blocked === true} · singleChannel=${payload.singleChannel === true} · existingChannel=${payload.existingChannel ?? 'null'}${target}${payload.reason ? ` · reason=${payload.reason}` : ''}`
|
|
463
|
+
if (payload.phase === 'ready' && payload.hasChannel) {
|
|
464
|
+
refreshExistingGroups()
|
|
465
|
+
}
|
|
466
|
+
if (payload.phase === 'blocked') {
|
|
467
|
+
actionText.value = `失败事件(phase=blocked):${payload.reason ?? '当前草稿不允许新增'}`
|
|
468
|
+
emit('notice', actionText.value)
|
|
469
|
+
}
|
|
369
470
|
if (payload.phase === 'cancel') {
|
|
370
471
|
cancelText.value = `取消:phase = ${payload.phase} · reason = ${payload.reason ?? '未提供'} · 起点 ${payload.from?.item?.name ?? payload.from?.id ?? '—'}`
|
|
371
472
|
actionText.value = payload.reason === 'click-outside-node'
|
|
@@ -438,7 +539,36 @@ onBeforeUnmount(() => {
|
|
|
438
539
|
</label>
|
|
439
540
|
<label>回线间距 <input v-model.number="circuitSpacing" type="range" min="6" max="28" step="1" data-demo="connect-spacing" /> {{ circuitSpacing }}px</label>
|
|
440
541
|
</div>
|
|
441
|
-
<p class="dv-hint">草稿样式直接传给 <code>
|
|
542
|
+
<p class="dv-hint">草稿样式直接传给 <code>beginAddLine</code> / <code>beginAddChannel</code>;连线期间改颜色 / 线宽 / 箭头位置 / 回线间距会走 <code>updateAddChannelStyle()</code>,预览立即更新。正交折线会自动补一个 90 度拐点;落地线路用完整 path,通道只写中间拐点。</p>
|
|
543
|
+
</div>
|
|
544
|
+
|
|
545
|
+
<div class="dv-group">
|
|
546
|
+
<h3>单一通道与已有分组</h3>
|
|
547
|
+
<div class="dv-row">
|
|
548
|
+
<button type="button" class="dv-button" data-demo="connect-seed-existing" :disabled="isDraftLive" @click="seedExistingChannel">预置已有通道(两组)</button>
|
|
549
|
+
</div>
|
|
550
|
+
<div class="dv-row">
|
|
551
|
+
<label><input v-model="singleChannel" type="checkbox" data-demo="connect-single-channel" :disabled="!isChannel || isDraftLive" /> singleChannel</label>
|
|
552
|
+
<label>已有通道时
|
|
553
|
+
<select v-model="existingChannelPolicy" data-demo="connect-existing-mode" :disabled="isDraftLive">
|
|
554
|
+
<option value="none">不指定(线路默认独立)</option>
|
|
555
|
+
<option value="auto">auto · 新分组优先</option>
|
|
556
|
+
<option value="newGroup">newGroup · 新分组</option>
|
|
557
|
+
<option value="joinGroup">joinGroup · 指定分组</option>
|
|
558
|
+
<option value="block">block · 直接失败</option>
|
|
559
|
+
</select>
|
|
560
|
+
</label>
|
|
561
|
+
</div>
|
|
562
|
+
<div class="dv-row">
|
|
563
|
+
<label>目标分组
|
|
564
|
+
<select v-model="groupTarget" data-demo="connect-group-target" :disabled="existingChannelPolicy !== 'joinGroup' || isDraftLive">
|
|
565
|
+
<option v-if="!groupOptions.length" value="">先预置已有通道</option>
|
|
566
|
+
<option v-for="group in groupOptions" :key="group.value" :value="group.value">{{ group.label }}</option>
|
|
567
|
+
</select>
|
|
568
|
+
</label>
|
|
569
|
+
</div>
|
|
570
|
+
<p class="dv-readout" data-demo="connect-existing-readout">{{ existingText }}</p>
|
|
571
|
+
<p class="dv-hint"><code>beginAddChannel({ singleChannel: true })</code> 在两端已有通道时默认阻断;显式 <code>existingChannel</code> 可改为新分组 / 指定分组 / 自动。<code>beginAddLine</code> 不读 singleChannel,但显式写 <code>existingChannel: 'newGroup' / 'joinGroup'</code> 时,会把新回线加进已有通道。</p>
|
|
442
572
|
</div>
|
|
443
573
|
|
|
444
574
|
<div class="dv-group">
|
|
@@ -481,6 +611,7 @@ onBeforeUnmount(() => {
|
|
|
481
611
|
<p class="dv-readout" data-demo="connect-draft-readout">{{ draftText }}</p>
|
|
482
612
|
<p class="dv-readout" data-demo="connect-data-readout">{{ dataText }}</p>
|
|
483
613
|
<p class="dv-readout" data-demo="connect-action-readout">{{ actionText }}</p>
|
|
614
|
+
<p class="dv-readout" data-demo="connect-event-readout">{{ eventText }}</p>
|
|
484
615
|
<p class="dv-readout" data-demo="connect-cancel-readout">{{ cancelText }}</p>
|
|
485
616
|
</div>
|
|
486
617
|
|
|
@@ -25,7 +25,8 @@ const copyItemsText = ref('—')
|
|
|
25
25
|
const copyLineChannelsText = ref('—')
|
|
26
26
|
const manualText = ref('—')
|
|
27
27
|
const mergeBackendText = ref('合并变更 → 后端事务:—')
|
|
28
|
-
const splitText = ref('
|
|
28
|
+
const splitText = ref('解除合并:先选中一个节点(不选就取第一个),再点按钮')
|
|
29
|
+
const lastSplitResult = shallowRef(null)
|
|
29
30
|
const copyBackendText = ref('复制落库:—')
|
|
30
31
|
const mergeSyncText = ref('合并落库:—')
|
|
31
32
|
const backendMode = ref('success')
|
|
@@ -748,15 +749,11 @@ const prepareMerge = () => {
|
|
|
748
749
|
return actionText.value
|
|
749
750
|
}
|
|
750
751
|
|
|
751
|
-
/**
|
|
752
|
+
/** 解除合并:splitPoint 的 autoLines=alternate 按通道成组、独立线路单条轮流分配,不会把同通道成员拆散 */
|
|
752
753
|
const splitSelectedPoint = () => {
|
|
753
754
|
const data = view?.getData()
|
|
754
755
|
if (!data?.points?.length) return '没有可拆分的节点'
|
|
755
756
|
const source = selectedPoints.value[0] ?? data.points[0]
|
|
756
|
-
const linked = data.lines
|
|
757
|
-
.map((line, index) => ({ line, index }))
|
|
758
|
-
.filter(({ line }) => String(line.from) === String(source.id) || String(line.to) === String(source.id))
|
|
759
|
-
const movedLines = linked.filter((_entry, index) => index % 2 === 1).map(entry => entry.line.id ?? entry.index)
|
|
760
757
|
splitRound += 1
|
|
761
758
|
const offset = 0.42 + splitRound * 0.04
|
|
762
759
|
const result = view.splitPoint(source.id, [
|
|
@@ -764,23 +761,38 @@ const splitSelectedPoint = () => {
|
|
|
764
761
|
x: Number(source.x) - offset,
|
|
765
762
|
y: Number(source.y) - offset * 0.55,
|
|
766
763
|
name: `${pointLabel(source)} · A`,
|
|
767
|
-
zone_name: '
|
|
764
|
+
zone_name: '解除合并后 · A'
|
|
768
765
|
},
|
|
769
766
|
{
|
|
770
767
|
x: Number(source.x) + offset,
|
|
771
768
|
y: Number(source.y) + offset * 0.55,
|
|
772
769
|
name: `${pointLabel(source)} · B`,
|
|
773
|
-
zone_name: '
|
|
774
|
-
lines: movedLines
|
|
770
|
+
zone_name: '解除合并后 · B'
|
|
775
771
|
}
|
|
776
|
-
])
|
|
772
|
+
], { autoLines: 'alternate', label: '解除合并' })
|
|
773
|
+
lastSplitResult.value = result
|
|
777
774
|
if (!result.ok) {
|
|
778
|
-
splitText.value =
|
|
775
|
+
splitText.value = `解除合并失败:${result.reason}`
|
|
779
776
|
actionText.value = splitText.value
|
|
780
777
|
pushLog('splitPoint', splitText.value)
|
|
781
778
|
return splitText.value
|
|
782
779
|
}
|
|
783
|
-
|
|
780
|
+
const lineSlot = new Map()
|
|
781
|
+
for (const assignment of result.lineAssignments ?? []) {
|
|
782
|
+
for (const lineId of assignment.lineIds ?? []) {
|
|
783
|
+
lineSlot.set(String(lineId), assignment.index)
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const channelsKept = (data.channels ?? []).every((channel) => {
|
|
787
|
+
const slots = new Set()
|
|
788
|
+
for (const lineId of channel.lineIds ?? []) {
|
|
789
|
+
const slot = lineSlot.get(String(lineId))
|
|
790
|
+
if (slot !== undefined) slots.add(slot)
|
|
791
|
+
}
|
|
792
|
+
return slots.size <= 1
|
|
793
|
+
})
|
|
794
|
+
const counts = result.lineAssignments.map(assignment => `${assignment.index === 0 ? 'A' : assignment.index === 1 ? 'B' : String(assignment.index + 1)} ${assignment.lineCount}`).join(' / ')
|
|
795
|
+
splitText.value = `splitPoint(解除合并):${pointLabel(source)} → ${result.pointIds.join(' / ')} · autoLines=${result.autoLines} · 自动分线 ${result.autoAssignedLines}(${counts})· 改线 ${result.linesChanged} / 删线 ${result.removedLines} · 改通道 ${result.channelsChanged} / 删通道 ${result.removedChannels} · 通道成组${channelsKept ? '保持完整' : '发生拆分'}`
|
|
784
796
|
actionText.value = splitText.value
|
|
785
797
|
pushLog('splitPoint', splitText.value)
|
|
786
798
|
view.selectFeatures(result.pointIds)
|
|
@@ -1177,11 +1189,12 @@ onBeforeUnmount(() => {
|
|
|
1177
1189
|
<p class="dv-hint">mergeSelectedPoints 只并选中集;target 可以指定保留谁,item 只改最终节点的字段。拖点相撞走 pending / confirm 两站,确认前也能改写。合并落地后的 changes 可直接作为后端事务:删除来源节点、更新保留节点、更新 / 删除线路与通道。</p>
|
|
1178
1190
|
</div>
|
|
1179
1191
|
<div class="dv-group">
|
|
1180
|
-
<h3>4 ·
|
|
1192
|
+
<h3>4 · 解除合并</h3>
|
|
1181
1193
|
<div class="dv-row">
|
|
1182
|
-
<button type="button" class="dv-button dv-button--primary" data-demo="split-selected" @click="splitSelectedPoint"
|
|
1194
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="split-selected" @click="splitSelectedPoint">解除合并(拆分节点)</button>
|
|
1183
1195
|
</div>
|
|
1184
1196
|
<p class="dv-readout" data-demo="split-readout">{{ splitText }}</p>
|
|
1197
|
+
<p class="dv-hint"><code>splitPoint(source, [A, B], { autoLines: 'alternate' })</code> 把没显式分配的线路按“同一条通道的成员成组、独立线路单条”轮流分给 A / B;返回的 <code>autoAssignedLines</code> 与 <code>lineAssignments</code> 直接显示每一份接到哪些回线。</p>
|
|
1185
1198
|
</div>
|
|
1186
1199
|
<div class="dv-group">
|
|
1187
1200
|
<h3>5 · 待确认合并</h3>
|