gis-view-webgl2 0.1.2 → 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 (61) hide show
  1. package/README.md +12 -8
  2. package/dist/gis-view.css +1 -1
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +309 -59
  5. package/dist/index.js +1 -1
  6. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +117 -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 +107 -0
  9. package/docs/api/index.html +1 -1
  10. package/docs/demos/ApiCoverageDemo.vue +644 -0
  11. package/docs/demos/ChannelDemo.vue +14 -12
  12. package/docs/demos/ClusterDemo.vue +251 -0
  13. package/docs/demos/CopyMergeDemo.vue +3 -4
  14. package/docs/demos/EventDemo.vue +3 -3
  15. package/docs/demos/GisViewDemo.vue +64 -8809
  16. package/docs/demos/api-index.js +48 -41
  17. package/docs/demos/demos.js +33 -16
  18. package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +28 -0
  19. package/docs/demos/gis-view-demo/GisViewDemoDialogs.vue +82 -0
  20. package/docs/demos/gis-view-demo/GisViewDemoFooter.vue +16 -0
  21. package/docs/demos/gis-view-demo/GisViewDemoHeading.vue +16 -0
  22. package/docs/demos/gis-view-demo/GisViewDemoPanelStack.vue +71 -0
  23. package/docs/demos/gis-view-demo/GisViewDemoStage.vue +219 -0
  24. package/docs/demos/gis-view-demo/demo.css +1230 -0
  25. package/docs/demos/gis-view-demo/demoContext.js +12 -0
  26. package/docs/demos/gis-view-demo/panels/DataPanels.vue +303 -0
  27. package/docs/demos/gis-view-demo/panels/OpsPanels.vue +174 -0
  28. package/docs/demos/gis-view-demo/panels/StylePanels.vue +561 -0
  29. package/docs/demos/gis-view-demo/panels/ViewPanels.vue +396 -0
  30. package/docs/demos/gis-view-demo/useGisViewDemo.js +6188 -0
  31. 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
  32. 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
  33. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +3 -2
  34. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +87 -14
  35. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +8 -8
  36. 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 +40 -3
  37. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +26 -0
  38. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +6 -2
  39. 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 +39 -7
  40. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +13 -4
  41. package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +3 -0
  42. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +35 -19
  43. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +5 -0
  44. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +12 -6
  45. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +39 -22
  46. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +52 -13
  47. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +25 -1
  48. 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
  49. package/docs/steps/README.md +17 -11
  50. package/docs/steps/selftest-actions.html +3 -5
  51. package/docs/steps/selftest-demo.html +27 -18
  52. package/docs/steps/selftest-demos.html +387 -27
  53. package/docs/steps/selftest-docs.html +4 -4
  54. package/docs/steps/selftest-effects.html +200 -0
  55. package/docs/steps/selftest-features.html +512 -11
  56. package/docs/steps/selftest-labels.html +74 -0
  57. package/docs/steps/selftest-perf.html +293 -20
  58. package/docs/steps/selftest-scale.html +52 -29
  59. package/docs/steps/step-10-full.js +1 -1
  60. package/docs/steps/step-12-events.js +1 -1
  61. package/package.json +1 -1
