gis-view-webgl2 0.1.0 → 0.1.2
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 +40 -8
- package/dist/gis-view.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1036 -249
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +194 -0
- package/docs/GisView.md +9 -8
- package/docs/GisView/345/256/236/347/216/260/345/216/237/347/220/206.md +2 -2
- 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 +172 -0
- package/docs/api/index.html +545 -59
- package/docs/demos/BackendSyncDemo.vue +10 -8
- package/docs/demos/BasicDemo.vue +101 -101
- package/docs/demos/BatchAddDemo.vue +5 -5
- package/docs/demos/ChannelDemo.vue +617 -10
- package/docs/demos/ConnectDemo.vue +392 -0
- package/docs/demos/CopyMergeDemo.vue +980 -929
- package/docs/demos/DecorationDraftDemo.vue +1300 -0
- package/docs/demos/DemoShell.vue +569 -240
- package/docs/demos/DragDropDemo.vue +11 -11
- package/docs/demos/EffectsDemo.vue +6 -6
- package/docs/demos/EventDemo.vue +327 -327
- package/docs/demos/ExtensionDemo.vue +458 -0
- package/docs/demos/GisViewDemo.vue +61 -48
- package/docs/demos/GraphicDemo.vue +338 -0
- package/docs/demos/LabelDemo.vue +553 -497
- package/docs/demos/MinimapDemo.vue +358 -0
- package/docs/demos/PerfDemo.vue +29 -0
- package/docs/demos/SearchFilterDemo.vue +412 -0
- package/docs/demos/StyleDemo.vue +121 -121
- package/docs/demos/api-index.js +98 -77
- package/docs/demos/demos.js +159 -135
- package/docs/demos/shared/demoData.js +107 -107
- package/docs/demos/shared/demoStyle.css +249 -249
- package/docs/demos/shared/fakeBackend.js +93 -6
- 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 +28 -8
- package/docs/format/04-/351/200/232/351/201/223/344/270/216/346/240/207/347/255/276.md +5 -4
- package/docs/format/06-/345/257/274/345/205/245/345/257/274/345/207/272/344/270/216/346/240/207/345/207/206/346/226/207/346/241/243.md +8 -6
- 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 +6 -6
- package/docs/guide/sample-document.html +4 -4
- package/docs/index.html +180 -9
- package/docs/internals/11-/350/246/206/347/233/226/345/261/202/346/240/207/347/255/276/344/270/216/346/217/220/347/244/272.md +1 -1
- package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +3 -2
- 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 +16 -0
- package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +11 -0
- 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
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +18 -4
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +110 -25
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +13 -8
- 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 +111 -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 +47 -9
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +17 -15
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +61 -11
- 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 +74 -7
- package/docs/manual/11-/346/240/267/345/274/217/344/270/216/350/247/204/345/210/231.md +3 -2
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +260 -3
- package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +6 -5
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +27 -14
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +142 -49
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +31 -29
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +55 -39
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +189 -64
- 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 +4 -4
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +42 -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 +22 -6
- package/docs/playground/index.html +5 -5
- package/docs/steps/README.md +14 -10
- package/docs/steps/check-doc-truth.mjs +1 -1
- package/docs/steps/selftest-actions.html +10 -10
- package/docs/steps/selftest-data.html +96 -14
- package/docs/steps/selftest-decoration.html +577 -0
- package/docs/steps/selftest-demo.html +6 -5
- package/docs/steps/selftest-demos.html +2201 -1612
- package/docs/steps/selftest-docs.html +89 -16
- package/docs/steps/selftest-effects.html +5 -5
- package/docs/steps/selftest-features.html +757 -86
- package/docs/steps/selftest-full.html +583 -583
- package/docs/steps/selftest-labels.html +1034 -652
- package/docs/steps/selftest-perf.html +1899 -1844
- package/docs/steps/selftest-scale.html +117 -7
- package/docs/steps/selftest-select.html +24 -17
- package/docs/steps/step-10-full.js +1 -1
- package/docs//346/216/245/345/217/243/345/215/217/350/256/256.md +167 -17
- package/docs//346/225/260/346/215/256/346/240/274/345/274/217.md +1 -1
- package/package.json +43 -43
- package/docs/demos/HistoryDemo.vue +0 -230
- package/docs/demos/InterfaceDemo.vue +0 -309
- package/docs/demos/SampleDemo.vue +0 -283
|
@@ -717,7 +717,7 @@ const handleQualityChange = (state) => {
|
|
|
717
717
|
}
|
|
718
718
|
/**
|
|
719
719
|
* 动画效果(effects):线路上的跑动图标(ECharts 的 lines.effect 那一类)与节点上的扩散涟漪(effectScatter 那一类)。
|
|
720
|
-
* 两种写法:全局缺省(
|
|
720
|
+
* 两种写法:全局缺省(updateOptions({ effects }) / options.effects)与逐要素(数据里的 points[].effect / lines[].effect)。
|
|
721
721
|
* 三种「关」是四件事:全局一个都不配(只有逐条配的动)/ effects: false 一处全关(连逐条配的也关掉)/
|
|
722
722
|
* effects.line: false 只关全局那一份 / 逐条 effect: false 只关这一条。
|
|
723
723
|
* 读数抄的是 getEffects()(归一化之后的配置,enabled 是「这次要不要动」的运行时口径)与 getStats()
|
|
@@ -790,14 +790,14 @@ const applyEffects = () => {
|
|
|
790
790
|
if (!view) {
|
|
791
791
|
return
|
|
792
792
|
}
|
|
793
|
-
view.
|
|
793
|
+
view.updateOptions({ effects: {
|
|
794
794
|
line: effectLineOn.value ? buildEffectLine() : null,
|
|
795
795
|
point: effectPointOn.value ? buildEffectPoint() : null
|
|
796
|
-
})
|
|
796
|
+
} })
|
|
797
797
|
syncEffectsReadout()
|
|
798
798
|
// 这一帧的读数要等引擎画完才准(动点 / 涟漪数是上一帧统计出来的):下一帧再抄一遍
|
|
799
799
|
requestAnimationFrame(() => syncEffectsReadout())
|
|
800
|
-
lastEvent.value = `
|
|
800
|
+
lastEvent.value = `updateOptions({ effects }):线路 ${effectLineOn.value ? '开' : '关'} / 节点 ${effectPointOn.value ? '开' : '关'} —— 与 options.effects 是同一份配置,区别只是这份在运行时换`
|
|
801
801
|
}
|
|
802
802
|
/** 逐条覆盖:前几条要素把 effect 写进数据(数据副本带 effect,导出不受影响),其余照旧走全局那一份 */
|
|
803
803
|
const PER_ITEM_EFFECT_COUNT = 3
|
|
@@ -1135,7 +1135,7 @@ const PANEL_ITEMS = [
|
|
|
1135
1135
|
{ id: 'view-export', tab: 'view', label: '导出图片与选中样式', hint: 'exportImage / exportSelection 与状态样式' },
|
|
1136
1136
|
{ id: 'view-events', tab: 'view', label: '事件 · 选中', hint: 'onClick / onSelectChange 与 getSelectionData' },
|
|
1137
1137
|
{ id: 'style-heat', tab: 'style', label: '热力图', hint: '色带 / 密度场 / 光晕与作用对象' },
|
|
1138
|
-
{ id: 'style-effect', tab: 'style', label: '动画效果与预热', hint: '全局
|
|
1138
|
+
{ id: 'style-effect', tab: 'style', label: '动画效果与预热', hint: '全局 updateOptions({ effects }) / 逐条 data.effect 与 prebake 预热' },
|
|
1139
1139
|
{ id: 'style-line', tab: 'style', label: '线型、标记与尺寸', hint: 'lineStyle、虚线、拐点与缩放联动' },
|
|
1140
1140
|
{ id: 'style-rules', tab: 'style', label: '样式、符号与规则', hint: 'pointStyle / pointRules 与符号表' },
|
|
1141
1141
|
{ id: 'style-label', tab: 'style', label: '标签与提示', hint: '节点 / 线路标签与悬停提示' },
|
|
@@ -1365,7 +1365,7 @@ const runApiEval = () => {
|
|
|
1365
1365
|
const pluginReadout = ref('当前没有运行时插件(options.plugins 里的 focusSelectionPlugin 照常挂着)')
|
|
1366
1366
|
let pluginDispose = null
|
|
1367
1367
|
/**
|
|
1368
|
-
* 运行时挂一个插件:写法与 options.plugins 里的一项完全一致(`view => hook`)——
|
|
1368
|
+
* 运行时挂一个插件:写法与 options.plugins 里的一项完全一致(`(view, context) => hook`)——
|
|
1369
1369
|
* use() 立即执行、返回卸载函数。这个示例插件自己订阅 selectChange 记账,
|
|
1370
1370
|
* 解绑写在它自己的 destroy 里:面板的「卸载」与 view.destroy() 走的都是那一个 destroy。
|
|
1371
1371
|
*/
|
|
@@ -1439,8 +1439,8 @@ const canvasMenuRows = computed(() => {
|
|
|
1439
1439
|
{ id: 'fit', label: '适应视图(fitView)', enabled: true, reason: '' },
|
|
1440
1440
|
{ id: 'inspect', label: '读一下选中集(getSelectionData)', enabled: count > 0, reason: '选中集是空的' },
|
|
1441
1441
|
{ id: 'exportImage', label: '导出选中的图(exportSelection)', enabled: count > 0, reason: '选中集是空的' },
|
|
1442
|
-
{ id: 'exportData', label: '导出选中的数据(
|
|
1443
|
-
{ id: 'clear', label: '清空选中(
|
|
1442
|
+
{ id: 'exportData', label: '导出选中的数据(exportData · scope: selection)', enabled: count > 0, reason: '选中集是空的' },
|
|
1443
|
+
{ id: 'clear', label: '清空选中(selectFeatures(null) / deselect())', enabled: count > 0, reason: '本来就是空的' },
|
|
1444
1444
|
{ id: 'background', label: `切底图(当前:${backgroundModeLabel.value})`, enabled: true, reason: '' }
|
|
1445
1445
|
]
|
|
1446
1446
|
})
|
|
@@ -1473,13 +1473,13 @@ const runCanvasMenuAction = (row) => {
|
|
|
1473
1473
|
exportSelectionImage()
|
|
1474
1474
|
}
|
|
1475
1475
|
else if (row.id === 'exportData') {
|
|
1476
|
-
|
|
1476
|
+
exportSelectedRecords()
|
|
1477
1477
|
}
|
|
1478
1478
|
else if (row.id === 'clear') {
|
|
1479
|
-
view.
|
|
1479
|
+
view.selectFeatures(null)
|
|
1480
1480
|
syncSelectionFromView()
|
|
1481
1481
|
syncVisibleCount()
|
|
1482
|
-
lastEvent.value = '画布菜单:
|
|
1482
|
+
lastEvent.value = '画布菜单:selectFeatures(null) 清空选中'
|
|
1483
1483
|
}
|
|
1484
1484
|
else if (row.id === 'background') {
|
|
1485
1485
|
const order = BACKGROUND_MODES.map(item => item.value)
|
|
@@ -1549,7 +1549,7 @@ const confirmStationDialog = () => {
|
|
|
1549
1549
|
return
|
|
1550
1550
|
}
|
|
1551
1551
|
// 新场站直接选上:面板读数与后续编辑都跟着它(与「程序化新增线路」的新回线整批选中同一手感)
|
|
1552
|
-
view.
|
|
1552
|
+
view.selectFeatures(result.item.id)
|
|
1553
1553
|
syncSelectionFromView()
|
|
1554
1554
|
syncVisibleCount()
|
|
1555
1555
|
lastEvent.value = `画布菜单:空白处右键新增场站 → view.addPoint({ id: '${id}', name: '${name}', x: ${x}, y: ${y}, vn_kv: ${vn} }) · 落在 points[${result.index}],已选中`
|
|
@@ -1746,6 +1746,12 @@ const buildCopyOptions = () => ({
|
|
|
1746
1746
|
/** 载荷里的节点名:name / label / id 依次回落(复制与合并的读数都用它) */
|
|
1747
1747
|
const pointLabel = item => String(item?.name ?? item?.label ?? item?.id ?? '节点')
|
|
1748
1748
|
const mergeEnabled = ref(true)
|
|
1749
|
+
/** 合并触发时机:碰撞(半径相碰)/ 中心点进入目标节点区域 */
|
|
1750
|
+
const mergeTrigger = ref('collision')
|
|
1751
|
+
const MERGE_TRIGGERS = [
|
|
1752
|
+
{ value: 'collision', label: '碰撞(两个节点半径相碰)' },
|
|
1753
|
+
{ value: 'center', label: '中心进入目标节点区域' }
|
|
1754
|
+
]
|
|
1749
1755
|
/** 碰撞容差(CSS 像素):两个节点画在屏幕上的半径之和再加它 */
|
|
1750
1756
|
const mergeDistance = ref(0)
|
|
1751
1757
|
/**
|
|
@@ -1791,6 +1797,7 @@ const confirmMergeByApi = payload => new Promise((resolve) => {
|
|
|
1791
1797
|
const buildMergeOptions = () => {
|
|
1792
1798
|
const options = {
|
|
1793
1799
|
enabled: mergeEnabled.value,
|
|
1800
|
+
trigger: mergeTrigger.value,
|
|
1794
1801
|
distance: mergeDistance.value,
|
|
1795
1802
|
channelGroups: mergeChannelGroups.value
|
|
1796
1803
|
}
|
|
@@ -2697,10 +2704,10 @@ const applyChannelNodeVisibility = () => {
|
|
|
2697
2704
|
const anchors = activeChannelAnchorIds()
|
|
2698
2705
|
const hidden = new Set(anchors)
|
|
2699
2706
|
const hide = hideChannelNodes.value
|
|
2700
|
-
view.
|
|
2707
|
+
view.updateData({ points: view.getData().points.map(point => ({
|
|
2701
2708
|
...point,
|
|
2702
2709
|
visible: hide ? !hidden.has(String(point.id)) : true
|
|
2703
|
-
})))
|
|
2710
|
+
})) })
|
|
2704
2711
|
syncChannelLabel()
|
|
2705
2712
|
lastEvent.value = hide
|
|
2706
2713
|
? `通道两端的 ${anchors.length} 个节点已隐藏:两端必须有节点,${activeChannels().length} 条通道与回线都不再显示`
|
|
@@ -2782,7 +2789,7 @@ const handleContextMenu = (payload) => {
|
|
|
2782
2789
|
// 为 false 就先把命中的这个选上再弹面板(右键落在哪、菜单就针对谁)
|
|
2783
2790
|
if (payload.type === 'point') {
|
|
2784
2791
|
if (!payload.wasSelected && payload.item?.id !== undefined && payload.item?.id !== null) {
|
|
2785
|
-
viewRef.value?.
|
|
2792
|
+
viewRef.value?.selectFeatures(payload.item.id)
|
|
2786
2793
|
}
|
|
2787
2794
|
// 选完再取一次选中集读数:面板上的「把选中的 N 个并拢」按的是点下去那一刻的实际选中集
|
|
2788
2795
|
pointMenu.value = { ...payload, kept: payload.wasSelected === true, selection: viewRef.value?.getSelectionData() ?? payload.selection }
|
|
@@ -2886,10 +2893,10 @@ const handlePointMerge = (payload) => {
|
|
|
2886
2893
|
target: `目标 ${target}`,
|
|
2887
2894
|
detail: `并进目标之后:尺寸取双方最大、图标优先目标;撞在一起的通道${mergeChannelGroups.value === 'merge' ? '合成一个分组' : '各自保留分组'}`
|
|
2888
2895
|
}
|
|
2889
|
-
lastEvent.value =
|
|
2896
|
+
lastEvent.value = `节点合并:${source} 拖到了 ${target} 上(${payload.trigger === 'center' ? '中心进入目标区域' : '碰撞'})· 组件只给载荷与 confirm() / cancel(),合不合并由 demo 自己写的这个弹层裁决`
|
|
2890
2897
|
return
|
|
2891
2898
|
}
|
|
2892
|
-
lastEvent.value =
|
|
2899
|
+
lastEvent.value = `节点合并:${source} 拖到了 ${target} 上(${payload.trigger === 'center' ? '中心进入目标区域' : '碰撞'})· 裁决交给 options.merge.confirm(面板上切「直接合并 / 先调接口」)`
|
|
2893
2900
|
return
|
|
2894
2901
|
}
|
|
2895
2902
|
if (payload.phase === 'confirm') {
|
|
@@ -2990,7 +2997,7 @@ const exportSelectionImage = async () => {
|
|
|
2990
2997
|
lastEvent.value = `导出选中为图片(${imageKind}):${result.counts.points} 个节点${result.counts.lines ? ` + ${result.counts.lines} 条线路` : ''}已下载 · 出图时组件把别的要素临时藏起来、相机对到导出范围,出完自动还原`
|
|
2991
2998
|
}
|
|
2992
2999
|
/** 导出选中为数据:points / lines 是拷贝(改它不影响组件内部那一份),直接存成 JSON */
|
|
2993
|
-
const
|
|
3000
|
+
const exportSelectedRecords = async () => {
|
|
2994
3001
|
const view = viewRef.value
|
|
2995
3002
|
if (!view) {
|
|
2996
3003
|
return
|
|
@@ -3014,7 +3021,7 @@ const exportSelectionData = async () => {
|
|
|
3014
3021
|
/* -------------------- 导出数据:节点 / 线路 / 通道 / 拐点分组分开取 -------------------- */
|
|
3015
3022
|
/** 导出整份时的口径:四份分开(缺省)/ new.json / test.json —— 与面板上的下拉一一对应 */
|
|
3016
3023
|
const exportDataFormat = ref('view')
|
|
3017
|
-
/** 拐点要不要连没编辑过的分组一起列(
|
|
3024
|
+
/** 拐点要不要连没编辑过的分组一起列(exportData({ parts: ['waypoint'] }) 的 all) */
|
|
3018
3025
|
const exportDataAll = ref(false)
|
|
3019
3026
|
/**
|
|
3020
3027
|
* 导出分项(多选):选什么就导什么,默认场站 + 通道。
|
|
@@ -3045,11 +3052,11 @@ const exportPartsOption = () => (exportParts.value.length ? [...exportParts.valu
|
|
|
3045
3052
|
/** 分项导出:一次只取一份,节点 / 线路 / 通道 / 拐点各是各的口径 */
|
|
3046
3053
|
const exportPart = ref('channels')
|
|
3047
3054
|
const EXPORT_PARTS = [
|
|
3048
|
-
{ value: 'station', label: '节点 · station_data(new.json)', file: 'gis-station-data', run: (view, options) => view.
|
|
3049
|
-
{ value: 'points', label: '节点 · point_data(test.json)', file: 'gis-point-data', run: (view, options) => view.
|
|
3050
|
-
{ value: 'lines', label: '线路 · line_data(test.json)', file: 'gis-line-data', run: (view, options) => view.
|
|
3051
|
-
{ value: 'channels', label: '通道 · channel_data(new.json)', file: 'gis-channel-data', run: (view, options) => view.
|
|
3052
|
-
{ value: 'groups', label: '拐点 · parallel_groups(test.json)', file: 'gis-parallel-groups', run: (view, options) => view.
|
|
3055
|
+
{ value: 'station', label: '节点 · station_data(new.json)', file: 'gis-station-data', run: (view, options) => view.exportData({ ...options, format: 'new', parts: ['station'] }) },
|
|
3056
|
+
{ value: 'points', label: '节点 · point_data(test.json)', file: 'gis-point-data', run: (view, options) => view.exportData({ ...options, format: 'test', parts: ['station'] }) },
|
|
3057
|
+
{ value: 'lines', label: '线路 · line_data(test.json)', file: 'gis-line-data', run: (view, options) => view.exportData({ ...options, format: 'test', parts: ['line'] }) },
|
|
3058
|
+
{ value: 'channels', label: '通道 · channel_data(new.json)', file: 'gis-channel-data', run: (view, options) => view.exportData({ ...options, format: 'new', parts: ['channel'] }) },
|
|
3059
|
+
{ value: 'groups', label: '拐点 · parallel_groups(test.json)', file: 'gis-parallel-groups', run: (view, options) => view.exportData({ ...options, format: 'test', parts: ['waypoint'] }) }
|
|
3053
3060
|
]
|
|
3054
3061
|
const exportDataReadout = ref('导出数据:—')
|
|
3055
3062
|
const exportDataSample = ref('')
|
|
@@ -3086,7 +3093,7 @@ const exportDataToFile = () => {
|
|
|
3086
3093
|
refreshExportSample()
|
|
3087
3094
|
}
|
|
3088
3095
|
/**
|
|
3089
|
-
* 导出「选中的这一撮」的业务表数据(
|
|
3096
|
+
* 导出「选中的这一撮」的业务表数据(exportData · scope: selection):选中口径与「导出选中为数据」一样,
|
|
3090
3097
|
* 出的却是分项记录(场站 / 通道 / 线路 / 拐点),同样跟着上面的分项勾选走。
|
|
3091
3098
|
*/
|
|
3092
3099
|
const exportSelectionPartsToFile = () => {
|
|
@@ -3094,7 +3101,7 @@ const exportSelectionPartsToFile = () => {
|
|
|
3094
3101
|
if (!view) {
|
|
3095
3102
|
return
|
|
3096
3103
|
}
|
|
3097
|
-
const result = view.
|
|
3104
|
+
const result = view.exportData({ scope: 'selection', parts: exportPartsOption(), all: exportDataAll.value })
|
|
3098
3105
|
if (!result.ok) {
|
|
3099
3106
|
exportReadout.value = `导出选中分项:${result.reason}`
|
|
3100
3107
|
lastEvent.value = `导出选中分项没成功:${result.reason}`
|
|
@@ -3180,7 +3187,7 @@ const refreshDataShape = () => {
|
|
|
3180
3187
|
const labelChannelId = (memberChannel ?? channels[0])?.channelId ?? null
|
|
3181
3188
|
const labelChannelRow = labelChannelId === null || labelChannelId === undefined
|
|
3182
3189
|
? null
|
|
3183
|
-
: (view.
|
|
3190
|
+
: (view.exportData({ format: 'new', parts: ['channel'], channels: [labelChannelId], labels: true }).channel_data?.[0] ?? null)
|
|
3184
3191
|
// 面板上只摆带标签的那几条回线(一条都没有就摆前几条),完整结构走「打印完整结构到控制台」
|
|
3185
3192
|
const labelChannelLines = labelChannelRow
|
|
3186
3193
|
? (labelChannelRow.line_data.some(row => row.labels?.length)
|
|
@@ -3356,8 +3363,8 @@ const syncParallelGroups = () => {
|
|
|
3356
3363
|
: '新增拐点集合:—'
|
|
3357
3364
|
parallelGroupsJson.value = groups.length ? `${JSON.stringify(groups.slice(0, 2), null, 1)}${groups.length > 2 ? `\n… 共 ${groups.length} 组` : ''}` : ''
|
|
3358
3365
|
}
|
|
3359
|
-
/**
|
|
3360
|
-
const
|
|
3366
|
+
/** 复制拐点集合:结构就是 test.json 的 parallel_groups,可以直接回写业务数据 */
|
|
3367
|
+
const copyParallelGroupRecords = async () => {
|
|
3361
3368
|
if (!parallelGroups.value.length) {
|
|
3362
3369
|
lastEvent.value = '还没有新增拐点:Ctrl + 点击线路加点、拖动拐点之后,通道与线路会各成一组'
|
|
3363
3370
|
return
|
|
@@ -4287,7 +4294,7 @@ watch([shortcutBoxSelect, shortcutMultiSelect, shortcutAddWaypoint, shortcutDupl
|
|
|
4287
4294
|
})
|
|
4288
4295
|
// 复制的开关与落点偏移、合并的开关 / 容差 / 确认方式 / 通道分组:只改「怎么响应」,数据不动
|
|
4289
4296
|
watch([copyEnabled, copyOffsetMode], () => viewRef.value?.updateOptions({ copy: buildCopyOptions() }))
|
|
4290
|
-
watch([mergeEnabled, mergeDistance, mergeConfirmMode, mergeChannelGroups], () => viewRef.value?.updateOptions({ merge: buildMergeOptions() }))
|
|
4297
|
+
watch([mergeEnabled, mergeTrigger, mergeDistance, mergeConfirmMode, mergeChannelGroups], () => viewRef.value?.updateOptions({ merge: buildMergeOptions() }))
|
|
4291
4298
|
// 通道选中模式:只影响下一次点选怎么摊开,updateOptions 不用重排场景
|
|
4292
4299
|
watch(channelSelectMode, value => viewRef.value?.updateOptions({ channelSelectMode: value }))
|
|
4293
4300
|
// 选中抬层:改了立刻整份重排一次(这类选项只影响绘制顺序)
|
|
@@ -4896,7 +4903,7 @@ const applyPerPointSymbol = () => {
|
|
|
4896
4903
|
}
|
|
4897
4904
|
const { points } = view.getData()
|
|
4898
4905
|
const next = points.map((point, index) => perPointSymbol.value ? (index % 24 === 0 ? { ...point, symbol: PER_POINT_SYMBOL } : point) : point.symbol === undefined ? point : { ...point, symbol: undefined })
|
|
4899
|
-
view.
|
|
4906
|
+
view.updateData({ points: next })
|
|
4900
4907
|
lastEvent.value = perPointSymbol.value
|
|
4901
4908
|
? `已给 ${Math.ceil(next.length / 24)} 个节点单独配置符号(数据里写 symbol,优先级最高)`
|
|
4902
4909
|
: '已清掉逐点符号,节点回到规则表的样子'
|
|
@@ -4906,7 +4913,7 @@ const toggleDraggable = (value) => {
|
|
|
4906
4913
|
const view = viewRef.value
|
|
4907
4914
|
if (view) {
|
|
4908
4915
|
const { points } = view.getData()
|
|
4909
|
-
view.
|
|
4916
|
+
view.updateData({ points: points.map((point, index) => ({ ...point, draggable: index % 2 === 0 ? value : false })) })
|
|
4910
4917
|
}
|
|
4911
4918
|
}
|
|
4912
4919
|
/** 选中读数:交互造成的选中走 onSelect,通过接口设置的选中要自己同步一份 */
|
|
@@ -4937,7 +4944,7 @@ const syncSelectionFromView = () => {
|
|
|
4937
4944
|
}
|
|
4938
4945
|
}
|
|
4939
4946
|
const selectFirstLine = () => {
|
|
4940
|
-
viewRef.value?.
|
|
4947
|
+
viewRef.value?.selectFeatures('line-0')
|
|
4941
4948
|
syncSelectionFromView()
|
|
4942
4949
|
}
|
|
4943
4950
|
/** 选中回线最多的那条通道的中间回线:拐点属于通道,整条通道的拐点都会显示 */
|
|
@@ -4946,13 +4953,13 @@ const selectChannelLine = () => {
|
|
|
4946
4953
|
const channel = [...(view?.getChannels() ?? [])].sort((left, right) => right.lineIndexes.length - left.lineIndexes.length)[0]
|
|
4947
4954
|
const line = channel ? view?.getData().lines[channel.lineIndexes[Math.floor(channel.lineIndexes.length / 2)]] : undefined
|
|
4948
4955
|
if (view && line) {
|
|
4949
|
-
view.
|
|
4956
|
+
view.selectFeatures(String(line.id))
|
|
4950
4957
|
}
|
|
4951
4958
|
syncSelectionFromView()
|
|
4952
4959
|
}
|
|
4953
4960
|
/** Ctrl 点击多个节点等价于框选:多选之后拖动任意一个,整组一起移动 */
|
|
4954
4961
|
const selectSeveralNodes = () => {
|
|
4955
|
-
viewRef.value?.
|
|
4962
|
+
viewRef.value?.selectFeatures(['node-1', 'node-2', 'node-3', 'node-4'])
|
|
4956
4963
|
syncSelectionFromView()
|
|
4957
4964
|
}
|
|
4958
4965
|
/** 框选接口也可以直接调用:给个覆盖左上角 1/4 画布的矩形 */
|
|
@@ -5097,10 +5104,10 @@ const mergeSelectedChannel = () => {
|
|
|
5097
5104
|
return
|
|
5098
5105
|
}
|
|
5099
5106
|
const name = channelRenameText.value.trim()
|
|
5100
|
-
view.
|
|
5107
|
+
view.updateChannel(id, { name: name === '' ? null : name })
|
|
5101
5108
|
lastEvent.value = name === ''
|
|
5102
|
-
? `通道 ${String(id)} 摘掉名称(
|
|
5103
|
-
: `通道 ${String(id)} 改名「${name}」(
|
|
5109
|
+
? `通道 ${String(id)} 摘掉名称(updateChannel(id, { name: null }))`
|
|
5110
|
+
: `通道 ${String(id)} 改名「${name}」(updateChannel)—— 悬停提示的 {name} 与搜索都跟着变`
|
|
5104
5111
|
syncChannelLabel()
|
|
5105
5112
|
}
|
|
5106
5113
|
/** 加一条回线到通道:挑第一条还没挂通道的线路(认不出 / 已在别的通道里的由 addChannelLines 挡下并说明) */
|
|
@@ -5336,7 +5343,7 @@ const readSelectionByApi = () => {
|
|
|
5336
5343
|
* 节点右键面板:组件本身不弹任何菜单,右键载荷里就已经给全了 ——
|
|
5337
5344
|
* wasSelected(命中前它在不在选中集里)+ selection(整份选中集读数)。
|
|
5338
5345
|
* 这里的规矩是常见的那一套:右键已经选中的节点保留整份选中集,右键没选中的节点先把它选上再弹面板;
|
|
5339
|
-
* 面板上的按钮各走一个公开方法(focusOn / copyPoint / mergeSelectedPoints / removePoints /
|
|
5346
|
+
* 面板上的按钮各走一个公开方法(focusOn / copyPoint / mergeSelectedPoints / removePoints / selectFeatures(null))。
|
|
5340
5347
|
*/
|
|
5341
5348
|
const pointMenu = ref(null)
|
|
5342
5349
|
const closePointMenu = () => {
|
|
@@ -5413,7 +5420,7 @@ const pointMenuRows = computed(() => {
|
|
|
5413
5420
|
{ id: 'merge', label: `把选中的 ${count} 个并拢(mergeSelectedPoints)`, enabled: count >= 2, reason: '选中两个以上节点才谈得上合并' },
|
|
5414
5421
|
{ id: 'remove', label: '删掉这个节点(removePoints)', enabled: true, reason: '' },
|
|
5415
5422
|
{ id: 'deselect', label: '取消它的选中(deselect)', enabled: true, reason: '' },
|
|
5416
|
-
{ id: 'clear', label: '清空全部选中(
|
|
5423
|
+
{ id: 'clear', label: '清空全部选中(selectFeatures(null))', enabled: count > 0, reason: '本来就是空的' }
|
|
5417
5424
|
]
|
|
5418
5425
|
})
|
|
5419
5426
|
const runPointMenuAction = (row) => {
|
|
@@ -5445,7 +5452,7 @@ const runPointMenuAction = (row) => {
|
|
|
5445
5452
|
lastEvent.value = `右键面板:deselect(${JSON.stringify(ref)}) —— ${formatFeature(payload)} 的选中已取消,其余选中不受影响`
|
|
5446
5453
|
}
|
|
5447
5454
|
else if (row.id === 'clear') {
|
|
5448
|
-
view.
|
|
5455
|
+
view.selectFeatures(null)
|
|
5449
5456
|
lastEvent.value = '右键面板:清空选中'
|
|
5450
5457
|
}
|
|
5451
5458
|
closePointMenu()
|
|
@@ -6249,6 +6256,12 @@ const deselectFirstSelected = () => {
|
|
|
6249
6256
|
<span class="demo-control-hint">{{ copyReadout }}</span>
|
|
6250
6257
|
<label><input v-model="copyRollback" type="checkbox" /> 复制后模拟「接口失败」:立刻 payload.revert() 回滚</label>
|
|
6251
6258
|
<label><input v-model="mergeEnabled" type="checkbox" /> 可合并节点(拖动一个节点撞上另一个)</label>
|
|
6259
|
+
<label class="demo-slider">
|
|
6260
|
+
合并触发时机
|
|
6261
|
+
<select v-model="mergeTrigger">
|
|
6262
|
+
<option v-for="item in MERGE_TRIGGERS" :key="item.value" :value="item.value">{{ item.label }}</option>
|
|
6263
|
+
</select>
|
|
6264
|
+
</label>
|
|
6252
6265
|
<label class="demo-slider">
|
|
6253
6266
|
合并容差
|
|
6254
6267
|
<input v-model.number="mergeDistance" type="range" min="0" max="40" step="1" />
|
|
@@ -6311,7 +6324,7 @@ const deselectFirstSelected = () => {
|
|
|
6311
6324
|
<span class="demo-control-hint">导出的是当前这一帧:格式与背景对「整幅」和「选中」两边都管用</span>
|
|
6312
6325
|
<label><input v-model="exportWithLines" type="checkbox" /> 导出选中时携带线路(两端都在选中集里的)</label>
|
|
6313
6326
|
<button type="button" @click="exportSelectionImage()">导出选中为图片</button>
|
|
6314
|
-
<button type="button" @click="
|
|
6327
|
+
<button type="button" @click="exportSelectedRecords()">导出选中为数据</button>
|
|
6315
6328
|
<span class="demo-control-hint">{{ exportReadout }}</span>
|
|
6316
6329
|
<label class="demo-slider">
|
|
6317
6330
|
选中点样式
|
|
@@ -6653,7 +6666,7 @@ const deselectFirstSelected = () => {
|
|
|
6653
6666
|
</div>
|
|
6654
6667
|
<div class="demo-controls demo-controls--shape">
|
|
6655
6668
|
<label class="demo-slider">
|
|
6656
|
-
|
|
6669
|
+
单份
|
|
6657
6670
|
<select v-model="exportPart" title="一次只取一份:节点 / 线路 / 通道 / 拐点各是各的口径,互不混在一起" @change="refreshExportSample()">
|
|
6658
6671
|
<option v-for="item in EXPORT_PARTS" :key="item.value" :value="item.value">{{ item.label }}</option>
|
|
6659
6672
|
</select>
|
|
@@ -6661,7 +6674,7 @@ const deselectFirstSelected = () => {
|
|
|
6661
6674
|
<button type="button" @click="exportPartToFile()">导出这一份(下载 JSON)</button>
|
|
6662
6675
|
</div>
|
|
6663
6676
|
<div class="demo-controls demo-controls--shape">
|
|
6664
|
-
<span class="demo-control-hint">分项多选:选什么就导什么,默认场站 + 通道;勾了通道或线路会自动带上拐点(通道的路由与线路的折线都靠拐点说话),一个都不勾就是四份全要。节点与通道走 new.json 那套列名(station_data / channel_data:通道的 line_data 里是回线、coords 是整条通道的路由),线路与拐点走 test.json 那套(line_data / parallel_groups:拐点只记在集合里,不散进每条回线);业务列(max_loading_rate 这类)原样透传,warnings 会说哪个 id
|
|
6677
|
+
<span class="demo-control-hint">分项多选:选什么就导什么,默认场站 + 通道;勾了通道或线路会自动带上拐点(通道的路由与线路的折线都靠拐点说话),一个都不勾就是四份全要。节点与通道走 new.json 那套列名(station_data / channel_data:通道的 line_data 里是回线、coords 是整条通道的路由),线路与拐点走 test.json 那套(line_data / parallel_groups:拐点只记在集合里,不散进每条回线);业务列(max_loading_rate 这类)原样透传,warnings 会说哪个 id 没找到。「导出选中数据的这几份」统一走 exportData({ scope: 'selection' }) —— 选中口径与「导出选中为数据」一样(框选 / 多选出来的那批节点,线路取两端都在里面的),出的却是分项业务表。下面这一段是「只取头两个节点 / 两条线 / 一条通道」的样例</span>
|
|
6665
6678
|
<pre class="demo-groups">{{ exportDataSample || '点「刷新样例」看当前口径下的导出结构' }}</pre>
|
|
6666
6679
|
</div>
|
|
6667
6680
|
</div>
|
|
@@ -6719,7 +6732,7 @@ const deselectFirstSelected = () => {
|
|
|
6719
6732
|
<button type="button" @click="mergeSelectedChannel">合并分组</button>
|
|
6720
6733
|
<button type="button" @click="separateSelectedChannelGroups">加拐点分开分组</button>
|
|
6721
6734
|
<button type="button" @click="clearSeparatedChannelGroups">去掉分开的拐点</button>
|
|
6722
|
-
<input v-model="channelRenameText" type="text" placeholder="通道名称(留空 = 摘掉)" title="
|
|
6735
|
+
<input v-model="channelRenameText" type="text" placeholder="通道名称(留空 = 摘掉)" title="updateChannel({ name }):悬停提示的 {name}、标签模板与搜索都取它" />
|
|
6723
6736
|
<button type="button" @click="renameSelectedChannel">改名</button>
|
|
6724
6737
|
<button type="button" @click="addLineToSelectedChannel">加一条回线</button>
|
|
6725
6738
|
<button type="button" @click="removeLineFromSelectedChannel">摘一条回线</button>
|
|
@@ -7116,7 +7129,7 @@ const deselectFirstSelected = () => {
|
|
|
7116
7129
|
<button type="button" @click="selectQuarterBox">选中左上 1/4</button>
|
|
7117
7130
|
<button type="button" @click="addWaypointToFirstLine">给首条线路加点</button>
|
|
7118
7131
|
<button type="button" @click="addWaypointToChannel">给通道加点</button>
|
|
7119
|
-
<button type="button" @click="
|
|
7132
|
+
<button type="button" @click="copyParallelGroupRecords">复制拐点集合</button>
|
|
7120
7133
|
<button type="button" @click="deselectFirstSelected">取消选中一个(deselect)</button>
|
|
7121
7134
|
<button type="button" @click="clearSelection">清空选中(deselect)</button>
|
|
7122
7135
|
<span class="demo-control-hint">{{ selectedLabel }}</span>
|
|
@@ -7145,7 +7158,7 @@ const deselectFirstSelected = () => {
|
|
|
7145
7158
|
<div class="demo-controls">
|
|
7146
7159
|
<label class="demo-slider"><input v-model="effectLineOn" type="checkbox" @change="applyEffects" />线路动点</label>
|
|
7147
7160
|
<label class="demo-slider"><input v-model="effectPointOn" type="checkbox" @change="applyEffects" />节点涟漪</label>
|
|
7148
|
-
<button type="button" @click="applyEffects">下发全局配置(
|
|
7161
|
+
<button type="button" @click="applyEffects">下发全局配置(updateOptions)</button>
|
|
7149
7162
|
<span class="demo-control-hint">动画效果:{{ effectText }}</span>
|
|
7150
7163
|
</div>
|
|
7151
7164
|
<div class="demo-controls">
|
|
@@ -7171,7 +7184,7 @@ const deselectFirstSelected = () => {
|
|
|
7171
7184
|
<span class="demo-control-hint">预热(prebake):{{ prebakeText }}</span>
|
|
7172
7185
|
</div>
|
|
7173
7186
|
<div class="demo-controls">
|
|
7174
|
-
<details class="demo-hint-more"><summary>说明</summary><span class="demo-control-hint">效果分两层:全局缺省(<code>
|
|
7187
|
+
<details class="demo-hint-more"><summary>说明</summary><span class="demo-control-hint">效果分两层:全局缺省(<code>updateOptions({ effects })</code> 或 <code>options.effects</code>)与逐要素(数据里的 <code>points[].effect</code> / <code>lines[].effect</code>)。上面那三个「逐条覆盖」按钮演示的就是逐要素的三种写法:写一份自己的、<code>true</code> 交回全局、<code>false</code> 这几条不动 —— 只在数据里逐条配也会动,全局一个都不配也行。三层「关」是四件事:什么都不配 / <code>effects: false</code> 一处关掉(谁都别动)/ <code>effects.line: false</code> 只关全局那一份 / 逐条 <code>effect: false</code>。线路的图标按弧长跑(拐点上连着走、朝向跟着所在那一段),节点涟漪每帧只改一个 <code>u_time</code>,两者都只铺视口里的、按「画多少」付费;页签切到后台或系统要求减少动态效果时直接停帧(<code>respectReducedMotion</code> 缺省尊重它,想照跑就关掉)。预热不改变画面:它把「会画到的那些贴图」在首帧之前烤好,省掉第一次画到时的解码 + 光栅化,稳态帧与它无关,再烤一遍命中的是缓存。收组合有两种写法:<strong>按实际组合(精确)</strong>走 <code>getSymbolCombos()</code>,把这份数据真正会用到的组合收出来(真实数据实测 <strong>26 条</strong>:照着它配预热零张新画布,逐条命中);<strong>按规则表全排列(粗)</strong>是笛卡尔积,颜色与尺寸由同一个字段决定时(都跟着电压走那种)会把用不到的组合也烤一遍 —— 同一份数据收出 <strong>1741 条</strong>、多烤 21 倍画布,无头下首帧从 29 ms 掉到 963 ms。<strong>「提前预知」的正确用法是清单要准,不是越多越好。</strong></span></details>
|
|
7175
7188
|
</div>
|
|
7176
7189
|
</div>
|
|
7177
7190
|
<div v-show="activePanel === 'ops-quality'" class="demo-panel-body">
|