gis-view-webgl2 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -7
- package/dist/gis-view.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +563 -61
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +182 -0
- package/docs/GisView.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 +165 -0
- package/docs/api/index.html +1 -1
- package/docs/demos/ApiCoverageDemo.vue +644 -0
- package/docs/demos/ChannelDemo.vue +580 -18
- package/docs/demos/ClusterDemo.vue +251 -0
- package/docs/demos/CopyMergeDemo.vue +60 -10
- package/docs/demos/EventDemo.vue +3 -3
- package/docs/demos/GisViewDemo.vue +64 -8809
- package/docs/demos/LabelDemo.vue +67 -11
- package/docs/demos/api-index.js +58 -41
- package/docs/demos/demos.js +33 -16
- package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +28 -0
- package/docs/demos/gis-view-demo/GisViewDemoDialogs.vue +82 -0
- package/docs/demos/gis-view-demo/GisViewDemoFooter.vue +16 -0
- package/docs/demos/gis-view-demo/GisViewDemoHeading.vue +16 -0
- package/docs/demos/gis-view-demo/GisViewDemoPanelStack.vue +71 -0
- package/docs/demos/gis-view-demo/GisViewDemoStage.vue +219 -0
- package/docs/demos/gis-view-demo/demo.css +1230 -0
- package/docs/demos/gis-view-demo/demoContext.js +12 -0
- package/docs/demos/gis-view-demo/panels/DataPanels.vue +303 -0
- package/docs/demos/gis-view-demo/panels/OpsPanels.vue +174 -0
- package/docs/demos/gis-view-demo/panels/StylePanels.vue +561 -0
- package/docs/demos/gis-view-demo/panels/ViewPanels.vue +396 -0
- package/docs/demos/gis-view-demo/useGisViewDemo.js +6188 -0
- package/docs/format/03-/346/213/220/347/202/271/350/243/205/351/245/260/344/270/216/346/240/267/345/274/217.md +4 -2
- 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/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
- 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
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +10 -4
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +105 -16
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +8 -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 +100 -0
- package/docs/manual/07-/346/214/207/351/222/210/344/270/216/350/246/201/347/264/240/347/274/226/350/276/221.md +78 -3
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +31 -5
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +64 -10
- package/docs/manual/10-/351/200/232/351/201/223/347/274/226/350/276/221/344/270/216/346/226/260/345/242/236.md +65 -8
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +54 -1
- package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +3 -0
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +35 -19
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +5 -0
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +18 -6
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +47 -22
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +80 -13
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +27 -0
- 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
- package/docs/steps/README.md +18 -10
- package/docs/steps/selftest-actions.html +3 -5
- package/docs/steps/selftest-demo.html +27 -18
- package/docs/steps/selftest-demos.html +578 -53
- package/docs/steps/selftest-docs.html +7 -6
- package/docs/steps/selftest-effects.html +200 -0
- package/docs/steps/selftest-features.html +725 -12
- package/docs/steps/selftest-labels.html +323 -4
- package/docs/steps/selftest-perf.html +293 -20
- package/docs/steps/selftest-scale.html +52 -29
- package/docs/steps/step-10-full.js +1 -1
- package/docs/steps/step-12-events.js +1 -1
- package/docs//346/216/245/345/217/243/345/215/217/350/256/256.md +17 -11
- package/package.json +2 -2
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/**
|
|
3
|
+
* 点聚合演示:同一份合成数据在多簇中心附近铺开,面板上调开关 / 半径 / 最少成员数 / 展开缩放。
|
|
4
|
+
*
|
|
5
|
+
* 聚合只改「展示什么、能点到什么」:原始 points、getData()、listPoints() 与业务导出都不变;
|
|
6
|
+
* 聚合圆替代密集成员参与绘制、拾取、快照与 SVG / PNG 导出。点中聚合圆只发 clusterClick,
|
|
7
|
+
* 不会同时发普通 click,也不会改动选中集。
|
|
8
|
+
*/
|
|
9
|
+
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
10
|
+
import { createGisView } from '../../src/index.js'
|
|
11
|
+
import { DEMO_CENTER, DEMO_RADIUS, createRandom } from './shared/demoData.js'
|
|
12
|
+
import './shared/demoStyle.css'
|
|
13
|
+
|
|
14
|
+
const emit = defineEmits(['notice'])
|
|
15
|
+
const container = ref(null)
|
|
16
|
+
let view = null
|
|
17
|
+
let statsDrawnAt = Number.NEGATIVE_INFINITY
|
|
18
|
+
|
|
19
|
+
const DATA_COUNTS = [2000, 12000, 60000, 100000]
|
|
20
|
+
const pointCountChoice = ref(12000)
|
|
21
|
+
const clusterOn = ref(true)
|
|
22
|
+
const radius = ref(60)
|
|
23
|
+
const minPoints = ref(2)
|
|
24
|
+
const maxZoom = ref(0)
|
|
25
|
+
const statsText = ref('—')
|
|
26
|
+
const clusterText = ref('—')
|
|
27
|
+
const eventText = ref('等待点击聚合圆')
|
|
28
|
+
const normalClicks = ref(0)
|
|
29
|
+
const clusterClicks = ref(0)
|
|
30
|
+
const lastCluster = ref(null)
|
|
31
|
+
const firstClusterClient = ref(null)
|
|
32
|
+
let dataset = { points: [] }
|
|
33
|
+
|
|
34
|
+
/** 多簇数据:每簇一个中心,正态近似(四个随机数求和)让簇内密度自然衰减。 */
|
|
35
|
+
const buildDataset = (count) => {
|
|
36
|
+
const random = createRandom(20261021)
|
|
37
|
+
const points = []
|
|
38
|
+
const centers = [
|
|
39
|
+
[-0.56, 0.34, 0.16],
|
|
40
|
+
[0.08, 0.56, 0.13],
|
|
41
|
+
[0.62, 0.22, 0.18],
|
|
42
|
+
[-0.32, -0.42, 0.15],
|
|
43
|
+
[0.48, -0.5, 0.14],
|
|
44
|
+
[0.02, -0.08, 0.1]
|
|
45
|
+
]
|
|
46
|
+
for (let index = 0; index < count; index += 1) {
|
|
47
|
+
const center = centers[index % centers.length]
|
|
48
|
+
const common = index < count * 0.08
|
|
49
|
+
const spread = common ? 1.6 : 0.68
|
|
50
|
+
const nx = (random() + random() + random() + random() - 2) * spread
|
|
51
|
+
const ny = (random() + random() + random() + random() - 2) * spread
|
|
52
|
+
points.push({
|
|
53
|
+
id: `cluster-point-${index}`,
|
|
54
|
+
x: Number((DEMO_CENTER[0] + (center[0] + nx * center[2]) * DEMO_RADIUS).toFixed(6)),
|
|
55
|
+
y: Number((DEMO_CENTER[1] + (center[1] + ny * center[2]) * DEMO_RADIUS).toFixed(6)),
|
|
56
|
+
name: `站点 ${index + 1}`,
|
|
57
|
+
zone_name: `分区 ${(index % centers.length) + 1}`,
|
|
58
|
+
vn_kv: [1050, 525, 230, 15.7][index % 4],
|
|
59
|
+
max_loading_rate: Number((0.35 + random() * 1.1).toFixed(3))
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
return { points }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const clusterOptions = () => clusterOn.value
|
|
66
|
+
? { enabled: true, radius: radius.value, minPoints: minPoints.value, maxZoom: maxZoom.value }
|
|
67
|
+
: false
|
|
68
|
+
|
|
69
|
+
const buildOptions = () => ({
|
|
70
|
+
points: dataset.points,
|
|
71
|
+
pointStyle: { size: 4, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1 },
|
|
72
|
+
showGrid: true,
|
|
73
|
+
gridColor: 'rgba(126, 168, 214, 0.18)',
|
|
74
|
+
fitPadding: 42,
|
|
75
|
+
denseMode: 'off',
|
|
76
|
+
features: 'view',
|
|
77
|
+
pointCluster: clusterOptions(),
|
|
78
|
+
onReady: () => {
|
|
79
|
+
emit('notice', `点聚合演示已加载 ${dataset.points.length.toLocaleString()} 个点`)
|
|
80
|
+
queueMicrotask(syncClusters)
|
|
81
|
+
},
|
|
82
|
+
onClick: () => {
|
|
83
|
+
normalClicks.value += 1
|
|
84
|
+
eventText.value = '普通 click:聚合圆的点击不会再走到这里'
|
|
85
|
+
},
|
|
86
|
+
onClusterClick: payload => {
|
|
87
|
+
clusterClicks.value += 1
|
|
88
|
+
lastCluster.value = {
|
|
89
|
+
id: payload.id,
|
|
90
|
+
count: payload.count,
|
|
91
|
+
pointIds: [...payload.pointIds],
|
|
92
|
+
bounds: { ...payload.bounds },
|
|
93
|
+
coordinate: [...payload.coordinate]
|
|
94
|
+
}
|
|
95
|
+
const ids = payload.pointIds.slice(0, 6).join('、') + (payload.pointIds.length > 6 ? '…' : '')
|
|
96
|
+
eventText.value = `clusterClick:${payload.count} 点 · ${ids} · 包围盒 ${payload.bounds.minX.toFixed(3)}, ${payload.bounds.minY.toFixed(3)} ~ ${payload.bounds.maxX.toFixed(3)}, ${payload.bounds.maxY.toFixed(3)}`
|
|
97
|
+
},
|
|
98
|
+
onStats: next => {
|
|
99
|
+
const now = performance.now()
|
|
100
|
+
if (now - statsDrawnAt < 250) {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
statsDrawnAt = now
|
|
104
|
+
statsText.value = `${next.pointCount.toLocaleString()} 原始点 · ${next.clusterCount.toLocaleString()} 个聚合圆(${next.clusterPointCount.toLocaleString()} 点)· 展示 ${(next.pointCount - next.clusterPointCount + next.clusterCount).toLocaleString()} 项 · 本帧 ${next.frameMs.toFixed(2)} ms`
|
|
105
|
+
clusterText.value = `聚合耗时 ${next.clusterMs.toFixed(2)} ms · ${view?.getPointCluster()?.active ? '当前缩放仍在聚合' : '当前缩放已展开原始点'}`
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
/** getClusters() 会按当前数据顺序展开单成员;这里只取第一个多成员组,给自测定位点击位置。 */
|
|
110
|
+
const syncClusters = async (options = {}) => {
|
|
111
|
+
if (!view) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
if (options.render !== false) {
|
|
115
|
+
await view.whenRendered()
|
|
116
|
+
}
|
|
117
|
+
const clusters = view.getClusters().filter(item => item.count > 1)
|
|
118
|
+
const first = clusters[0]
|
|
119
|
+
firstClusterClient.value = first ? view.screenToClient(first.screen[0], first.screen[1]) : null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const applyCluster = async () => {
|
|
123
|
+
if (!view) {
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
view.updateOptions({ pointCluster: clusterOptions() })
|
|
127
|
+
await syncClusters()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const applyData = async () => {
|
|
131
|
+
if (!view) {
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
dataset = buildDataset(pointCountChoice.value)
|
|
135
|
+
view.updateData(dataset.points, [])
|
|
136
|
+
view.fitView(42)
|
|
137
|
+
lastCluster.value = null
|
|
138
|
+
await syncClusters()
|
|
139
|
+
emit('notice', `已换成 ${dataset.points.length.toLocaleString()} 个点`)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const fitAll = () => {
|
|
143
|
+
view?.fitView(42)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const zoomLastCluster = () => {
|
|
147
|
+
if (lastCluster.value?.bounds) {
|
|
148
|
+
view?.zoomToBounds(lastCluster.value.bounds, 54)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const countText = computed(() => `${normalClicks.value} 次普通点击 / ${clusterClicks.value} 次聚合点击`)
|
|
153
|
+
|
|
154
|
+
onMounted(() => {
|
|
155
|
+
dataset = buildDataset(pointCountChoice.value)
|
|
156
|
+
view = createGisView(container.value, buildOptions())
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
onBeforeUnmount(() => {
|
|
160
|
+
view?.destroy()
|
|
161
|
+
view = null
|
|
162
|
+
})
|
|
163
|
+
</script>
|
|
164
|
+
|
|
165
|
+
<template>
|
|
166
|
+
<section class='dv-demo'>
|
|
167
|
+
<div class='dv-stage' data-demo='cluster-stage'>
|
|
168
|
+
<div ref='container' class='dv-canvas' data-demo='cluster-canvas' />
|
|
169
|
+
<div class='dv-toolbar'>
|
|
170
|
+
<button type='button' data-demo='cluster-fit' @click='fitAll'>适应全部</button>
|
|
171
|
+
<button type='button' data-demo='cluster-zoom' :disabled='!lastCluster' @click='zoomLastCluster'>放大到最近聚合</button>
|
|
172
|
+
</div>
|
|
173
|
+
<span
|
|
174
|
+
class='cl-anchor'
|
|
175
|
+
data-demo='cluster-first-client'
|
|
176
|
+
:data-client-x='firstClusterClient ? firstClusterClient[0] : undefined'
|
|
177
|
+
:data-client-y='firstClusterClient ? firstClusterClient[1] : undefined'
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
<aside class='dv-panel'>
|
|
181
|
+
<div class='dv-group'>
|
|
182
|
+
<h3>聚合配置</h3>
|
|
183
|
+
<div class='dv-row'>
|
|
184
|
+
<label><input v-model='clusterOn' data-demo='cluster-toggle' type='checkbox' @change='applyCluster' /> 开启聚合</label>
|
|
185
|
+
</div>
|
|
186
|
+
<div class='dv-row'>
|
|
187
|
+
<label>半径
|
|
188
|
+
<input v-model.number='radius' data-demo='cluster-radius' type='range' min='16' max='180' step='4' @change='applyCluster' />
|
|
189
|
+
<span class='cl-value'>{{ radius }}px</span>
|
|
190
|
+
</label>
|
|
191
|
+
</div>
|
|
192
|
+
<div class='dv-row'>
|
|
193
|
+
<label>最少点数
|
|
194
|
+
<select v-model.number='minPoints' data-demo='cluster-min-points' @change='applyCluster'>
|
|
195
|
+
<option :value='2'>2</option>
|
|
196
|
+
<option :value='3'>3</option>
|
|
197
|
+
<option :value='5'>5</option>
|
|
198
|
+
<option :value='8'>8</option>
|
|
199
|
+
<option :value='12'>12</option>
|
|
200
|
+
</select>
|
|
201
|
+
</label>
|
|
202
|
+
<label>展开缩放
|
|
203
|
+
<select v-model.number='maxZoom' data-demo='cluster-max-zoom' @change='applyCluster'>
|
|
204
|
+
<option :value='0'>不限</option>
|
|
205
|
+
<option :value='1.5'>1.5×</option>
|
|
206
|
+
<option :value='2'>2×</option>
|
|
207
|
+
<option :value='4'>4×</option>
|
|
208
|
+
<option :value='8'>8×</option>
|
|
209
|
+
</select>
|
|
210
|
+
</label>
|
|
211
|
+
</div>
|
|
212
|
+
<p class='dv-hint'>半径是屏幕像素;maxZoom 是相机比例相对基准比例的倍数,超过后展开原始点。聚合期间发散初始化暂停,缩放 / 数据 / 选中变化才重算。</p>
|
|
213
|
+
</div>
|
|
214
|
+
<div class='dv-group'>
|
|
215
|
+
<h3>数据规模</h3>
|
|
216
|
+
<div class='dv-row'>
|
|
217
|
+
<label>点数
|
|
218
|
+
<select v-model.number='pointCountChoice' data-demo='cluster-count'>
|
|
219
|
+
<option v-for='count in DATA_COUNTS' :key='count' :value='count'>{{ count.toLocaleString() }}</option>
|
|
220
|
+
</select>
|
|
221
|
+
</label>
|
|
222
|
+
<button type='button' class='dv-button' data-demo='cluster-rebuild' @click='applyData'>重建</button>
|
|
223
|
+
</div>
|
|
224
|
+
<p class='dv-hint'>十万点仍按整份网格 O(n) 建组;平移不重算,拖动 / 选中中的点强制保持单成员。</p>
|
|
225
|
+
</div>
|
|
226
|
+
<div class='dv-group'>
|
|
227
|
+
<h3>读数</h3>
|
|
228
|
+
<p class='dv-readout' data-demo='cluster-stats'>{{ statsText }}</p>
|
|
229
|
+
<p class='dv-readout' data-demo='cluster-summary'>{{ clusterText }}</p>
|
|
230
|
+
<p class='dv-readout' data-demo='cluster-click-count'>{{ countText }}</p>
|
|
231
|
+
<p class='dv-readout' data-demo='cluster-event'>{{ eventText }}</p>
|
|
232
|
+
<p class='dv-hint'>点击聚合圆应只增加「聚合点击」计数;普通 click 不会被聚合圆触发,选中集也保持不变。</p>
|
|
233
|
+
</div>
|
|
234
|
+
</aside>
|
|
235
|
+
</section>
|
|
236
|
+
</template>
|
|
237
|
+
|
|
238
|
+
<style scoped>
|
|
239
|
+
.cl-anchor {
|
|
240
|
+
position: absolute;
|
|
241
|
+
width: 1px;
|
|
242
|
+
height: 1px;
|
|
243
|
+
overflow: hidden;
|
|
244
|
+
pointer-events: none;
|
|
245
|
+
}
|
|
246
|
+
.cl-value {
|
|
247
|
+
min-width: 38px;
|
|
248
|
+
color: #526b8d;
|
|
249
|
+
font-size: 11px;
|
|
250
|
+
}
|
|
251
|
+
</style>
|
|
@@ -25,6 +25,7 @@ const copyItemsText = ref('—')
|
|
|
25
25
|
const copyLineChannelsText = ref('—')
|
|
26
26
|
const manualText = ref('—')
|
|
27
27
|
const mergeBackendText = ref('合并变更 → 后端事务:—')
|
|
28
|
+
const splitText = ref('拆分:先选中一个节点(不选就取第一个),再点拆分')
|
|
28
29
|
const copyBackendText = ref('复制落库:—')
|
|
29
30
|
const mergeSyncText = ref('合并落库:—')
|
|
30
31
|
const backendMode = ref('success')
|
|
@@ -64,6 +65,7 @@ let logSeq = 0
|
|
|
64
65
|
let copyRound = 0
|
|
65
66
|
let pasteRound = 0
|
|
66
67
|
let addPointRound = 0
|
|
68
|
+
let splitRound = 0
|
|
67
69
|
let mergeSyncTimer = null
|
|
68
70
|
let mergeSyncQueue = []
|
|
69
71
|
let disposed = false
|
|
@@ -735,7 +737,7 @@ const manualAddPointCopy = () => {
|
|
|
735
737
|
return manualText.value
|
|
736
738
|
}
|
|
737
739
|
|
|
738
|
-
const prepareMerge = () => {
|
|
740
|
+
const prepareMerge = () => {
|
|
739
741
|
if (!selectedPoints.value.length) selectBatchSources()
|
|
740
742
|
if (!mergeTargetOptions.value.some(item => item.value === mergeTargetId.value)) {
|
|
741
743
|
mergeTargetId.value = mergeTargetOptions.value[0]?.value ?? ''
|
|
@@ -743,8 +745,48 @@ const prepareMerge = () => {
|
|
|
743
745
|
refreshState()
|
|
744
746
|
actionText.value = `已准备 ${selectedPoints.value.length} 个选中节点;保留目标 ${mergeTargetId.value || '—'},可在画布继续 Ctrl 点选`
|
|
745
747
|
pushLog('merge/prepare', actionText.value)
|
|
746
|
-
return actionText.value
|
|
747
|
-
}
|
|
748
|
+
return actionText.value
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/** 节点拆分:把连在来源节点上的线路隔一条分给第二份,通道端点默认跟随成员线路 */
|
|
752
|
+
const splitSelectedPoint = () => {
|
|
753
|
+
const data = view?.getData()
|
|
754
|
+
if (!data?.points?.length) return '没有可拆分的节点'
|
|
755
|
+
const source = selectedPoints.value[0] ?? data.points[0]
|
|
756
|
+
const linked = data.lines
|
|
757
|
+
.map((line, index) => ({ line, index }))
|
|
758
|
+
.filter(({ line }) => String(line.from) === String(source.id) || String(line.to) === String(source.id))
|
|
759
|
+
const movedLines = linked.filter((_entry, index) => index % 2 === 1).map(entry => entry.line.id ?? entry.index)
|
|
760
|
+
splitRound += 1
|
|
761
|
+
const offset = 0.42 + splitRound * 0.04
|
|
762
|
+
const result = view.splitPoint(source.id, [
|
|
763
|
+
{
|
|
764
|
+
x: Number(source.x) - offset,
|
|
765
|
+
y: Number(source.y) - offset * 0.55,
|
|
766
|
+
name: `${pointLabel(source)} · A`,
|
|
767
|
+
zone_name: '拆分后 · A'
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
x: Number(source.x) + offset,
|
|
771
|
+
y: Number(source.y) + offset * 0.55,
|
|
772
|
+
name: `${pointLabel(source)} · B`,
|
|
773
|
+
zone_name: '拆分后 · B',
|
|
774
|
+
lines: movedLines
|
|
775
|
+
}
|
|
776
|
+
])
|
|
777
|
+
if (!result.ok) {
|
|
778
|
+
splitText.value = `拆分失败:${result.reason}`
|
|
779
|
+
actionText.value = splitText.value
|
|
780
|
+
pushLog('splitPoint', splitText.value)
|
|
781
|
+
return splitText.value
|
|
782
|
+
}
|
|
783
|
+
splitText.value = `splitPoint:${pointLabel(source)} → ${result.pointIds.join(' / ')} · 改线 ${result.linesChanged} / 删线 ${result.removedLines} · 改通道 ${result.channelsChanged} / 删通道 ${result.removedChannels} · 第 2 份接回线 ${movedLines.length} 条`
|
|
784
|
+
actionText.value = splitText.value
|
|
785
|
+
pushLog('splitPoint', splitText.value)
|
|
786
|
+
view.selectFeatures(result.pointIds)
|
|
787
|
+
refreshState()
|
|
788
|
+
return splitText.value
|
|
789
|
+
}
|
|
748
790
|
|
|
749
791
|
const mergePatch = () => {
|
|
750
792
|
const patch = {
|
|
@@ -879,6 +921,7 @@ const resetScene = () => {
|
|
|
879
921
|
copyText.value = '复制:—'
|
|
880
922
|
copyLineChannelsText.value = '—'
|
|
881
923
|
mergeBackendText.value = '合并变更 → 后端事务:—'
|
|
924
|
+
splitText.value = '拆分:先选中一个节点(不选就取第一个),再点拆分'
|
|
882
925
|
clipboardText.value = '剪贴板:—'
|
|
883
926
|
copyEventText.value = '还没有 pointCopy 事件'
|
|
884
927
|
copySourcesText.value = '—'
|
|
@@ -888,7 +931,8 @@ const resetScene = () => {
|
|
|
888
931
|
resetBackendDemo()
|
|
889
932
|
copyRound = 0
|
|
890
933
|
pasteRound = 0
|
|
891
|
-
addPointRound = 0
|
|
934
|
+
addPointRound = 0
|
|
935
|
+
splitRound = 0
|
|
892
936
|
refreshState()
|
|
893
937
|
actionText.value = `已恢复 ${fresh.points.length} 节点 / ${fresh.lines.length} 线路 / ${fresh.channels.length} 通道`
|
|
894
938
|
return actionText.value
|
|
@@ -940,10 +984,9 @@ onMounted(() => {
|
|
|
940
984
|
merge: true,
|
|
941
985
|
keyboard: true,
|
|
942
986
|
waypoint: false,
|
|
943
|
-
remove: false,
|
|
944
|
-
channelDraft: false,
|
|
945
|
-
|
|
946
|
-
contextMenu: false
|
|
987
|
+
remove: false,
|
|
988
|
+
channelDraft: false,
|
|
989
|
+
contextMenu: false
|
|
947
990
|
},
|
|
948
991
|
copy: {
|
|
949
992
|
enabled: true,
|
|
@@ -1134,7 +1177,14 @@ onBeforeUnmount(() => {
|
|
|
1134
1177
|
<p class="dv-hint">mergeSelectedPoints 只并选中集;target 可以指定保留谁,item 只改最终节点的字段。拖点相撞走 pending / confirm 两站,确认前也能改写。合并落地后的 changes 可直接作为后端事务:删除来源节点、更新保留节点、更新 / 删除线路与通道。</p>
|
|
1135
1178
|
</div>
|
|
1136
1179
|
<div class="dv-group">
|
|
1137
|
-
<h3>4 ·
|
|
1180
|
+
<h3>4 · 拆分节点</h3>
|
|
1181
|
+
<div class="dv-row">
|
|
1182
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="split-selected" @click="splitSelectedPoint">拆分选中节点</button>
|
|
1183
|
+
</div>
|
|
1184
|
+
<p class="dv-readout" data-demo="split-readout">{{ splitText }}</p>
|
|
1185
|
+
</div>
|
|
1186
|
+
<div class="dv-group">
|
|
1187
|
+
<h3>5 · 待确认合并</h3>
|
|
1138
1188
|
<p class="dv-readout" data-demo="merge-pending-readout">{{ pendingText }}</p>
|
|
1139
1189
|
<div class="dv-row cm-form">
|
|
1140
1190
|
<label>
|
|
@@ -1156,7 +1206,7 @@ onBeforeUnmount(() => {
|
|
|
1156
1206
|
<p class="dv-hint">清空选中后,把画布上的一个节点拖到另一个节点上松手;选保留拖动来源 / 碰撞目标,再点确认。pending 载荷的 impact 已按「保留碰撞目标」预演影响线路数,confirm 时按最终 keep 重算;名称、分区、电压和样式都随 item 写回最终节点。</p>
|
|
1157
1207
|
</div>
|
|
1158
1208
|
<div class="dv-group">
|
|
1159
|
-
<h3>
|
|
1209
|
+
<h3>6 · 复制 / 合并假接口<span class="dv-chip">{{ backendBusy ? '请求中' : '空闲' }}</span></h3>
|
|
1160
1210
|
<div class="dv-row cm-form">
|
|
1161
1211
|
<label>
|
|
1162
1212
|
下一次响应
|
package/docs/demos/EventDemo.vue
CHANGED
|
@@ -89,14 +89,14 @@ const dataLabel = (value) => {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
* 要 DOM
|
|
92
|
+
* 要 DOM 事件的三个事件(pointerDown / pointerUp / contextMenu)在总线上
|
|
93
93
|
* 载荷是 `{ payload, event }`(与回调的两个参数一一对应),其余事件载荷就是回调里那一份 —— 这里统一拆开。
|
|
94
94
|
*/
|
|
95
|
-
const EVENT_PAYLOAD_EVENTS = new Set(['pointerDown', 'pointerUp', 'contextMenu'
|
|
95
|
+
const EVENT_PAYLOAD_EVENTS = new Set(['pointerDown', 'pointerUp', 'contextMenu'])
|
|
96
96
|
const unwrapPayload = (raw, name) => (EVENT_PAYLOAD_EVENTS.has(name) && raw && typeof raw === 'object' && 'payload' in raw ? raw.payload : raw)
|
|
97
97
|
|
|
98
98
|
/** 会带 action 的事件名:* 订阅里从载荷上认出「这一次是什么操作」 */
|
|
99
|
-
const ACTION_EVENTS = new Set(['click', 'dblClick', 'pointerDown', 'pointerUp', 'contextMenu', '
|
|
99
|
+
const ACTION_EVENTS = new Set(['click', 'dblClick', 'pointerDown', 'pointerUp', 'contextMenu', 'labelClick', 'labelDrag', 'labelContextMenu', 'labelDblClick', 'selectChange', 'waypointChange'])
|
|
100
100
|
|
|
101
101
|
/** 最近一次操作的判读:面板上的「动作判读」直接读它 */
|
|
102
102
|
const lastAction = ref('—')
|