gis-view-webgl2 0.1.11 → 0.1.13

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 (54) hide show
  1. package/README.md +0 -24
  2. package/dist/gis-view.css +1 -1
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +524 -33
  5. package/dist/index.js +1 -1
  6. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +156 -8
  7. package/docs/GisView.md +6 -6
  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 +157 -7
  9. package/docs/demos/BackgroundDemo.vue +600 -0
  10. package/docs/demos/ChannelDemo.vue +145 -3
  11. package/docs/demos/StyleDemo.vue +280 -69
  12. package/docs/demos/api-index.js +35 -19
  13. package/docs/demos/demos.js +26 -18
  14. package/docs/demos/gis-view-demo/demo.css +76 -0
  15. package/docs/demos/gis-view-demo/panels/StylePanels.vue +24 -8
  16. package/docs/demos/gis-view-demo/panels/ViewPanels.vue +33 -3
  17. package/docs/demos/gis-view-demo/useGisViewDemo.js +236 -22
  18. package/docs/format/02-/350/212/202/347/202/271/344/270/216/347/272/277/350/267/257.md +1 -1
  19. 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 +3 -3
  20. package/docs/from0/06-/344/273/216/346/225/260/346/215/256/345/210/260/345/256/236/344/276/213-/350/247/243/346/236/220/345/261/202.md +13 -11
  21. package/docs/from0/15-/351/231/204/345/275/225-/345/270/270/351/207/217/351/200/237/346/237/245/344/270/216/350/270/251/345/235/221/346/270/205/345/215/225.md +1 -1
  22. package/docs/index.html +2 -2
  23. package/docs/internals/10-/350/247/204/345/210/231/346/235/241/344/273/266/344/270/216/347/212/266/346/200/201/346/240/267/345/274/217.md +3 -3
  24. package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +1 -1
  25. package/docs/internals/14-/350/203/214/346/231/257/345/261/202.md +1 -0
  26. package/docs/internals/15-03-/351/200/220/350/275/256/350/256/260/345/275/225-/346/200/247/350/203/275/344/270/216/344/277/256/345/244/215-/344/270/213.md +199 -0
  27. package/docs/internals/17-/350/260/203/350/257/225/344/270/216/346/211/251/345/261/225/345/273/272/350/256/256.md +1 -1
  28. package/docs/internals/18-/346/200/247/350/203/275/345/256/236/346/265/213/344/270/216WebGPU/350/257/204/344/274/260.md +32 -0
  29. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +1 -1
  30. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +217 -26
  31. 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 +2 -2
  32. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +37 -4
  33. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +58 -1
  34. package/docs/manual/11-/346/240/267/345/274/217/344/270/216/350/247/204/345/210/231.md +59 -8
  35. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +1 -1
  36. package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +220 -2
  37. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +77 -16
  38. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +30 -17
  39. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +11 -4
  40. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +17 -8
  41. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +128 -4
  42. 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 +2 -2
  43. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +13 -1
  44. 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 -1
  45. package/docs/steps/index.html +1 -1
  46. package/docs/steps/selftest-demo.html +2 -2
  47. package/docs/steps/selftest-demos.html +626 -11
  48. package/docs/steps/selftest-docs.html +8 -4
  49. package/docs/steps/selftest-features.html +1428 -2
  50. package/docs/steps/selftest-perf.html +296 -25
  51. package/docs/steps/selftest-scale.html +61 -3
  52. package/docs/steps/selftest-select.html +303 -1
  53. package/docs/steps/step-06-style.js +3 -2
  54. package/package.json +2 -2
@@ -43,12 +43,17 @@ const simplifyColor = ref('#ff6b35')
43
43
  const simplifyWidth = ref(4)
44
44
  const simplifyDash = ref('dashed')
45
45
  const simplifyText = ref('通道简略:关(reopen 后甲 / 丙通道会各折叠成一条代表线)')
46
+ /** 隐藏回线 / 分组后,剩余回线是保留原槽位还是重新居中;切完立刻重排车道 */
47
+ const channelHiddenLineLayout = ref('preserve')
48
+ const groupVisibilityText = ref('分组显隐:先点「准备两分组」,再试单组 / 批量隐藏与恢复')
49
+ const channelLaneText = ref('车道布局:—')
46
50
  /** 待确认的删除请求('remove' 事件的 pending 载荷):确认框照着它渲染 */
47
51
  const removeRequest = ref(null)
48
52
  const removeText = ref('删除:按 Delete 或点上面的按钮,先出载荷、确认之后才动数据')
49
53
  const channelsText = ref('—')
50
54
  const groupsText = ref('—')
51
55
  const visibilityText = ref('隐藏 / 显示:—')
56
+ const channelLineVisibilityText = ref('通道内线路显隐:—')
52
57
  const changedText = ref('最近变化:—')
53
58
  const exportText = ref('')
54
59
  const logLines = ref([])