@@ -14,6 +14,7 @@ const DOC_EDIT = { href: `${docsBase}#manual/07-指针与要素编辑.md`, label
14
14
  const DOC_UPDATE = { href: `${docsBase}#manual/06-数据与场景更新.md`, label: '06 数据与场景更新' }
15
15
  const DOC_LABELS = { href: `${docsBase}#manual/12-标签与装饰.md`, label: '12 标签与装饰' }
16
16
  const DOC_VIEW = { href: `${docsBase}#manual/14-视图与筛选.md`, label: '14 视图与筛选' }
17
+ const DOC_PERF = { href: `${docsBase}#manual/20-性能与大数据.md`, label: '20 性能与大数据' }
17
18
  const DOC_API = { href: `${docsBase}#接口协议.md`, label: '编辑接口协议' }
18
19
  const DOC_EXTEND = { href: `${docsBase}#manual/15-导出与集成.md`, label: '15 导出与集成' }
19
20
 
@@ -92,16 +93,24 @@ export const DEMOS = [
92
93
  doc: DOC_API,
93
94
  component: () => import('./BackendSyncDemo.vue')
94
95
  },
95
- {
96
- id: 'events',
97
- title: '事件与方法',
96
+ {
97
+ id: 'events',
98
+ title: '事件与方法',
98
99
  group: 'interact',
99
100
  blurb: '一条 * 订阅看全部事件,配上常用方法按钮 —— 事件载荷与方法返回值都打在日志里。',
100
- doc: DOC_GUIDE,
101
- component: () => import('./EventDemo.vue')
102
- },
103
- {
104
- id: 'copy-merge',
101
+ doc: DOC_GUIDE,
102
+ component: () => import('./EventDemo.vue')
103
+ },
104
+ {
105
+ id: 'api-coverage',
106
+ title: '接口能力',
107
+ group: 'interact',
108
+ blurb: '级联高亮三种触发、节点 / 线路 / 通道 / 分组 / 拐点选中、四类 focusOn、lineFollowsNode,以及坐标 / 视口 / 快照等只读 API 同屏对照。',
109
+ doc: { href: `${docsBase}#manual/17-参数速查.md`, label: '17 参数速查' },
110
+ component: () => import('./ApiCoverageDemo.vue')
111
+ },
112
+ {
113
+ id: 'copy-merge',
105
114
  title: '批量复制与合并',
106
115
  group: 'interact',
107
116
  blurb: '在画布 Shift 框选后整组复制或合并:复制 / 合并都接假后端,演示版本号、opId 幂等、失败重试与本地回滚;合并可切碰撞 / 中心进入目标区域两种触发时机,Alt 点击 / Ctrl+V 的复制事件也可直接回滚或用 addPoint 接管。',
@@ -156,14 +165,22 @@ export const DEMOS = [
156
165
  doc: DOC_EXTEND,
157
166
  component: () => import('./ExtensionDemo.vue')
158
167
  },
159
- {
160
- id: 'perf',
161
- title: '性能与质量',
162
- group: 'perf',
163
- blurb: '几千 / 十万级数据切换、筛选走 Worker、质量档自动升降,交互期细线与 MSAA 两个开关现场对比帧耗时。',
164
- doc: DOC_INTERNALS,
165
- component: () => import('./PerfDemo.vue')
166
- },
168
+ {
169
+ id: 'perf',
170
+ title: '性能与质量',
171
+ group: 'perf',
172
+ blurb: '几千 / 十万级数据切换、筛选走 Worker、质量档自动升降,交互期细线与 MSAA 两个开关现场对比帧耗时。',
173
+ doc: DOC_INTERNALS,
174
+ component: () => import('./PerfDemo.vue')
175
+ },
176
+ {
177
+ id: 'cluster',
178
+ title: '点聚合',
179
+ group: 'perf',
180
+ blurb: '多簇点数据按屏幕半径合并:开关 / 半径 / 最少点数 / 展开缩放现场调,读数与聚合点击载荷同屏。',
181
+ doc: DOC_PERF,
182
+ component: () => import('./ClusterDemo.vue')
183
+ },
167
184
  {
168
185
  id: 'effects',
169
186
  title: '动画效果',
@@ -0,0 +1,28 @@
1
+ <script setup>
2
+ import { useGisViewDemoContext } from './demoContext.js'
3
+ const {
4
+ notesOpen, scale, selectionOnTop, channelSelectMode, defaultHighlight, labelOptimize,
5
+ } = useGisViewDemoContext()
6
+ </script>
7
+
8
+ <template>
9
+ <div class="demo-card-header">
10
+ <div :class="{ 'demo-notes--closed': !notesOpen }">
11
+ <h3>框选、拐点、通道与拖拽</h3>
12
+ <button type="button" class="demo-notes-toggle" @click="notesOpen = !notesOpen">{{ notesOpen ? '收起说明' : '这一版都演示了什么(展开说明)' }}</button>
13
+ <p>Ctrl(macOS 为 Cmd)拖动框选节点;Ctrl + 点击节点逐个多选(勾上面板上的「点击多选」就不用按修饰键 —— 普通单击即取反累加,线路也认;双击的第二次点击不取反),之后拖动任意一个节点,整组节点与相关线路同步移动;Ctrl + 点击线路插入拐点(线比节点更近时优先给线路加点),拐点可选中、拖动、双击或按 Delete 删除。通道两端锚在节点上(带一圈粉色描边的那几个节点就是端点):回线共用一个骨架,拖动拐点整条通道一起折线,拐点拖到节点外、折回去、拐到另一侧都保持原样,拖动不再有任何夹取;通道宽度默认按两端节点尺寸取(两端不一样时按较小的那个取:整条通道用同一个宽度、回线始终平行,小节点那头正好切在节点边上、不会探出去,大节点那头落在节点里面),节点放大缩小、回线位置跟着变,始终铺在节点两边,两端的切点再按回线线宽往节点里收一段(面板上的「切点内收」),线因此不会从节点边缘探出去;面板上的「节点宽度倍数」与「通道宽度」是这两个来源的开关;拖动节点两端跟着走,两端节点没了通道与回线都不显示。</p>
14
+ <p>节点外观完全按电压等级来:大小与描边都取自 getGisStyle 的分档(勾上「按电压等级配色 / 定大小」)—— getGisStyle 的颜色在节点上指的是描边色,节点一律白底,配色只画在那一圈边上 —— **描边色与同电压的线路同色**(同一张分档表);同一个 vn_kv 的节点长得一模一样;「按站点类型换符号」默认开着:**每个类型都画 test.js 的原路径(SVG)** —— station 是同心圆环(>330kV 三圈、其余两圈),开关 / 开关站 / 换流站 / 直流 / 煤电 / 气电 / 风电 / 光伏 / 核电 / 水电 / 抽蓄 / 储能 / 外送是 rect 外框加内部线条(煤电 / 气电画在 200 见方里),所以画出来就是「station 圆、其他都是方的」;fill / stroke 认 'currentColor' 与 'currentStrokeColor',线条取 getGisStyle 里的节点描边色,「图标描边」调的是这条路径的粗细(CSS 像素,默认 1px,symbol.strokeWidth 那一项),面板上的「图标类型」下拉能把整层换成内置方块(station 退回内置圆)。代价是同电压的节点会因为符号不同而看起来不一样。「按电压分层」(默认开)是绘制顺序那一层的事:电压分档的规则各带一个 zIndex(取该档起始电压),电压越高画得越靠上、同一档的节点连成一层,叠在一起时高电压等级不会被低电压等级压住;层号只进绘制顺序,不改样式,关掉就退回按数据顺序画。「逐点样式」默认关着,勾上才会每 43 个节点给一个自带的颜色 / 大小(金色节点),用来演示逐点配置这一层优先于规则;「逐点符号」更直接:往节点数据里写 symbol,连规则表都盖得住 —— 想给某一个节点单独换形状 / 颜色就是这么写。下面两行是新增能力:内置的圆与正方形节点(「节点形状」下拉可以整体切换,方形带圆角半径,两者都由着色器按距离场画、放大也不会糊)、节点标签与线路标签(面板上分成两组各自配置:节点标签管位置 / 内容 / HTML / 数量 / 候选上限 / 避让 / 交互期动态收起,线路标签默认挂在线路中点显示名字,位置 / 内容 / HTML / 数量 / 候选 / 避让 / 交互期动态收起都能单独配,密集处会自动跳过会和已放下标签重叠的候选);节点标签面板上的「标签优化」是顶层开关 `labelOptimize`:默认打开(避让 / 上限截断 / 候选上限全生效),取消勾选就切到「强制显示」档 —— 不避让、不按上限截断,视口里该出现的标签一条不少)、点 / 线 / 通道的悬浮提示,以及筛选接口——条件支持「某个属性大于多少」这类写法,被过滤掉的节点会带着绑在它身上的线路与通道一起消失,数据量大时判定丢给 Web Worker,回主线程之前画面保持上一次结果。</p>
15
+ <p>多选与框选各有自己的样式,缺省与选中样式完全一致:面板上的「多选点样式 / 框选点样式 / 多选线样式」对应 options.pointMultiSelectStyle / pointBoxSelectStyle / lineMultiSelectStyle,单个节点或线路自己配了 multiSelectStyle / boxSelectStyle 就以它为准(勾上「逐点样式」出现的金色节点演示的就是这一层优先)。节点与线路另有两组放大系数:「节点放大 / 线宽放大」与地图无关,「节点跟随缩放 / 线路跟随缩放」是地图缩放的联动指数(0 表示不跟随),实际尺寸是两者相乘,地图放大不会把整体放大再放大一次。这一轮还修了放大后发糊:贴图符号(SVG / 图片)按「整体放大 × 缩放联动 × 设备像素比」分档重烤、只会被缩小不会被拉大,标签的间距默认从节点外沿量起、节点放大时一起往外让,位置跟着节点走。这一轮又把重烤的空窗补上了:停手 200 ms 之后(也就是档位不会再变的空闲时间)组件会把**下一档**的贴图先烤好,一帧烤一小批,真放大到那一档时贴图早就位,不会再出现「先糊一下才变清晰」;缩回之前放大过的倍数时,直接拿当初烤好的那张顶着画,连重烤都省了。框选矩形本身的样式也开放出来了:options.selectionBoxStyle 配 fill / fillOpacity / stroke / strokeWidth,面板上的「框选矩形」给了组件默认(蓝)、琥珀粗框、粉色填充与只描边四档。</p>
16
+ <p>这一轮又开了两组配置:① 线路的线型可以逐条配 —— solid / dashed / dotted / dashdot(也可以直接给图案数组或 { type, offset } 对象),写在线路的 lineType / dash 上,规则与 lineStyle 只是没写时的回落;面板上的「线型」下拉把整批线都写上自己那一档(逐条配置盖得住规则的统一线型),「数据 / 规则」一档把字段清掉、回到默认实线,图案长度是 CSS 像素、缩放时屏幕上的长短不变。② 光标按「悬停到哪一类要素上」分开配 —— 节点 / 线路 / 通道 / 拐点,外加空白、平移、框选、拖动与新增通道草稿这几个状态,写法支持系统关键字、var(--x) 与图片({ url, hotspot, fallback },内联 SVG 的 data URI 也行),按住 Ctrl / ⌘ 还能整批换一套(cursorStyle.ctrl,只有配过的分类会换);要素自己写了 cursor / ctrlCursor 时以要素为准。面板上的「光标档位」给了默认 / 分类关键字 / 图片光标 / Ctrl 变体四档,换档只改光标表、不重排场景(网格、草稿样式这些「画法与交互开关」同理)。demo 里 dc_line 的线路中间还挂了一个 PATH_DC_LINE 装饰(decorations: [{ position: 0.5, symbol }]),颜色跟这条线路的电压分档走;装饰的定位方式在面板上的「标记定位」里切:默认「自动」——线路没有拐点时按 position 的比例跟随(一直待在中点,拖端点也跟着走),加过拐点之后装饰就跟拐点一样钉在那一刻的坐标上,再拖端点 / 拐点、增删拐点都不动;「跟随线路」一直跟着新几何走,「写死坐标」把挂载那一刻的中点当 at 写死。勾上「与线路平行」装饰按锚点处的线路方向转(方框里的箭头指向线路方向),斜线、折线上都看得出来;勾掉「直流线路中点挂 PATH_DC_LINE」就能摘掉。</p>
17
+ <p>状态样式的默认行为这轮也改了:没配 pointHoverStyle / lineHoverStyle 时不再叠加尺寸 / 宽度增幅与强调环——悬停只触发提示与回调,选中只改选中集合,要反馈就自己配一份样式。面板上的「悬停强调」与「未配样式的内置强调」正是这两层开关(后者对应 defaultHighlight: true,也就是旧行为),把两个都关掉,悬停前后画面完全一致。相机还补齐了公开方法:zoomTo(scale) 直接给比例、zoomToBounds(bounds, padding) 把一段范围适应到视图内、focusOn(target, options) 定位到命中 / id / 坐标 / 矩形 / 通道,也可以一次给一批(数组取整体包围盒 —— 搜到三个节点就是这三个点的中心当屏幕中心),options 还配 center(只居中、比例一点不动)/ maxZoom / padding / select(顺带把这批选中,mode 决定怎么并进当前选中集)。</p>
18
+ <p>筛选下面新增了一行搜索:搜索只找不藏——输入关键字回车,第一个命中会被放到视图中间(下拉可以限定节点 / 线路 / 通道,通道会连成员回线一起搜,读数里会标出命中的字段与第几回回线);要隐藏不符合条件的要素还是用上面的筛选,两者互不影响。</p>
19
+ <p>性能这轮又往前走了一步:10 万点 / 10 万线连续平移、标签最多 200 个时原来只有 17.9 FPS(关掉标签 54.4 FPS)——瓶颈不是 GPU,而是交互期每隔一帧就要重排上百个标签 DOM。这一轮把标签图层做成「交互期整体变换 + 停手后重排」:平移 / 缩放 / 拖点期间标签层只写一次 transform,相机停手 180 ms 后再真实排版,三者分别提到 57.9 / 57.9 / 55.0 FPS,和关掉标签(57.7 / 57.9 FPS)同档;框选顺手也压了一遍,整屏框住约 9.9 万个节点从 97 ms 降到 52 ms。更早一轮修的是标签候选上限(label.scanLimit,缺省 maxCount × 8、不低于 256,面板上的「标签候选」可以切到前 256 个或整屏扫描直接对比),它先把标签开 / 关的差距从 13 倍(0.8 vs 10.3 FPS)压到 1.34 倍(36.5 vs 48.9 FPS);这一轮又把线路标签排版与拐点拾取做了粗筛:线路标签只遍历包围盒落在视口里的线(十万条回路也不必为几十个标签扫描整份数据,长线横穿视口也不会漏),拐点拾取只查光标附近的几条线(原来每次按下都要把全量折线的内部顶点扫一遍),线路标签配 hideOnInteract 之后交互期整层收起、停手后再排一次,密标签场景拖动时既不闪也不占帧;引擎每帧渲染函数耗时一直是 0.05 ~ 0.2 ms,所以这里换 WebGPU 并不能提高性能,完整实测与取舍见 docs/GisView实现原理.md 第 18 章。</p>
20
+ <p>数据集下拉第一项是仓库根目录的 new.json(2164 个站点 / 2629 条通道 / 3669 条回线):站点按 lon / lat 落点,一条 channel_data 一条走廊,line_data 展开成多回线。线色 / 线宽与站点符号直接来自 test.js 的分档(getGisStyle 按 type + vn_kv 分档,getSymbolPath 按 station_type / 子系统取 SVG),demo 把它们逐档翻成了批量规则,关掉「按电压等级配色 / 定大小」就能对比同一份数据的两套外观。</p>
21
+ <p>Ctrl + 点击线路或通道加点之后,拐点不会分散写进每条回线,而是归到一个新的拐点集合里:一个分组一组(没拆分过就是一条通道一组),组里有 member_line_ids(哪些回线共用)与 group_route(两端 + 拐点),结构对齐 test.json 的 parallel_groups;「导出拐点集合」按钮会把这份集合复制到剪贴板,可以直接回写业务数据。</p>
22
+ <p>这一轮补上三块:① 通道可以分组了——默认一条通道一组,「拆分分组」按每组几条回线把它拆开(第一组沿用当前拐点、新分组的线路不带拐点),「合并分组」把其余分组并进第一组(被并入的线路跟随第一组的拐点,第一组没有拐点就一条都不带),加点 / 拖点只作用于同一分组,分组声明写回 channels[].groups;② 鹰眼图也支持规则——「规则沿用主图」只搬颜色与层级(主图的像素尺寸不会搬过来),「按电压单独配色」给小图自己配一份(半径缩到 1 ~ 2.6px、高电压用方块、zIndex 沿用电压分档),「分层」控制分层叠画、层级下拉只画某几层,「放大节点 / 放大线路」让节点与线路跟着小图自身的倍数放大(滚轮或 - / + 都算);③ 定向更新——updatePoint / updateLine / updateChannel 改一个节点、线路或通道就重渲染相关的视图,只重解析受影响的那一小片,其余要素与缓存一个字节都不动,鹰眼图与标签覆盖层跟着重画。</p>
23
+ <p>热力图这一轮把节点那一层补上了:合成数据的节点与线路都按一张「热区场」随机生成 max_loading_rate(在数据范围里撒 4 ~ 6 个热区,节点取自己坐标处的场值、线路取两端中点处的场值 —— 画出来是几团往外化开的色斑,而不是均匀噪点),作用对象因此可以切「节点 + 线路 + 通道」/「只按节点上色」/「线路 + 通道回线」/「只按通道自己的值」四档;面板上的「重新随机一份热力数据」能在任意数据集上现生成一份(换种子、按当前节点范围重建场,只改字段值,坐标与拓扑一动不动),真实数据 new.json 的节点本来没有这一列、线路那一列又恒为 1.5,点一下就能立刻看到热力。作用对象带上节点时图标不再跟着换:图标符号写了 tint(逐实例着色),填充按白色烘焙、由热力色逐实例染上,描边写 currentStrokeColor 不参与染色 —— 形状与描边还是那张图标,只是填充跟着值走;贴图缓存键里因此不再有逐点的热力色,900 节点时绘制调用仍然在 36 上下(以前一个节点一张贴图时会涨到约 1500,10 万级直接把主线程烤住),想看「完全不参与热力图」的效果就把节点从作用对象里去掉 —— 图标本身不会变成内置圆 / 方块。</p>
24
+ <p>这一轮补上六件事:① <b>节点复制</b>——按住 Alt(面板上可以改成别的键)点一个节点,复制品立刻压在源节点上跟着鼠标走,松开左键(不再看 Alt 什么时候松)或再点一下落地;也可以点面板上的「先点按钮再选节点」再点一个节点(复制品落在它旁边),或者选中节点按 Ctrl+C、把鼠标移到目标位置按 Ctrl+V。复制全程走 <b>onPointCopy</b>:start / move / commit / end / cancel(复制到剪贴板另发一条 clipboard),落地前的 commit 阶段可以 payload.cancel() 撤掉、payload.update() / setItem() 改数据,id 与整份数据由 copy.createId / copy.createItem 定;落地之后的 end 阶段还能 payload.revert() 整份回滚(面板上「复制后模拟接口失败」那个勾就是它)。② <b>节点合并</b>——把一个节点拖到另一个节点上(碰撞按两个节点画在屏幕上的半径之和 + merge.distance 判,整组拖动不参与),<b>组件不弹任何对话框</b>:pending 停在原地等使用方裁决,面板上的「合并确认」三档分别是「停住(由这个自写弹层来确认)」/ false 直接合并 / 函数返回 Promise(先调接口再决定,resolve(false) 取消);确认会先推进到 confirm 阶段并把合并结果给你(可以改,也可以在这一步因为「接口失败」整份撤掉),确认之后来源节点的数据并进目标(缺省 { ...来源, ...目标 },可用 merge.createItem 整份接管)、<b>尺寸取双方最大、图标优先目标</b>、绑在它上面的线路改绑、合并出来的自环与零长线删掉;两端因此撞成同一条的通道按 merge.channelGroups 处理(各自保留分组 / 合成一个分组,自环的整条删),读数里会带上改端点的通道数与并掉的通道数。取消或接口失败则把拖动的节点与跟随它的线路还原回原位,并补发一条拖拽结束。③ <b>所有快捷键都能改</b>——options.shortcuts 按动作配(框选 / 多选 / 加点 / 复制 / 复制到剪贴板 / 粘贴 / 取消 / 删除 / 适应视图),写法是纯修饰键(ctrl / meta / shift / alt / mod)、组合键(ctrl+c)、单键(escape / 0)或数组,写 false 表示不绑定;面板「视图 · 交互」里那四个下拉就是改框选 / 多选 / 加点 / 复制的修饰键——比如把框选改成 Shift,之后 Shift + 拖动才出选框,按住 Shift 时连光标变体也跟着走。④ <b>选中抬层</b>——selectionOnTop(缺省开)把选中的节点、拖动中的整组、待确认合并的目标节点与选中线路所在的整条通道画在所有要素之上:选中了就一定看得见,与 zIndex 无关;关掉就完全按 zIndex 与数据顺序画。⑤ <b>删除(Delete)</b>——选中拐点 / 线路 / 节点之后按 Delete(或面板上的「删除选中」)先出一份删除载荷:要删什么、连带什么、能按哪些范围删。<b>组件不弹界面</b>:pending 停在原地等 <b>confirm(scope)</b> / cancel(),Esc 也能取消;范围三档是「只删选中的线路」/「删除整个分组」/「删除整条通道」(后两档只在删的是通道回线、且这条通道分过组时才有),选中的拐点与节点一律一起删 —— <b>删节点会连挂在它身上的线路、以及拿它当端点的整条通道一起删</b>,载荷里的 lineCount / channelCount 就是这份连带读数;确认之后整段合成一条历史,撤销一步全回来;开关是 features.remove,快捷键照样走 shortcuts.remove(可以单独配成 shift+delete 之类)。⑥ <b>通道选中模式</b>——channelSelectMode 决定点中一条通道回线时选中什么:line(缺省,只选这一条)/ group(连它所在分组一起)/ channel(整条通道一起);在 group / channel 下 Ctrl + 点击是<b>整批取反</b>,通道以外的线路永远只选它自己。它只影响「下一次点选怎么摊开」,切换走 updateOptions,场景一个字节都不用重排。面板「视图 · 复制合并」里那两项就是它们。</p>
25
+ </div>
26
+ <span class="demo-badge">WebGL2 · 无内置 GUI</span>
27
+ </div>
28
+ </template>
@@ -0,0 +1,82 @@
1
+ <script setup>
2
+ import { useGisViewDemoContext } from './demoContext.js'
3
+ const {
4
+ VOLTAGE_LEVELS, stationDialog, closeStationDialog, confirmStationDialog, mergeDialog, removeDialog,
5
+ confirmMergeDialog, cancelMergeDialog, confirmMergeByApiFail, confirmRemoveDialog, cancelRemoveDialog,
6
+ } = useGisViewDemoContext()
7
+ </script>
8
+
9
+ <template>
10
+ <div v-if="mergeDialog" class="demo-merge-mask" @click.self="cancelMergeDialog()">
11
+ <div class="demo-merge-dialog">
12
+ <h4>合并节点 · demo 自己写的确认框</h4>
13
+ <p>组件不带任何界面:onPointMerge 的 pending 载荷停在这里等裁决,下面三个按钮就是调它的 confirm() / cancel()。</p>
14
+ <div class="demo-merge-pair">
15
+ <span>{{ mergeDialog.source }}</span>
16
+ <b>→</b>
17
+ <span>{{ mergeDialog.target }}</span>
18
+ </div>
19
+ <p class="demo-merge-detail">{{ mergeDialog.detail }}</p>
20
+ <div class="demo-merge-actions">
21
+ <button type="button" @click="cancelMergeDialog()">取消(拖动的节点回原位)</button>
22
+ <button type="button" class="is-primary" @click="confirmMergeDialog()">确认合并</button>
23
+ <button type="button" @click="confirmMergeByApiFail()">先调接口(这次失败)</button>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ <div v-if="removeDialog" class="demo-merge-mask" @click.self="cancelRemoveDialog()">
28
+ <div class="demo-merge-dialog">
29
+ <h4>删除要素 · demo 自己写的确认框</h4>
30
+ <p>组件不带任何界面:'remove' 事件的 pending 载荷停在这里等裁决,下面按钮就是调它的 confirm(scope) / cancel()。</p>
31
+ <ul class="demo-remove-list">
32
+ <li v-for="line in removeDialog.lines" :key="'rm-line-' + String(line.id)">
33
+ 线路 {{ line.id }}
34
+ <span>{{ line.channelId ? '通道 ' + line.channelId + (line.groupId !== null ? ' / 分组 ' + line.groupId : '') : '不在通道里' }}</span>
35
+ </li>
36
+ <li v-for="point in removeDialog.points" :key="'rm-point-' + String(point.id)">
37
+ 节点 {{ point.id }}
38
+ <span>连带 {{ point.lineCount }} 条线路 / {{ point.channelCount }} 条通道</span>
39
+ </li>
40
+ <li v-for="waypoint in removeDialog.waypoints" :key="'rm-wp-' + waypoint.lineIndex + '-' + waypoint.pathIndex">
41
+ 拐点 {{ waypoint.lineId }}
42
+ <span>这条回线的第 {{ waypoint.pathIndex }} 个顶点</span>
43
+ </li>
44
+ <li v-if="!removeDialog.points.length && !removeDialog.lines.length && !removeDialog.waypoints.length">没有选中的要素</li>
45
+ </ul>
46
+ <div class="demo-merge-actions">
47
+ <button type="button" @click="cancelRemoveDialog()">取消删除</button>
48
+ <button
49
+ v-for="action in removeDialog.actions"
50
+ :key="action.id"
51
+ type="button"
52
+ :class="{ 'is-primary': action.id === removeDialog.scope }"
53
+ @click="confirmRemoveDialog(action.id)"
54
+ >
55
+ {{ action.label }}({{ action.lineCount }} 回)
56
+ </button>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ <div v-if="stationDialog" class="demo-merge-mask" @click.self="closeStationDialog()">
61
+ <div class="demo-merge-dialog demo-station-dialog">
62
+ <h4>新增场站 · demo 自己写的表单</h4>
63
+ <p>组件不带任何界面:这份表单收集字段,确认时走 <code>view.addPoint(item)</code>;落点取自这一下右键的 world 坐标,随便改。</p>
64
+ <div class="demo-station-grid">
65
+ <label>编号<input v-model.trim="stationDialog.id" data-demo="add-station-id" /></label>
66
+ <label>名称<input v-model.trim="stationDialog.name" data-demo="add-station-name" /></label>
67
+ <label>x(世界)<input v-model.number="stationDialog.x" type="number" step="any" data-demo="add-station-x" /></label>
68
+ <label>y(世界)<input v-model.number="stationDialog.y" type="number" step="any" data-demo="add-station-y" /></label>
69
+ <label>电压等级
70
+ <select v-model.number="stationDialog.vn_kv" data-demo="add-station-voltage">
71
+ <option v-for="level in VOLTAGE_LEVELS" :key="level" :value="level">{{ level }} kV</option>
72
+ </select>
73
+ </label>
74
+ </div>
75
+ <p v-if="stationDialog.error" class="demo-station-error">{{ stationDialog.error }}</p>
76
+ <div class="demo-merge-actions">
77
+ <button type="button" data-demo="add-station-cancel" @click="closeStationDialog()">取消</button>
78
+ <button type="button" class="is-primary" data-demo="add-station-confirm" @click="confirmStationDialog()">确认新增(addPoint)</button>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup>
2
+ import { useGisViewDemoContext } from './demoContext.js'
3
+ const {
4
+ lastEvent, dataChangeLabel, selectChangeLabel, shortcutEventLabel, renderLabel,
5
+ } = useGisViewDemoContext()
6
+ </script>
7
+
8
+ <template>
9
+ <p class="gis-demo-event">{{ lastEvent }}</p>
10
+ <p class="gis-demo-events">
11
+ <b>onDataChange</b><span>{{ dataChangeLabel }}</span>
12
+ <b>onSelectChange</b><span>{{ selectChangeLabel }}</span>
13
+ <b>onShortcut</b><span>{{ shortcutEventLabel }}</span>
14
+ <b>onRender</b><span>{{ renderLabel }}</span>
15
+ </p>
16
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup>
2
+ import { useGisViewDemoContext } from './demoContext.js'
3
+ const {
4
+ dataset,
5
+ } = useGisViewDemoContext()
6
+ </script>
7
+
8
+ <template>
9
+ <div class="demo-heading">
10
+ <div>
11
+ <p class="eyebrow">GisView</p>
12
+ <h2>{{ dataset.points.length.toLocaleString() }} 节点 / {{ dataset.lines.length.toLocaleString() }} 线路</h2>
13
+ </div>
14
+ <p>组件只提供画布、实例方法与自带鹰眼图,没有别的内置界面:下面这块浮层、按钮与读数都写在 demo 里,样式完全由使用方决定。</p>
15
+ </div>
16
+ </template>
@@ -0,0 +1,71 @@
1
+ <script setup>
2
+ import { useGisViewDemoContext } from './demoContext.js'
3
+ import ViewPanels from './panels/ViewPanels.vue'
4
+ import StylePanels from './panels/StylePanels.vue'
5
+ import DataPanels from './panels/DataPanels.vue'
6
+ import OpsPanels from './panels/OpsPanels.vue'
7
+
8
+ const {
9
+ PANEL_TABS, panelTab, switchPanelTab, activePanel, activePanelItem, tabPanelItems, openPanel, togglePanel, closePanel,
10
+ } = useGisViewDemoContext()
11
+ </script>
12
+
13
+ <template>
14
+ <div class="demo-panel-stack">
15
+ <div class="demo-tabs" role="tablist" aria-label="配置分类">
16
+ <button
17
+ v-for="tab in PANEL_TABS"
18
+ :key="tab.value"
19
+ type="button"
20
+ role="tab"
21
+ :aria-selected="panelTab === tab.value"
22
+ :class="{ 'is-active': panelTab === tab.value }"
23
+ @click="switchPanelTab(tab.value)"
24
+ >{{ tab.label }}</button>
25
+ </div>
26
+
27
+ <div class="demo-panel-buttons" role="group" aria-label="配置主题">
28
+ <button
29
+ v-for="item in tabPanelItems"
30
+ :key="item.id"
31
+ type="button"
32
+ :class="{ 'is-active': activePanel === item.id }"
33
+ :title="item.label + ' · ' + item.hint"
34
+ :aria-expanded="activePanel === item.id"
35
+ aria-controls="demo-panel-detail"
36
+ @click="togglePanel(item.id)"
37
+ @contextmenu.prevent="openPanel(item.id)"
38
+ >
39
+ <span>{{ item.label }}</span>
40
+ <span class="demo-panel-button-mark" aria-hidden="true">{{ activePanel === item.id ? '−' : '+' }}</span>
41
+ </button>
42
+ </div>
43
+
44
+ <section
45
+ v-show="activePanelItem"
46
+ id="demo-panel-detail"
47
+ class="demo-panel-inline"
48
+ role="region"
49
+ :aria-label="activePanelItem?.label"
50
+ >
51
+ <header class="demo-panel-inline-head">
52
+ <div>
53
+ <strong>{{ activePanelItem?.label }}</strong>
54
+ <span>{{ activePanelItem?.hint }}</span>
55
+ </div>
56
+ <button type="button" title="收起面板(Esc)" @click="closePanel()">收起</button>
57
+ </header>
58
+ <div class="demo-panel-inline-body">
59
+ <ViewPanels />
60
+ <StylePanels />
61
+ <DataPanels />
62
+ <OpsPanels />
63
+ </div>
64
+ </section>
65
+
66
+ <aside v-show="!activePanelItem" class="demo-panel-empty">
67
+ <strong>选一个配置主题</strong>
68
+ <span>点上面的按钮,面板会在这一列下方展开;调画布时不用反复开关。</span>
69
+ </aside>
70
+ </div>
71
+ </template>
@@ -0,0 +1,219 @@
1
+ <script setup>
2
+ import { useGisViewDemoContext } from './demoContext.js'
3
+ const {
4
+ containerRef, stageRef, isFullscreen, fullscreenSupported, toggleFullscreen, viewRef, selectionCount, stats,
5
+ qualityBadge, cursorText, hostZoom, zoomText, pointerText, screenText, lngLatText, canvasMenu, closeCanvasMenu,
6
+ canvasMenuStyle, canvasMenuRows, runCanvasMenuAction, pointSizeScale, lineWidthScale, minimapSize,
7
+ minimapEnabled, minimapShowLines, minimapExtent, minimapZoomable, minimapDragAction, minimapChannelCircuits,
8
+ minimapZoomStep, zoomMinimap, minimapRuleMode, minimapLayered, minimapLevels, minimapCascade, minimapPointSize,
9
+ minimapLineWidth, minimapPointZoom, minimapLineZoom, minimapPointMapZoom, minimapLineMapZoom, minimapBoxFill,
10
+ minimapBoxFillOpacity, minimapBoxStroke, minimapBoxStrokeWidth, minimapBoxStrokeStyle, minimapBoxRadius,
11
+ minimapDrawLayer, minimapSummary, minimapLayerOptions, heatDensityText, channelMenu, closeChannelMenu,
12
+ menuLineTargets, menuLineRows, assignMenuLine, channelMenuStyle, selectMenuGroup, runMenuAction, pointMenu,
13
+ closePointMenu, pointMenuStyle, pointMenuRows, runPointMenuAction, ref,
14
+ } = useGisViewDemoContext()
15
+ </script>
16
+
17
+ <template>
18
+ <div ref="stageRef" class="demo-stage" :style="{ '--demo-host-zoom': hostZoom }">
19
+ <div ref="containerRef" class="demo-canvas"></div>
20
+ <div class="demo-stage-toolbar">
21
+ <button type="button" @click="viewRef?.fitView()">适应视图</button>
22
+ <button v-if="fullscreenSupported" type="button" @click="toggleFullscreen">{{ isFullscreen ? '退出全屏' : '全屏' }}</button>
23
+ </div>
24
+ <div v-if="channelMenu" class="demo-channel-menu" :style="channelMenuStyle">
25
+ <div class="demo-channel-menu-head">
26
+ <strong>{{ channelMenu.channelId }}</strong>
27
+ <span>共 {{ channelMenu.groupCount }} 组 · 当前 {{ channelMenu.groupId }}</span>
28
+ <button type="button" @click="closeChannelMenu">×</button>
29
+ </div>
30
+ <ul class="demo-channel-menu-groups">
31
+ <li v-for="group in channelMenu.groups" :key="String(group.id)" :class="{ 'is-current': group.current }" @click="selectMenuGroup(group)">
32
+ <span>{{ group.id }}</span>
33
+ <span>{{ group.lineCount }} 回{{ group.waypointCount ? ` / ${group.waypointCount} 拐点` : '' }}</span>
34
+ </li>
35
+ </ul>
36
+ <div v-if="menuLineRows.length" class="demo-channel-menu-lines">
37
+ <p class="demo-channel-menu-subtitle">线路分配 · 每条回线都可以拆成新分组或并进别的分组</p>
38
+ <ul>
39
+ <li v-for="row in menuLineRows" :key="String(row.id)">
40
+ <span class="demo-channel-menu-line-id" :title="String(row.id)">{{ row.id }}</span>
41
+ <select v-model="menuLineTargets[String(row.id)]" class="demo-channel-menu-target">
42
+ <option v-for="choice in row.targets" :key="String(choice.value)" :value="String(choice.value)">{{ choice.label }}</option>
43
+ </select>
44
+ <button type="button" class="demo-channel-menu-assign" @click="assignMenuLine(row)">分配</button>
45
+ </li>
46
+ </ul>
47
+ </div>
48
+ <div class="demo-channel-menu-actions">
49
+ <button v-for="action in channelMenu.actions" :key="action.id" type="button" :disabled="!action.enabled" :title="action.reason ?? ''" @click="runMenuAction(action)">{{ action.label }}</button>
50
+ </div>
51
+ <p class="demo-channel-menu-hint">命中回线 {{ channelMenu.lineId }} · 当前第 {{ channelMenu.group.index + 1 }} 组({{ channelMenu.group.lineCount }} 回线)· 这些数据都在 onContextMenu(payload).channelContext 里,点分组切换当前分组,按钮就是 runChannelAction,线路分配走 moveChannelLine(两回线选「拆成新分组」就是每组一条)</p>
52
+ </div>
53
+ <div v-if="pointMenu" class="demo-channel-menu demo-point-menu" :style="pointMenuStyle">
54
+ <div class="demo-channel-menu-head">
55
+ <strong>{{ pointMenu.item?.name ?? pointMenu.item?.id ?? '节点' }}</strong>
56
+ <span>{{ pointMenu.wasSelected ? '命中已经选中的那一个' : '命中的没在选中集里' }} · 选中集 {{ pointMenu.selection?.count ?? 1 }} 个</span>
57
+ <button type="button" @click="closePointMenu">×</button>
58
+ </div>
59
+ <p class="demo-channel-menu-hint">右键时它在不在选中集里?看载荷里的 <b>wasSelected</b>:为 true 就保留整份选中集(多选不被打断),为 false 就先把这一个选上;选中集有多少、都是什么,看 <b>selection</b> 与 getSelectionData()</p>
60
+ <div class="demo-channel-menu-actions">
61
+ <button v-for="row in pointMenuRows" :key="row.id" type="button" :disabled="!row.enabled" :title="row.reason" @click="runPointMenuAction(row)">{{ row.label }}</button>
62
+ </div>
63
+ </div>
64
+ <div v-if="canvasMenu" class="demo-channel-menu demo-canvas-menu" :style="canvasMenuStyle">
65
+ <div class="demo-channel-menu-head">
66
+ <strong>{{ canvasMenu.hit ? '画布菜单 · 命中要素' : '画布菜单 · 空白处' }}</strong>
67
+ <span>world ({{ canvasMenu.world[0].toFixed(2) }}, {{ canvasMenu.world[1].toFixed(2) }}) · 选中 {{ canvasMenu.selection?.count ?? 0 }} 个</span>
68
+ <button type="button" @click="closeCanvasMenu">×</button>
69
+ </div>
70
+ <p class="demo-channel-menu-hint">右键载荷(onContextMenu)里就带着 x / y、world、命中的要素与 selection 读数:这份菜单整个写在 demo 里,组件不带任何界面。每一行都是一个公开方法;落在空白处先 preventDefault 掉浏览器自带菜单。空白处多一行「新增场站」:弹一份 demo 自己的表单,收集字段后走 addPoint,落点默认就是这一下右键的 world 坐标。</p>
71
+ <div class="demo-channel-menu-actions">
72
+ <button v-for="row in canvasMenuRows" :key="row.id" type="button" :data-demo="'canvas-menu-' + row.id" :disabled="!row.enabled" :title="row.reason" @click="runCanvasMenuAction(row)">{{ row.label }}</button>
73
+ </div>
74
+ </div>
75
+ <aside class="demo-overlay">
76
+ <p class="demo-overlay-title">自建读数面板</p>
77
+ <dl>
78
+ <div><dt>光标</dt><dd>{{ cursorText }}</dd></div>
79
+ <div><dt>屏幕</dt><dd>{{ screenText }}</dd></div>
80
+ <div><dt>经纬</dt><dd>{{ lngLatText }}</dd></div>
81
+ <div><dt>缩放</dt><dd>{{ zoomText }}</dd></div>
82
+ <div><dt>指针</dt><dd>{{ pointerText }}</dd></div>
83
+ <div><dt>要素</dt><dd>{{ stats ? `${stats.pointCount} 点 / ${stats.lineCount} 线` : '—' }}</dd></div>
84
+ <div><dt>分层</dt><dd>{{ stats ? (stats.pointLayers > 1 ? `${stats.pointLayers} 层` : '不分层') : '—' }}</dd></div>
85
+ <div><dt>选中</dt><dd>{{ selectionCount ? `${selectionCount} 个` : '—' }}</dd></div>
86
+ <div><dt>渲染</dt><dd>{{ stats ? `${stats.fps.toFixed(0)} FPS · ${stats.frameMs.toFixed(1)} ms` : '—' }}</dd></div>
87
+ <div><dt>拾取</dt><dd>{{ stats ? `${stats.pickMs.toFixed(2)} ms` : '—' }}</dd></div>
88
+ <div><dt>绘制调用</dt><dd>{{ stats ? `${stats.drawCalls} 次` : '—' }}</dd></div>
89
+ <div><dt>密度场</dt><dd>{{ heatDensityText }}</dd></div>
90
+ <div><dt>画质</dt><dd>{{ qualityBadge }}</dd></div>
91
+ </dl>
92
+ </aside>
93
+ <div class="demo-minimap">
94
+ <details class="demo-minimap-settings">
95
+ <summary>鹰眼图设置<em>{{ minimapSummary }}</em></summary>
96
+ <div class="demo-minimap-toolbar">
97
+ <select v-model="minimapSize">
98
+ <option value="small">小</option>
99
+ <option value="medium">中</option>
100
+ <option value="large">大</option>
101
+ <option value="huge">特大</option>
102
+ </select>
103
+ <select v-model="minimapExtent" :disabled="!minimapEnabled" title="小图的范围:auto = 数据与底图合起来(GeoJSON 底图不会缩成一个点)、data = 只看数据范围(老行为)、background = 只看底图范围">
104
+ <option value="auto">范围:自动(数据 ∪ 底图)</option>
105
+ <option value="data">范围:只看数据</option>
106
+ <option value="background">范围:只看底图</option>
107
+ </select>
108
+ <label class="demo-minimap-switch">
109
+ <input v-model="minimapEnabled" type="checkbox" />
110
+ 鹰眼
111
+ </label>
112
+ <label class="demo-minimap-switch">
113
+ <input v-model="minimapShowLines" type="checkbox" />
114
+ 线路
115
+ </label>
116
+ <label class="demo-minimap-switch" title="通道是否画全部回线:关掉一个通道只画偏移最靠中间的那一根">
117
+ <input v-model="minimapChannelCircuits" type="checkbox" />
118
+ 回线
119
+ </label>
120
+ <label class="demo-minimap-switch">
121
+ <input v-model="minimapZoomable" type="checkbox" />
122
+ 可缩放
123
+ </label>
124
+ <select v-model="minimapDragAction" :disabled="!minimapEnabled || !minimapZoomable" title="开了可缩放之后,小图空白处拖动的动作(按住 Ctrl / ⌘ 拖动是另一个)">
125
+ <option value="pan">拖动=平移小图</option>
126
+ <option value="box">拖动=框选</option>
127
+ </select>
128
+ <select v-model.number="minimapZoomStep" :disabled="!minimapEnabled || !minimapZoomable" title="每一次放大 / 缩小的倍率(滚轮一格与 - / + 按钮)">
129
+ <option :value="1.2">步进 1.2</option>
130
+ <option :value="1.4">步进 1.4</option>
131
+ <option :value="1.8">步进 1.8</option>
132
+ <option :value="2">步进 2</option>
133
+ </select>
134
+ <button type="button" :disabled="!minimapEnabled || !minimapZoomable" @click="zoomMinimap(1 / minimapZoomStep)">-</button>
135
+ <button type="button" :disabled="!minimapEnabled || !minimapZoomable" @click="zoomMinimap(minimapZoomStep)">+</button>
136
+ </div>
137
+ <div class="demo-minimap-toolbar demo-minimap-toolbar--rules">
138
+ <select v-model="minimapRuleMode" :disabled="!minimapEnabled" title="小图的规则:沿用主图(只取颜色与层级)/ 按电压单独配一份(半径、形状、层级都在内)/ 完全不用规则">
139
+ <option value="inherit">规则沿用主图</option>
140
+ <option value="voltage">按电压单独配色</option>
141
+ <option value="off">不用规则</option>
142
+ </select>
143
+ <label class="demo-minimap-switch" title="分层叠画:zIndex 大的盖在上面;关掉按数据顺序画">
144
+ <input v-model="minimapLayered" type="checkbox" :disabled="!minimapEnabled" />
145
+ 分层
146
+ </label>
147
+ <select v-model="minimapLevels" :disabled="!minimapEnabled || minimapRuleMode === 'off'" title="只画哪几层:层号就是电压分档的起始电压">
148
+ <option value="all">全部层级</option>
149
+ <option value="top2">最高两层</option>
150
+ <option v-for="layer in minimapLayerOptions" :key="layer" :value="layer">只看 {{ layer }}kV 层</option>
151
+ </select>
152
+ <select v-model="minimapCascade" :disabled="!minimapEnabled || !minimapLayerOptions.length || minimapLevels === 'all'" title="被 levels 挡掉的节点,连在它们身上的线路怎么跟着隐藏(与筛选的 cascade 同一套说法)">
153
+ <option value="binding">挡掉的节点:按绑定连锁</option>
154
+ <option value="coordinate">挡掉的节点:按坐标连锁</option>
155
+ <option value="both">挡掉的节点:绑定 + 坐标</option>
156
+ <option :value="false">挡掉的节点:不连锁</option>
157
+ </select>
158
+ <label class="demo-minimap-switch" title="节点半径跟着小图自身的倍数放大(0 不跟随、1 同倍率)">
159
+ 放大节点
160
+ <input v-model.number="minimapPointZoom" type="number" min="0" max="1" step="0.1" :disabled="!minimapEnabled" />
161
+ </label>
162
+ <label class="demo-minimap-switch" title="线路宽度跟着小图自身的倍数放大(0 不跟随、1 同倍率)">
163
+ 放大线路
164
+ <input v-model.number="minimapLineZoom" type="number" min="0" max="1" step="0.1" :disabled="!minimapEnabled" />
165
+ </label>
166
+ <span class="demo-control-hint">「放大节点 / 放大线路」跟的是<strong>小图自身</strong>的倍数:要勾上「可缩放」再滚轮 / +- 放大鹰眼图才看得到效果(改这两个值时这里会自动打开);要让节点 / 线路跟着<strong>主视图</strong>的缩放走,用下面的「节点跟主图 / 线路跟主图」(pointMapZoomScale)。</span>
167
+ <label class="demo-minimap-switch" title="节点半径的整体倍数:与主视图的 pointSizeScale 同名同义(常数倍数,与小图倍数无关)">
168
+ 整体节点
169
+ <input v-model.number="minimapPointSize" type="number" min="0" max="4" step="0.2" :disabled="!minimapEnabled" />
170
+ </label>
171
+ <label class="demo-minimap-switch" title="线路宽度的整体倍数:与主视图的 lineWidthScale 同名同义">
172
+ 整体线路
173
+ <input v-model.number="minimapLineWidth" type="number" min="0" max="4" step="0.2" :disabled="!minimapEnabled" />
174
+ </label>
175
+ <label class="demo-minimap-switch" title="节点半径跟着主视图的地图倍数放大:0 不跟随、1 同倍率、0.5 是主图放大 4 倍节点放大 2 倍(基准缺省取主图适应视图时的比例)">
176
+ 节点跟主图
177
+ <input v-model.number="minimapPointMapZoom" type="number" min="0" max="1" step="0.1" :disabled="!minimapEnabled" />
178
+ </label>
179
+ <label class="demo-minimap-switch" title="线路宽度跟着主视图的地图倍数放大:0 不跟随、1 同倍率">
180
+ 线路跟主图
181
+ <input v-model.number="minimapLineMapZoom" type="number" min="0" max="1" step="0.1" :disabled="!minimapEnabled" />
182
+ </label>
183
+ </div>
184
+ <div class="demo-minimap-toolbar demo-minimap-toolbar--box">
185
+ <label class="demo-minimap-switch" title="视口框(和框选矩形)的填充色">
186
+ 框底色
187
+ <input v-model="minimapBoxFill" type="color" :disabled="!minimapEnabled" />
188
+ </label>
189
+ <label class="demo-minimap-switch" title="视口框的填充不透明度">
190
+ 透明度
191
+ <input v-model.number="minimapBoxFillOpacity" type="number" min="0" max="1" step="0.05" :disabled="!minimapEnabled" />
192
+ </label>
193
+ <label class="demo-minimap-switch" title="视口框(和框选矩形)的边框色">
194
+ 框边色
195
+ <input v-model="minimapBoxStroke" type="color" :disabled="!minimapEnabled" />
196
+ </label>
197
+ <label class="demo-minimap-switch" title="视口框(和框选矩形)的边框宽度,0 表示不画边框">
198
+ 边框
199
+ <input v-model.number="minimapBoxStrokeWidth" type="number" min="0" max="6" step="1" :disabled="!minimapEnabled" />
200
+ </label>
201
+ <select v-model="minimapBoxStrokeStyle" :disabled="!minimapEnabled" title="边框线型:实线 / 虚线 / 点线(框选矩形缺省是虚线,没单独配时沿用这里的线型与颜色)">
202
+ <option value="solid">实线</option>
203
+ <option value="dashed">虚线</option>
204
+ <option value="dotted">点线</option>
205
+ </select>
206
+ <label class="demo-minimap-switch" title="视口框的圆角(CSS 像素)">
207
+ 圆角
208
+ <input v-model.number="minimapBoxRadius" type="number" min="0" max="16" step="1" :disabled="!minimapEnabled" />
209
+ </label>
210
+ <label class="demo-minimap-switch" title="draw 钩子示例:每帧在小图上画一枚指北针,并把当前范围的中心圈出来">
211
+ <input v-model="minimapDrawLayer" type="checkbox" :disabled="!minimapEnabled" />
212
+ 自定义层
213
+ </label>
214
+ </div>
215
+ <span class="demo-minimap-hint">内置鹰眼图 · 拖框移视图 · 空白处长按后跟随(没开「可缩放」时)· 开了「可缩放」:滚轮按步进放大缩小(Ctrl / ⌘ + 滚轮留给主图)、空白处拖动按上面的选择平移小图或直接框选、关掉「回线」一个通道只画一根 · 范围三档:自动(数据 ∪ 底图,GeoJSON 底图不会缩成一点)/ 只看数据 / 只看底图 · 规则:沿用主图(节点取主图的描边色,不再是白点)/ 按电压单独配(半径与形状也换)/ 不用,分层叠画可以关,层级可以只看某几层 · 尺寸三组系数与主视图对应:整体节点 / 整体线路是常数倍数,放大节点 / 放大线路跟着小图自身的倍数,节点跟主图 / 线路跟主图跟着主视图的地图倍数 · 视口框与框选矩形的底色、不透明度、边框(颜色 / 宽度 / 线型)与圆角都能调 · 「自定义层」是 draw 钩子的示例:每帧按小图坐标画指北针与范围中心十字</span>
216
+ </details>
217
+ </div>
218
+ </div>
219
+ </template>