@@ -317,6 +322,8 @@ const buildOptions = () => ({
317
322
  // 分组拐点拖动:共用骨架 / 只移同组同序号拐点
318
323
  channelWaypointDragMode: channelWaypointDragMode.value,
319
324
  channelSimplify: simplifyOption(),
325
+ // 隐藏回线 / 分组后:保留原车道槽位,或让剩余可见回线重新居中
326
+ channelHiddenLineLayout: channelHiddenLineLayout.value,
320
327
  // 删除配置:confirm 缺省(null)表示组件不弹界面、停在待确认 —— 这一页订阅 'remove' 事件自己画确认框
321
328
  remove: { scope: 'lines' },
322
329
  // 交互框里的「加拐点 / 删拐点」动的是分组骨架,要有这一项才不是灰的(Ctrl + 点击加点、双击删点也一起开了)
@@ -376,6 +383,113 @@ const applyChannelSimplify = () => {
376
383
  pushLog(simplifyText.value)
377
384
  }
378
385
 
386
+ /** 车道读数:隐藏组仍保留成员与偏移,读 getChannels() 就能看出 preserve / center 的差别 */
387
+ const syncGroupVisibilityReadout = () => {
388
+ const entry = view?.getChannels()?.[0]
389
+ const channelId = entry?.channelId ?? entry?.id
390
+ if (channelId === undefined || channelId === null) {
391
+ channelLaneText.value = '车道布局:—'
392
+ return
393
+ }
394
+ const states = (view?.getChannels() ?? []).filter(state => String(state.channelId) === String(channelId))
395
+ if (!states.length) {
396
+ channelLaneText.value = '车道布局:—'
397
+ return
398
+ }
399
+ const dataLines = view?.getData().lines ?? []
400
+ const spacing = states.find(state => Number(state.spacing) > 0)?.spacing ?? 0
401
+ const describeState = state => {
402
+ const lineIds = state.lineIndexes.map(index => dataLines[index]?.id ?? index).join(',')
403
+ const offsets = state.offsets.map(offset => Number(offset).toFixed(2)).join(',')
404
+ return `${state.id ?? state.channelId}${state.visible ? '' : '(隐)'}[${lineIds}] → [${offsets}]`
405
+ }
406
+ channelLaneText.value = `车道布局:${channelHiddenLineLayout.value} · spacing ${Number(spacing).toFixed(2)} · ${states.map(describeState).join(' · ')}`
407
+ }
408
+
409
+ /** 准备两分组显隐演示:首通道只有一组时,把末回线拆成独立组;其余情况保持现状 */
410
+ const prepareGroupVisibilityDemo = () => {
411
+ const current = firstChannelGroups()
412
+ if (!view || !current) {
413
+ groupVisibilityText.value = '分组显隐:当前没有可编辑的通道'
414
+ return
415
+ }
416
+ let groups = current.groups
417
+ let added = false
418
+ if (groups.length < 2) {
419
+ const lineIds = groups[0]?.lineIds ?? []
420
+ if (lineIds.length < 2) {
421
+ groupVisibilityText.value = `${current.channelId}:至少要有两回线才能拆成两组`
422
+ return
423
+ }
424
+ const result = view.addChannelGroup(current.channelId, [lineIds[lineIds.length - 1]])
425
+ if (!result.ok) {
426
+ groupVisibilityText.value = '分组显隐准备失败:' + result.reason
427
+ pushLog('addChannelGroup 被拒绝:' + result.reason)
428
+ return
429
+ }
430
+ groups = view.getChannelGroups(current.channelId)
431
+ added = true
432
+ }
433
+ groupVisibilityText.value = `${current.channelId}:${groups.map(group => `「${group.id}」${group.lineIds.length} 回`).join(',')}${added ? ';已把末回线拆成独立组' : ''}。单组按钮操作末组,批量按钮一次收起或恢复全部组`
434
+ pushLog('分组显隐准备:' + groups.map(group => group.id + '(' + group.lineIds.length + ' 回)').join(' / '))
435
+ syncReadouts()
436
+ }
437
+
438
+ /**
439
+ * 分组显隐:all 为 true 走批量引用,否则只隐藏 / 恢复最后一个分组。
440
+ * 单组隐藏后保留其余分组的车道;批量隐藏全部组时成员一起退场,但分组与拐点声明仍在。
441
+ */
442
+ const setVisibilityGroups = (visible, all) => {
443
+ const current = firstChannelGroups()
444
+ if (!view || !current || current.groups.length < 2) {
445
+ groupVisibilityText.value = '分组显隐:先点「准备两分组」,再试单组 / 批量隐藏与恢复'
446
+ return
447
+ }
448
+ const targets = all ? current.groups : [current.groups[current.groups.length - 1]]
449
+ const refs = targets.map(group => ({ channelId: current.channelId, groupId: group.id }))
450
+ const outcome = visible ? view.showFeatures('group', refs) : view.hideFeatures('group', refs)
451
+ const targetLabel = all ? '全部组(批量)' : `末组「${targets[0].id}」`
452
+ groupVisibilityText.value = outcome.ok
453
+ ? `${visible ? '显示' : '隐藏'}${targetLabel}:${outcome.ids.map(String).join('、')}${outcome.skipped.length ? ` · 跳过 ${outcome.skipped.length}` : ''}`
454
+ : `${visible ? '显示' : '隐藏'}${targetLabel}失败:${outcome.reason}`
455
+ pushLog(visible ? 'showFeatures(group)' : 'hideFeatures(group)', groupVisibilityText.value)
456
+ syncReadouts()
457
+ }
458
+
459
+ const setLastGroupVisible = visible => setVisibilityGroups(visible, false)
460
+ const setAllGroupsVisible = visible => setVisibilityGroups(visible, true)
461
+
462
+ /**
463
+ * 通道内单条回线显隐:引用直接给线路 id,通道 / 分组声明完全不动。
464
+ * 隐藏后成员仍留在 getChannelGroups() 里,只从画面与车道账本退场;preserve / center 仍按 options 决定其余回线的位置。
465
+ */
466
+ const setFirstChannelLineVisible = (visible) => {
467
+ const current = firstChannelGroups()
468
+ if (!view || !current) {
469
+ channelLineVisibilityText.value = '通道内线路显隐:当前没有可编辑的通道'
470
+ return
471
+ }
472
+ const group = [...current.groups].reverse().find(item => item.lineIds?.length)
473
+ const lineId = group?.lineIds?.[group.lineIds.length - 1]
474
+ if (lineId === undefined) {
475
+ channelLineVisibilityText.value = `${current.channelId}:读不到可隐藏的成员回线`
476
+ return
477
+ }
478
+ const outcome = visible ? view.showFeatures('line', lineId) : view.hideFeatures('line', lineId)
479
+ channelLineVisibilityText.value = outcome.ok
480
+ ? `${visible ? '显示' : '隐藏'}通道内回线:通道 ${String(current.channelId)} · 分组「${String(group.id)}」· 回线 ${String(lineId)}(线路下标 ${outcome.indexes.join('、')})`
481
+ : `${visible ? '显示' : '隐藏'}通道内回线失败:${outcome.reason}`
482
+ pushLog(visible ? 'showFeatures(line)' : 'hideFeatures(line)', channelLineVisibilityText.value)
483
+ syncReadouts()
484
+ }
485
+
486
+ /** 隐藏回线车道:preserve 保留原槽位,center 让剩余可见回线重新居中;只改布局,不动分组 */
487
+ const applyChannelHiddenLineLayout = () => {
488
+ view?.updateOptions({ channelHiddenLineLayout: channelHiddenLineLayout.value })
489
+ syncGroupVisibilityReadout()
490
+ pushLog('隐藏回线车道:' + channelHiddenLineLayout.value)
491
+ }
492
+
379
493
  /**
380
494
  * 通道读数:几条通道、各自几回线、每组分了几组。
381
495
  * getChannels() 给的是**分组**那一档(一条通道拆成几组就返回几条,按 channelId 归并);
@@ -401,6 +515,7 @@ const syncReadouts = () => {
401
515
  syncMenu()
402
516
  syncChangedChannels()
403
517
  syncSimplifyReadout()
518
+ syncGroupVisibilityReadout()
404
519
  }
405
520
 
406
521
  /** 最近变化通道:分组 / 成员 / 拐点 / 端点 / 可见性 / 其余声明字段 */
@@ -1283,24 +1398,51 @@ onBeforeUnmount(() => {
1283
1398
  </div>
1284
1399
 
1285
1400
  <div class="dv-group">
1286
- <h3>隐藏 / 显示与最近变化</h3>
1401
+ <h3>显隐 / 车道布局与最近变化</h3>
1287
1402
  <div class="dv-row">
1288
1403
  <button type="button" class="dv-button" data-demo="point-hide" @click="setFirstFeatureVisible('point', false)">隐藏第一个节点</button>
1289
1404
  <button type="button" class="dv-button dv-button--primary" data-demo="point-show" @click="setFirstFeatureVisible('point', true)">显示第一个节点</button>
1290
1405
  <button type="button" class="dv-button" data-demo="line-hide" @click="setFirstFeatureVisible('line', false)">隐藏第一条线路</button>
1291
1406
  <button type="button" class="dv-button dv-button--primary" data-demo="line-show" @click="setFirstFeatureVisible('line', true)">显示第一条线路</button>
1292
1407
  </div>
1408
+ <div class="dv-row">
1409
+ <button type="button" class="dv-button" data-demo="channel-line-hide" @click="setFirstChannelLineVisible(false)">隐藏首通道末回线</button>
1410
+ <button type="button" class="dv-button dv-button--primary" data-demo="channel-line-show" @click="setFirstChannelLineVisible(true)">显示首通道末回线</button>
1411
+ </div>
1293
1412
  <div class="dv-row">
1294
1413
  <button type="button" class="dv-button" data-demo="channel-hide" @click="setFirstFeatureVisible('channel', false)">隐藏第一条通道</button>
1295
1414
  <button type="button" class="dv-button dv-button--primary" data-demo="channel-show" @click="setFirstFeatureVisible('channel', true)">显示第一条通道</button>
1296
1415
  <button type="button" class="dv-button" data-demo="channel-clear-changed" @click="clearChannelChanges">通道对账</button>
1297
1416
  </div>
1417
+ <div class="dv-row">
1418
+ <button type="button" class="dv-button" data-demo="channel-group-prepare" @click="prepareGroupVisibilityDemo">准备两分组</button>
1419
+ <button type="button" class="dv-button" data-demo="channel-group-hide" @click="setLastGroupVisible(false)">隐藏末组</button>
1420
+ <button type="button" class="dv-button dv-button--primary" data-demo="channel-group-show" @click="setLastGroupVisible(true)">显示末组</button>
1421
+ </div>
1422
+ <div class="dv-row">
1423
+ <button type="button" class="dv-button" data-demo="channel-group-hide-all" @click="setAllGroupsVisible(false)">批量隐藏全部组</button>
1424
+ <button type="button" class="dv-button dv-button--primary" data-demo="channel-group-show-all" @click="setAllGroupsVisible(true)">批量显示全部组</button>
1425
+ <label>隐藏后车道
1426
+ <select v-model="channelHiddenLineLayout" data-demo="channel-hidden-line-layout" @change="applyChannelHiddenLineLayout">
1427
+ <option value="preserve">preserve(保留原槽位)</option>
1428
+ <option value="center">center(剩余回线居中)</option>
1429
+ </select>
1430
+ </label>
1431
+ </div>
1298
1432
  <p class="dv-readout" data-demo="channel-visibility">{{ visibilityText }}</p>
1433
+ <p class="dv-readout" data-demo="channel-line-visibility">{{ channelLineVisibilityText }}</p>
1434
+ <p class="dv-readout" data-demo="channel-group-visibility">{{ groupVisibilityText }}</p>
1435
+ <p class="dv-readout" data-demo="channel-lane-layout">{{ channelLaneText }}</p>
1299
1436
  <p class="dv-readout" data-demo="channel-changed">{{ changedText }}</p>
1300
1437
  <p class="dv-hint">
1301
1438
  节点 / 线路 / 通道都能用 hideFeatures / showFeatures 收起再恢复,只改 visible,数据本身不删;通道收起时成员回线、分组、拐点与编辑状态都保留;
1302
- getChangedChannels 会把分组、成员、拐点、端点、可见性与其余字段的变化逐条列出来,
1303
- clearChangedChannels 把当前声明记成新的对账基准。
1439
+ 「隐藏首通道末回线」演示的是通道内部只收一条成员线路:引用直接传 line id,分组与其它回线不动,隐藏后的车道仍受 preserve / center 控制;
1440
+ 分组也走同一条接口:「准备两分组」把首通道末回线拆成独立组,单组按钮操作最后一组,批量按钮把全部组引用一次交给接口。
1441
+ getChangedChannels 会把分组、成员、拐点、端点、可见性与其余字段的变化逐条列出来,clearChangedChannels 把当前声明记成新的对账基准。
1442
+ </p>
1443
+ <p class="dv-hint">
1444
+ 隐藏回线车道:preserve(缺省)保留整条通道原来的槽位;center 把剩余可见回线压紧并重新居中。
1445
+ 先隐藏末组,再切换两项,看下面读数里的 offset 变化 —— 分组、成员与拐点声明始终不变。
1304
1446
  </p>
1305
1447
  </div>
1306
1448
 
@@ -1,10 +1,10 @@
1
1
  <script setup>
2
2
  /**
3
- * 样式与规则演示:同一份数据,换几套规则看画面怎么变。
3
+ * 样式与规则演示:同一份数据,换几套规则看画面怎么变,并现场改单个 / 多个要素。
4
4
  *
5
- * 规则就是「条件 + 样式」的数组(`pointRules` / `lineRules`),改完交给 updateOptions 立刻重排;
6
- * 演示里还对比了两种优先级:规则样式 < 要素自身字段(逐点样式),
7
- * 以及选中高亮(pointSelectStyle / lineSelectStyle)压在最上面。
5
+ * 规则就是「条件 + 样式」的数组(`pointRules` / `lineRules`),改完交给 updateOptions 立刻重排;
6
+ * 普通规则高于 pointStyle / lineStyle、低于要素自身字段(逐点样式),单条规则写 override: true 后压过要素字段;
7
+ * 悬停 / 选中 / 多选 / 框选 / 级联这些交互样式始终压在最上面,override 规则也盖不住它们写的颜色。
8
8
  */
9
9
  import { onBeforeUnmount, onMounted, ref } from 'vue'
10
10
  import { createGisView } from '../../src/index.js'
@@ -18,47 +18,89 @@ let view = null
18
18
 
19
19
  const preset = ref('voltage')
20
20
  const pointSize = ref(7)
21
- const lineWidth = ref(1.6)
22
- const perPointStyle = ref(false)
21
+ const lineWidth = ref(1.6)
22
+ const perPointStyle = ref(false)
23
+ const ruleOverride = ref(false)
23
24
  const highlightOn = ref(true)
25
+ const cascadeOn = ref(true)
26
+ const editPointId = ref(dataset.points[0]?.id ?? '')
27
+ const editLineId = ref(dataset.lines[0]?.id ?? '')
24
28
  const applyMs = ref('—')
25
29
  const ruleText = ref('—')
26
30
  const selectText = ref('未选中:点一下节点或线路看高亮')
31
+ const cascadeText = ref('级联高亮未触发:点一下画布上的节点或线路')
32
+ const editText = ref('单 / 多要素补丁尚未执行')
33
+ const detailText = ref('点「查询详情」读取当前节点、线路与第一条通道')
34
+
35
+ const pointChoices = dataset.points.slice(0, 12)
36
+ const lineChoices = dataset.lines.slice(0, 12)
37
+ const pointBatchIds = dataset.points.slice(0, 3).map(point => point.id)
38
+ const lineBatchIds = dataset.lines.slice(0, 3).map(line => line.id)
39
+
40
+ const INTERACTION_STYLES = {
41
+ pointHoverStyle: { color: '#fb923c', strokeColor: '#7c2d12', strokeWidth: 2, sizeScale: 1.25 },
42
+ pointSelectStyle: { color: '#ffd166', strokeColor: '#5c3b00', strokeWidth: 2, sizeScale: 1.45 },
43
+ pointMultiSelectStyle: { color: '#f97316', strokeColor: '#7c2d12', strokeWidth: 2, sizeScale: 1.4 },
44
+ pointBoxSelectStyle: { color: '#22d3ee', strokeColor: '#155e75', strokeWidth: 2, sizeScale: 1.4 },
45
+ lineHoverStyle: { color: '#fb923c', width: 4, opacity: 1 },
46
+ lineSelectStyle: { color: '#ffd166', width: 5, opacity: 1 },
47
+ lineMultiSelectStyle: { color: '#f97316', width: 5.5, opacity: 1 },
48
+ waypointSelectStyle: { color: '#ffd166', strokeColor: '#5c3b00', strokeWidth: 2, sizeScale: 1.35 }
49
+ }
50
+
51
+ const CASCADE_STYLES = {
52
+ scope: 'both',
53
+ pointStyle: { color: '#22d3ee', strokeColor: '#155e75', strokeWidth: 2, sizeScale: 1.3 },
54
+ lineStyle: { color: '#22d3ee', width: 4.5, opacity: 1 }
55
+ }
27
56
 
28
57
  /**
29
- * 选中高亮:颜色 / 尺寸 / 描边都是状态样式的字段(`pointSelectStyle` / `lineSelectStyle`),
30
- * 命中时盖在规则与要素自身字段之上;关掉时写 false 把样式摘掉(只清「选中」这一层,不动规则)。
31
- * 注意别把这份样式塞给 `defaultHighlight`:那个开关只认布尔值,管的是「没配状态样式时的内置强调环」。
58
+ * 交互样式按状态拆成几份:悬停 / 单选 / 多选 / 框选 / 级联各有一种颜色。
59
+ * 这些颜色是 `resolvePointWithBase` / `resolveLineStyleInto` 取值链的第一层,
60
+ * 即使规则写了 override: true 也压不过它们;关掉高亮时整层传 false,普通规则照旧生效。
32
61
  */
33
- const selectHighlight = () => (highlightOn.value
34
- ? { color: '#ffd166', sizeScale: 1.5, strokeColor: '#12233a', strokeWidth: 2 }
35
- : false)
36
-
37
- /** 三套预设:按电压上色 / 按负载上色 / 单色 */
38
- const buildPointRules = () => {
39
- if (preset.value === 'load') {
40
- return [
41
- { name: '重载', when: { max_loading_rate: { gte: 1.1 } }, style: { color: '#ff7a45', strokeColor: '#7a2d10', strokeWidth: 2, size: pointSize.value + 3 } },
42
- { name: '正常', when: { max_loading_rate: { gte: 0.7, lt: 1.1 } }, style: { color: '#ffd166', strokeColor: '#6d5a1e', strokeWidth: 1.6, size: pointSize.value } },
43
- { name: '轻载', when: { max_loading_rate: { lt: 0.7 } }, style: { color: '#4fd1a5', strokeColor: '#1d5c46', strokeWidth: 1.6, size: pointSize.value - 1 } }
44
- ]
45
- }
46
- if (preset.value === 'mono') {
47
- return [{ name: '统一', when: { field: 'id', op: 'exists' }, style: { color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.5, size: pointSize.value } }]
48
- }
49
- return [
50
- { name: '1050 kV', when: { vn_kv: { gte: 1050 } }, style: { color: VOLTAGE_COLORS[1050], strokeColor: '#5c1338', strokeWidth: 2.2, size: pointSize.value + 3, zIndex: 4 } },
51
- { name: '525 kV', when: { vn_kv: { gte: 525, lt: 1050 } }, style: { color: VOLTAGE_COLORS[525], strokeColor: '#2f1b5c', strokeWidth: 2, size: pointSize.value + 1, zIndex: 3 } },
52
- { name: '230 kV', when: { vn_kv: { gte: 230, lt: 525 } }, style: { color: VOLTAGE_COLORS[230], strokeColor: '#123a5c', strokeWidth: 1.6, size: pointSize.value, zIndex: 2 } },
53
- { name: '其它', when: { vn_kv: { lt: 230 } }, style: { color: VOLTAGE_COLORS[15.7], strokeColor: '#12402f', strokeWidth: 1.4, size: pointSize.value - 1, zIndex: 1 } }
54
- ]
55
- }
56
-
57
- const buildLineRules = () => (preset.value === 'load'
58
- ? [{ name: '线路重载', when: { max_loading_rate: { gte: 1 } }, style: { color: '#ff9f68', width: lineWidth.value + 1.4 } }]
59
- : [])
62
+ const interactionStyles = () => highlightOn.value
63
+ ? { ...INTERACTION_STYLES }
64
+ : {
65
+ pointHoverStyle: false,
66
+ pointSelectStyle: false,
67
+ pointMultiSelectStyle: false,
68
+ pointBoxSelectStyle: false,
69
+ lineHoverStyle: false,
70
+ lineSelectStyle: false,
71
+ lineMultiSelectStyle: false,
72
+ waypointSelectStyle: false
73
+ }
74
+
75
+ const cascadeOptions = () => (cascadeOn.value ? { ...CASCADE_STYLES } : false)
60
76
 
61
- /** 逐点样式:数据里自己写了 color / size 的节点直接压过规则(每 6 个一个金节点) */
77
+ /** 三套预设:按电压上色 / 按负载上色 / 单色 */
78
+ const withRuleOverride = rules => (ruleOverride.value ? rules.map(rule => ({ ...rule, override: true })) : rules)
79
+
80
+ const buildPointRules = () => {
81
+ if (preset.value === 'load') {
82
+ return withRuleOverride([
83
+ { name: '重载', when: { max_loading_rate: { gte: 1.1 } }, style: { color: '#ff7a45', strokeColor: '#7a2d10', strokeWidth: 2, size: pointSize.value + 3 } },
84
+ { name: '正常', when: { max_loading_rate: { gte: 0.7, lt: 1.1 } }, style: { color: '#ffd166', strokeColor: '#6d5a1e', strokeWidth: 1.6, size: pointSize.value } },
85
+ { name: '轻载', when: { max_loading_rate: { lt: 0.7 } }, style: { color: '#4fd1a5', strokeColor: '#1d5c46', strokeWidth: 1.6, size: pointSize.value - 1 } }
86
+ ])
87
+ }
88
+ if (preset.value === 'mono') {
89
+ return withRuleOverride([{ name: '统一', when: { field: 'id', op: 'exists' }, style: { color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.5, size: pointSize.value } }])
90
+ }
91
+ return withRuleOverride([
92
+ { name: '1050 kV', when: { vn_kv: { gte: 1050 } }, style: { color: VOLTAGE_COLORS[1050], strokeColor: '#5c1338', strokeWidth: 2.2, size: pointSize.value + 3, zIndex: 4 } },
93
+ { name: '525 kV', when: { vn_kv: { gte: 525, lt: 1050 } }, style: { color: VOLTAGE_COLORS[525], strokeColor: '#2f1b5c', strokeWidth: 2, size: pointSize.value + 1, zIndex: 3 } },
94
+ { name: '230 kV', when: { vn_kv: { gte: 230, lt: 525 } }, style: { color: VOLTAGE_COLORS[230], strokeColor: '#123a5c', strokeWidth: 1.6, size: pointSize.value, zIndex: 2 } },
95
+ { name: '其它', when: { vn_kv: { lt: 230 } }, style: { color: VOLTAGE_COLORS[15.7], strokeColor: '#12402f', strokeWidth: 1.4, size: pointSize.value - 1, zIndex: 1 } }
96
+ ])
97
+ }
98
+
99
+ const buildLineRules = () => withRuleOverride(preset.value === 'load'
100
+ ? [{ name: '线路重载', when: { max_loading_rate: { gte: 1 } }, style: { color: '#ff9f68', width: lineWidth.value + 1.4 } }]
101
+ : [])
102
+
103
+ /** 逐点样式:数据里自己写了 color / size 的节点默认压过普通规则;开启强制覆盖后由规则压回来(每 6 个一个金节点) */
62
104
  const pointsWithOwnStyle = () => (perPointStyle.value
63
105
  ? dataset.points.map((point, index) => (index % 6 === 0 ? { ...point, color: '#ffd15c', size: 15, selectStyle: { color: '#ff7a45' } } : point))
64
106
  : dataset.points)
@@ -69,11 +111,10 @@ const buildOptions = () => ({
69
111
  channels: dataset.channels,
70
112
  pointStyle: { size: pointSize.value, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.5 },
71
113
  lineStyle: { width: lineWidth.value, color: 'rgba(120, 168, 226, 0.5)' },
72
- pointRules: buildPointRules(),
73
- lineRules: buildLineRules(),
74
- pointSelectStyle: selectHighlight(),
75
- lineSelectStyle: selectHighlight(),
76
- waypointSelectStyle: selectHighlight(),
114
+ pointRules: buildPointRules(),
115
+ lineRules: buildLineRules(),
116
+ ...interactionStyles(),
117
+ cascadeHighlight: cascadeOptions(),
77
118
  showGrid: true,
78
119
  fitPadding: 60,
79
120
  // 功能开关缺省全关:这一页要能平移缩放、能悬停选中看状态样式,显式打开查看类
@@ -81,10 +122,11 @@ const buildOptions = () => ({
81
122
  label: { content: '{vn_kv} kV', position: 'top', gap: 7, maxCount: 30, style: { color: '#27405f', fontSize: '11px' } },
82
123
  // 标签按优化档显示(限数 + 避让),见 docs/GisView.md 的「标签显示优化开关」
83
124
  labelOptimize: true,
84
- onReady: () => {
85
- emit('notice', `${dataset.points.length} 个节点,规则预设「${preset.value}」`)
86
- syncRules()
87
- },
125
+ onReady: () => {
126
+ emit('notice', `${dataset.points.length} 个节点,规则预设「${preset.value}」`)
127
+ syncRules()
128
+ requestAnimationFrame(syncDetail)
129
+ },
88
130
  /**
89
131
  * 读数接 onSelectChange 而不是 onSelect:前者对「点选、框选、API selectFeatures()」都会发,
90
132
  * 后者只代表画布上的那一次点击 —— 用接口选中时读数也不会停在「未选中」。
@@ -92,14 +134,19 @@ const buildOptions = () => ({
92
134
  */
93
135
  onSelectChange: (info) => {
94
136
  selectText.value = info?.count
95
- ? `选中 ${info.count} 个(点 ${info.points} / 线 ${info.lines} / 通道 ${info.channels}):高亮样式在 pointSelectStyle / lineSelectStyle 上`
96
- : '未选中:点一下节点或线路看高亮'
137
+ ? `选中 ${info.count} 个(点 ${info.points} / 线 ${info.lines} / 通道 ${info.channels}):多选与框选会换成各自的状态色`
138
+ : '未选中:点一下节点或线路看高亮;Ctrl 点击多选、Shift 拖动框选'
139
+ },
140
+ onCascadeHighlight: (payload) => {
141
+ cascadeText.value = payload
142
+ ? `级联:起点 ${payload.origin?.item?.id ?? payload.origin?.channelId ?? '—'},邻接点 ${payload.points.length} 个 / 线 ${payload.lines.length} 条`
143
+ : '级联高亮未触发:点一下画布上的节点或线路'
97
144
  }
98
145
  })
99
146
 
100
- const syncRules = () => {
101
- ruleText.value = `节点规则 ${buildPointRules().length} 条 · 线路规则 ${buildLineRules().length} 条`
102
- }
147
+ const syncRules = () => {
148
+ ruleText.value = `节点规则 ${buildPointRules().length} 条 · 线路规则 ${buildLineRules().length} 条${ruleOverride.value ? ' · 全部 override: true' : ''}`
149
+ }
103
150
 
104
151
  /**
105
152
  * 改规则 / 尺寸 / 高亮:只发样式类配置(不重发 points / lines),
@@ -112,9 +159,8 @@ const applyRules = () => {
112
159
  lineRules: buildLineRules(),
113
160
  pointStyle: { size: pointSize.value, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.5 },
114
161
  lineStyle: { width: lineWidth.value, color: 'rgba(120, 168, 226, 0.5)' },
115
- pointSelectStyle: selectHighlight(),
116
- lineSelectStyle: selectHighlight(),
117
- waypointSelectStyle: selectHighlight()
162
+ ...interactionStyles(),
163
+ cascadeHighlight: cascadeOptions()
118
164
  })
119
165
  syncRules()
120
166
  applyMs.value = `${(performance.now() - started).toFixed(1)} ms`
@@ -125,6 +171,120 @@ const applyPerPointStyle = () => {
125
171
  view?.updateOptions({ points: pointsWithOwnStyle() })
126
172
  syncRules()
127
173
  }
174
+
175
+ let pointEditStep = 0
176
+ let lineEditStep = 0
177
+ const POINT_EDIT_COLORS = ['#f97316', '#22c55e', '#e11d48', '#0ea5e9']
178
+ const LINE_EDIT_COLORS = ['#ec4899', '#14b8a6', '#eab308', '#8b5cf6']
179
+
180
+ /** 单个节点:按字段合并,颜色 / 尺寸 / 图标 / 描边都能现场改 */
181
+ const updateSinglePoint = () => {
182
+ const color = POINT_EDIT_COLORS[pointEditStep % POINT_EDIT_COLORS.length]
183
+ pointEditStep += 1
184
+ view?.updatePoint(editPointId.value, {
185
+ color,
186
+ size: 18,
187
+ strokeColor: '#17233d',
188
+ strokeWidth: 3,
189
+ symbol: { type: 'square', radius: 3 }
190
+ })
191
+ editText.value = `updatePoint(${editPointId.value}):color=${color},size=18,symbol=square`
192
+ syncDetail()
193
+ }
194
+
195
+ /** 一批节点:同一份补丁一次写完,patchPoints 返回 indexes / count / skipped */
196
+ const patchPointBatch = () => {
197
+ const result = view?.patchPoints(pointBatchIds, {
198
+ color: '#22c55e',
199
+ size: 15,
200
+ strokeColor: '#14532d',
201
+ strokeWidth: 2
202
+ })
203
+ editText.value = `patchPoints(${pointBatchIds.join(', ')}):${result?.count ?? 0} 个,颜色 #22c55e`
204
+ syncDetail()
205
+ }
206
+
207
+ /** 单条线路:颜色、线宽、透明度与线型都能逐条改 */
208
+ const updateSingleLine = () => {
209
+ const color = LINE_EDIT_COLORS[lineEditStep % LINE_EDIT_COLORS.length]
210
+ lineEditStep += 1
211
+ view?.updateLine(editLineId.value, { color, width: 4, opacity: 0.95, lineType: 'dashed' })
212
+ editText.value = `updateLine(${editLineId.value}):color=${color},width=4,lineType=dashed`
213
+ syncDetail()
214
+ }
215
+
216
+ /** 一批线路:entries 也支持 `{ id, ...字段 }` 逐条给不同补丁 */
217
+ const patchLineBatch = () => {
218
+ const result = view?.patchLines(lineBatchIds, {
219
+ color: '#14b8a6',
220
+ width: 2.8,
221
+ opacity: 0.9,
222
+ lineType: 'dotted'
223
+ })
224
+ editText.value = `patchLines(${lineBatchIds.join(', ')}):${result?.count ?? 0} 条,颜色 #14b8a6`
225
+ syncDetail()
226
+ }
227
+
228
+ /** 清掉单条 / 批量补丁,回到原始数据;规则、通道与相机保持不变 */
229
+ const resetElementStyles = () => {
230
+ view?.updateData({ points: pointsWithOwnStyle(), lines: dataset.lines, channels: dataset.channels })
231
+ view?.selectFeatures(null)
232
+ editText.value = '已恢复原始节点 / 线路数据(规则与交互样式仍在)'
233
+ syncDetail()
234
+ }
235
+
236
+ const selectSinglePoint = () => view?.selectFeatures(editPointId.value)
237
+ const selectMultiplePoints = () => view?.selectFeatures(dataset.points.slice(0, 2).map(point => point.id))
238
+ const selectSingleLine = () => view?.selectFeatures({ type: 'line', id: editLineId.value })
239
+ const selectMultipleLines = () => view?.selectFeatures(dataset.lines.slice(0, 2).map(line => line.id))
240
+
241
+ /** 程序化框选相邻节点:与 Shift 拖动走同一个 selectInBox,直接命中项用 boxSelectStyle */
242
+ const selectPointsInBox = () => {
243
+ const screens = dataset.points.slice(0, 3).map(point => view?.project(point.x, point.y)).filter(Boolean)
244
+ if (!screens.length) {
245
+ return
246
+ }
247
+ const margin = 28
248
+ const xs = screens.map(screen => screen[0])
249
+ const ys = screens.map(screen => screen[1])
250
+ const minX = Math.min(...xs) - margin
251
+ const minY = Math.min(...ys) - margin
252
+ view?.selectInBox({
253
+ x: minX,
254
+ y: minY,
255
+ width: Math.max(Math.max(...xs) + margin - minX, 2),
256
+ height: Math.max(Math.max(...ys) + margin - minY, 2)
257
+ }, { selectionKinds: ['point'] })
258
+ }
259
+
260
+ const clearSelection = () => view?.selectFeatures(null)
261
+
262
+ const screenText = screen => (Array.isArray(screen) ? `${screen[0].toFixed(1)}, ${screen[1].toFixed(1)}` : '—')
263
+
264
+ /**
265
+ * 详情查询分两层:getPoint / getLine / getChannel 查单个,listPoints / listLines / getChannels 查一批。
266
+ * 这里只用节点、线路与通道;分组详情不在这份演示里。
267
+ */
268
+ const syncDetail = () => {
269
+ if (!view) {
270
+ return
271
+ }
272
+ const point = view.getPoint(editPointId.value)
273
+ const points = view.listPoints({ indexes: dataset.points.slice(0, 3).map((_point, index) => index), withScreen: true, visibleOnly: false, limit: 3 })
274
+ const line = view.getLine(editLineId.value)
275
+ const lines = view.listLines({ indexes: dataset.lines.slice(0, 3).map((_line, index) => index), withScreen: true, visibleOnly: false, limit: 3 })
276
+ const channelId = dataset.channels[0]?.id
277
+ const channel = channelId === undefined || channelId === null ? null : view.getChannel(channelId)
278
+ const channelCount = view.getChannels().length
279
+ detailText.value = [
280
+ `节点单查:${point ? `${point.item.id} · index ${point.index} · screen ${screenText(point.screen)} · radius ${point.radius.toFixed(1)}` : 'null'}`,
281
+ `节点批量:${points.length} 条 · ${points.map(entry => entry.item.id).join(', ')}`,
282
+ `线路单查:${line ? `${line.item.id} · ${line.path.length} 个折点 · width ${line.width.toFixed(1)}` : 'null'}`,
283
+ `线路批量:${lines.length} 条 · ${lines.map(entry => entry.item.id).join(', ')}`,
284
+ `通道单查:${channel ? `${channel.item.id} · ${channel.lineIndexes.length} 条回线 · ${channel.skeleton.length} 个骨架点` : 'null'}`,
285
+ `通道全量:getChannels() ${channelCount} 条`
286
+ ].join(';')
287
+ }
128
288
 
129
289
  onMounted(() => {
130
290
  view = createGisView(container.value, buildOptions())
@@ -161,24 +321,75 @@ onBeforeUnmount(() => {
161
321
  <div class="dv-row">
162
322
  <label><input v-model="perPointStyle" type="checkbox" @change="applyPerPointStyle" /> 逐点样式(数据里自己写 color / size)</label>
163
323
  </div>
164
- <div class="dv-row">
165
- <label><input v-model="highlightOn" type="checkbox" @change="applyRules" /> 选中高亮</label>
166
- </div>
324
+ <div class="dv-row">
325
+ <label><input v-model="highlightOn" type="checkbox" @change="applyRules" /> 悬停 / 选中 / 多选 / 框选</label>
326
+ <label><input v-model="cascadeOn" data-demo="style-cascade" type="checkbox" @change="applyRules" /> 级联高亮</label>
327
+ </div>
328
+ <div class="dv-row">
329
+ <label><input v-model="ruleOverride" data-demo="style-rule-override" type="checkbox" @change="applyRules" /> 规则强制覆盖(override: true)</label>
330
+ </div>
167
331
  <p class="dv-readout">{{ ruleText }}</p>
168
332
  <p class="dv-readout">最近一次重排:{{ applyMs }}</p>
333
+ </div>
334
+ <div class="dv-group">
335
+ <h3>单个 / 多个要素样式</h3>
336
+ <div class="dv-row">
337
+ <label>节点
338
+ <select v-model="editPointId" data-demo="style-point-target">
339
+ <option v-for="point in pointChoices" :key="point.id" :value="point.id">{{ point.id }}</option>
340
+ </select>
341
+ </label>
342
+ <button type="button" class="dv-button dv-button--primary" data-demo="style-update-point" @click="updateSinglePoint">updatePoint</button>
343
+ </div>
344
+ <div class="dv-row">
345
+ <button type="button" class="dv-button" data-demo="style-patch-points" @click="patchPointBatch">patchPoints(前 3 个)</button>
346
+ </div>
347
+ <div class="dv-row">
348
+ <label>线路
349
+ <select v-model="editLineId" data-demo="style-line-target">
350
+ <option v-for="line in lineChoices" :key="line.id" :value="line.id">{{ line.id }}</option>
351
+ </select>
352
+ </label>
353
+ <button type="button" class="dv-button dv-button--primary" data-demo="style-update-line" @click="updateSingleLine">updateLine</button>
354
+ </div>
355
+ <div class="dv-row">
356
+ <button type="button" class="dv-button" data-demo="style-patch-lines" @click="patchLineBatch">patchLines(前 3 条)</button>
357
+ <button type="button" class="dv-button dv-button--danger" data-demo="style-reset" @click="resetElementStyles">恢复原始数据</button>
358
+ </div>
359
+ <p class="dv-readout" data-demo="style-edit-readout">{{ editText }}</p>
360
+ <p class="dv-hint">单条走 updatePoint / updateLine,批量走 patchPoints / patchLines;普通规则只补空位,override 规则会压过这里写进去的字段,但压不过下面这些交互状态。</p>
361
+ </div>
362
+ <div class="dv-group">
363
+ <h3>交互样式优先级</h3>
364
+ <div class="dv-row">
365
+ <button type="button" class="dv-button" data-demo="style-select-single" @click="selectSinglePoint">单选节点</button>
366
+ <button type="button" class="dv-button" data-demo="style-select-multi" @click="selectMultiplePoints">多选节点</button>
367
+ <button type="button" class="dv-button" data-demo="style-select-box" @click="selectPointsInBox">框选节点</button>
368
+ </div>
369
+ <div class="dv-row">
370
+ <button type="button" class="dv-button" data-demo="style-select-line" @click="selectSingleLine">单选线路</button>
371
+ <button type="button" class="dv-button" data-demo="style-select-lines" @click="selectMultipleLines">多选线路</button>
372
+ <button type="button" class="dv-button" data-demo="style-clear-selection" @click="clearSelection">清除选中</button>
373
+ </div>
169
374
  <p class="dv-readout">{{ selectText }}</p>
170
- </div>
171
- <div class="dv-group">
172
- <h3>优先级</h3>
173
- <p class="dv-hint">
174
- 规则命中之后,要素自己写的字段(逐点样式)压过规则;选中的高亮又压在两者之上。
175
- 勾上「逐点样式」看每 6 个一个金节点,点一下它看高亮。
176
- </p>
177
- </div>
178
- <div class="dv-group">
179
- <h3>试一下</h3>
180
- <p class="dv-hint">先把预设切到「按电压上色」再切「按负载率」,同一份数据两种读法;调尺寸 / 线宽只改样式,不动数据与相机。</p>
181
- </div>
375
+ <p class="dv-readout">{{ cascadeText }}</p>
376
+ <p class="dv-hint">
377
+ 状态颜色始终是第一条取值链:黄 = 单选、橙 = 多选、青 = 框选、青色级联;即使勾上 override 规则也会被这些颜色盖住。
378
+ 直接点击画布上的节点 / 线路可看悬停与级联,Ctrl 点击看多选,Shift 拖动看框选。
379
+ </p>
380
+ </div>
381
+ <div class="dv-group">
382
+ <h3>详情查询</h3>
383
+ <div class="dv-row">
384
+ <button type="button" class="dv-button dv-button--primary" data-demo="style-query" @click="syncDetail">查询详情</button>
385
+ </div>
386
+ <p class="dv-readout" data-demo="style-detail-readout">{{ detailText }}</p>
387
+ <p class="dv-hint">单条用 getPoint / getLine / getChannel;多条用 listPoints / listLines;全部通道用 getChannels。单条查不到返回 null,批量查询返回数组。</p>
388
+ </div>
389
+ <div class="dv-group">
390
+ <h3>试一下</h3>
391
+ <p class="dv-hint">先把预设切到「按电压上色」再切「按负载率」,同一份数据两种读法;同时勾「逐点样式」与「规则强制覆盖」,对比同一批金节点在两种优先级下的颜色 / 尺寸;再选一个节点或线路点 updatePoint / updateLine,最后直接点画布看交互色仍然最高。</p>
392
+ </div>
182
393
  </aside>
183
394
  </section>
184
395
  </template>