gis-view-webgl2 0.1.7 → 0.1.9
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 +14 -11
- package/dist/gis-view.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +445 -241
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +65 -498
- package/docs/GisView.md +1 -1
- package/docs/GisView/345/256/236/347/216/260/345/216/237/347/220/206.md +1 -1
- package/docs/ai/06-/344/274/232/350/257/235/347/264/242/345/274/225/344/270/216/345/244/215/346/237/245/346/226/271/346/263/225.md +114 -478
- package/docs/demos/BasicDemo.vue +144 -27
- package/docs/demos/BatchAddDemo.vue +133 -7
- package/docs/demos/ChannelDemo.vue +61 -0
- package/docs/demos/ConnectDemo.vue +164 -33
- package/docs/demos/CopyMergeDemo.vue +144 -42
- package/docs/demos/EventDemo.vue +29 -24
- package/docs/demos/LabelDemo.vue +5 -5
- package/docs/demos/api-index.js +53 -41
- package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +1 -1
- package/docs/demos/gis-view-demo/GisViewDemoStage.vue +1 -1
- package/docs/demos/gis-view-demo/panels/DataPanels.vue +1 -1
- package/docs/demos/gis-view-demo/useGisViewDemo.js +31 -33
- package/docs/format/02-/350/212/202/347/202/271/344/270/216/347/272/277/350/267/257.md +1 -0
- package/docs/format/04-/351/200/232/351/201/223/344/270/216/346/240/207/347/255/276.md +3 -0
- package/docs/format/07-/344/272/213/344/273/266/350/275/275/350/215/267/344/270/216/347/244/272/344/276/213/346/225/260/346/215/256.md +8 -7
- package/docs/from0/05-/347/233/270/346/234/272/344/270/216/344/272/244/344/272/222.md +1 -1
- package/docs/from0/07-/351/200/232/351/201/223/345/210/206/347/273/204/344/270/216/345/244/232/345/233/236/347/272/277.md +26 -10
- package/docs/index.html +136 -13
- package/docs/internals/15-04-/351/200/220/350/275/256/350/256/260/345/275/225-/347/203/255/345/212/233/345/233/276/344/270/216/346/200/247/350/203/275.md +10 -0
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +1 -1
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +82 -14
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +23 -17
- package/docs/manual/05-/347/224/273/350/264/250/344/270/216/346/211/271/351/207/217/346/233/264/346/226/260.md +65 -0
- package/docs/manual/06-/346/225/260/346/215/256/344/270/216/345/234/272/346/231/257/346/233/264/346/226/260.md +9 -10
- package/docs/manual/07-/346/214/207/351/222/210/344/270/216/350/246/201/347/264/240/347/274/226/350/276/221.md +100 -40
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +11 -7
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +66 -6
- package/docs/manual/10-/351/200/232/351/201/223/347/274/226/350/276/221/344/270/216/346/226/260/345/242/236.md +226 -493
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +28 -5
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +46 -14
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +5 -2
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +34 -29
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +30 -22
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +168 -81
- package/docs/manual/19-/350/257/273/346/225/260/344/270/216/346/213/226/346/213/275/351/242/204/350/247/210.md +1 -1
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +5 -3
- 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 +9 -6
- package/docs/steps/README.md +201 -120
- package/docs/steps/bench-scale.html +1 -1
- package/docs/steps/index.html +222 -6
- package/docs/steps/selftest-actions.html +26 -25
- package/docs/steps/selftest-data.html +1 -1
- package/docs/steps/selftest-demos.html +290 -16
- package/docs/steps/selftest-docs.html +17 -6
- package/docs/steps/selftest-features.html +594 -54
- package/docs/steps/selftest-full.html +3 -3
- package/docs/steps/selftest-labels.html +82 -1
- package/docs/steps/selftest-perf.html +62 -7
- package/docs/steps/selftest-select.html +71 -6
- package/docs/steps/step-05-pick.js +4 -8
- package/docs/steps/step-07-channels.js +3 -5
- package/docs/steps/step-08-labels.js +3 -3
- package/docs/steps/step-10-full.js +3 -3
- package/docs/steps/step-12-events.js +41 -24
- package/docs//344/273/2160/345/210/260/345/256/214/346/225/264/347/232/204/345/256/236/347/216/260.md +1 -0
- package/package.json +1 -1
- package/docs/ai/01-/350/265/267/346/272/220/344/270/216/350/277/231/344/273/275/346/261/207/346/200/273/347/232/204/347/224/261/346/235/245.md +0 -29
- package/docs/ai/02-/346/214/211/345/206/205/345/256/271/345/210/206/347/261/273-/350/277/231/344/270/200/350/267/257/351/203/275/345/234/250/346/224/271/344/273/200/344/271/210.md +0 -66
- package/docs/ai/03-/351/207/215/350/246/201/351/227/256/347/255/224/351/200/211/357/274/210/344/270/200/357/274/211/357/274/232/351/200/211/345/236/213/351/200/232/351/201/223/344/270/216/346/240/267/345/274/217.md +0 -178
- package/docs/ai/04-/351/207/215/350/246/201/351/227/256/347/255/224/351/200/211/357/274/210/344/272/214/357/274/211/357/274/232/344/272/244/344/272/222/346/200/247/350/203/275/344/270/216/346/225/260/346/215/256.md +0 -140
- package/docs/ai/05-/351/207/215/350/246/201/351/227/256/347/255/224/351/200/211/357/274/210/344/270/211/357/274/211/357/274/232/345/256/277/344/270/273/346/227/201/346/224/257/344/270/216/346/224/266/345/217/243.md +0 -43
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* 可视化连线:只靠 points + lines 也能独立成图,再把「新增一条线」的几种常见配置摆在一起。
|
|
4
4
|
*
|
|
5
5
|
* 这一页的初始数据明确是 channels: [] —— 没有任何通道,节点与线路照样能渲染、选中与新增。
|
|
6
|
-
*
|
|
6
|
+
* 「开始连线」按类型分流:独立线路走 beginAddLine,通道走 beginAddChannel,两个节点选完先出草稿:
|
|
7
7
|
* - routing: 'orthogonal' 让预览自动补一个先横后纵的 90° 拐点;
|
|
8
|
-
* - channelDraftStyle 直接交给 beginAddChannel,草稿期间用 updateAddChannelStyle 原地改箭头位置 / 间距 / 配色;
|
|
8
|
+
* - channelDraftStyle 直接交给 beginAddLine / beginAddChannel,草稿期间用 updateAddChannelStyle 原地改箭头位置 / 间距 / 配色;
|
|
9
|
+
* - 预置一条已有通道(两组)后,可演示 singleChannel 阻断、existingChannel 新分组或并入指定分组;
|
|
10
|
+
* - beginAddLine 显式写 existingChannel 时会把新回线挂进已有通道,不写则始终是独立线路;
|
|
9
11
|
* - 取消只收草稿,数据一个字节不动;
|
|
10
12
|
* - 选好起点后点节点以外的位置也会直接取消,事件里的 reason 是 click-outside-node;
|
|
11
13
|
* - 默认添加走 commitAddChannel;
|
|
@@ -29,6 +31,10 @@ const arrowSize = ref(12)
|
|
|
29
31
|
const arrowPositionsText = ref('0.5')
|
|
30
32
|
const circuitSpacing = ref(12)
|
|
31
33
|
const startPointId = ref('')
|
|
34
|
+
const singleChannel = ref(true)
|
|
35
|
+
const existingChannelPolicy = ref('none')
|
|
36
|
+
const groupTarget = ref('')
|
|
37
|
+
const existingGroups = ref([])
|
|
32
38
|
const dotsVisible = ref(true)
|
|
33
39
|
const dotSize = ref(5)
|
|
34
40
|
const startDotColor = ref('#2f6fd0')
|
|
@@ -38,6 +44,7 @@ const draftLayer = ref('above')
|
|
|
38
44
|
const draft = ref(null)
|
|
39
45
|
const actionText = ref('配置好线条之后点「开始连线」,依次点两个节点')
|
|
40
46
|
const dataText = ref('正在创建视图…')
|
|
47
|
+
const eventText = ref('channelDraft:尚未触发')
|
|
41
48
|
const cancelText = ref('尚未取消:选好起点后点一下节点以外的空白处,应收到 reason = click-outside-node')
|
|
42
49
|
const logLines = ref([])
|
|
43
50
|
const logText = computed(() => logLines.value.length
|
|
@@ -78,6 +85,15 @@ const createInitialLines = () => {
|
|
|
78
85
|
|
|
79
86
|
const isChannel = computed(() => linkKind.value !== 'line')
|
|
80
87
|
const circuits = computed(() => linkKind.value === 'channel-3' ? 3 : linkKind.value === 'channel-2' ? 2 : 1)
|
|
88
|
+
const addMethod = computed(() => isChannel.value ? 'beginAddChannel' : 'beginAddLine')
|
|
89
|
+
const groupLineCount = group => Number(group?.lineCount ?? group?.lineIndexes?.length ?? group?.lineIds?.length ?? 0)
|
|
90
|
+
const groupOptions = computed(() => existingGroups.value.map((group, index) => ({
|
|
91
|
+
value: String(group.id),
|
|
92
|
+
label: `第 ${index + 1} 组 · ${String(group.id)} · ${groupLineCount(group)} 回`
|
|
93
|
+
})))
|
|
94
|
+
const existingText = computed(() => existingGroups.value.length
|
|
95
|
+
? `已有通道 connect-existing-channel:${existingGroups.value.map((group, index) => `第 ${index + 1} 组 ${groupLineCount(group)} 回`).join(' / ')}`
|
|
96
|
+
: '已有通道:未预置(先点上方按钮创建一条两组的通道)')
|
|
81
97
|
const arrowPositions = computed(() => arrowPositionsText.value.split(',').map(value => Number(value.trim())).filter(value => Number.isFinite(value)))
|
|
82
98
|
const startPointLabel = computed(() => {
|
|
83
99
|
const point = points.find(item => String(item.id) === String(startPointId.value))
|
|
@@ -100,7 +116,12 @@ const draftText = computed(() => {
|
|
|
100
116
|
}
|
|
101
117
|
const to = current.to.item?.name ?? current.to.id
|
|
102
118
|
const bends = current.path?.length ?? 0
|
|
103
|
-
|
|
119
|
+
const policy = current.existingChannel
|
|
120
|
+
? ` · existingChannel=${current.existingChannel}${current.targetGroupId !== null ? ` → ${String(current.targetGroupId)}` : ''}`
|
|
121
|
+
: ''
|
|
122
|
+
const single = current.singleChannel ? ' · singleChannel' : ''
|
|
123
|
+
const blocked = current.blocked ? ` · 已阻断:${current.reason ?? '未知原因'}` : ''
|
|
124
|
+
return `${kind}${single}${policy} · ${from} → ${to} · ${current.routing === 'orthogonal' ? '正交折线' : '直线'} · 中间拐点 ${bends} · 箭头 ${current.style?.arrow ? current.style.arrowPositions.map(value => `${Math.round(value * 100)}%`).join('/') : '关'} · 回线间距 ${current.style?.circuitSpacing ?? 0}px${blocked}`
|
|
104
125
|
})
|
|
105
126
|
|
|
106
127
|
const pushLog = (name, text) => {
|
|
@@ -143,24 +164,91 @@ const currentLineProps = () => ({
|
|
|
143
164
|
direction: arrow.value ? { count: 1, size: arrowSize.value } : false
|
|
144
165
|
})
|
|
145
166
|
|
|
167
|
+
/** 读取预置通道的分组,供 joinGroup 下拉直接选择;预置前后 / 草稿 ready 时都刷新一次 */
|
|
168
|
+
const refreshExistingGroups = () => {
|
|
169
|
+
if (!view) {
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
const between = view.getChannelBetween('node-0', 'node-1')
|
|
173
|
+
existingGroups.value = between?.hasChannel ? [...(between.groups ?? [])] : []
|
|
174
|
+
if (!existingGroups.value.some(group => String(group.id) === String(groupTarget.value))) {
|
|
175
|
+
groupTarget.value = existingGroups.value[0]?.id ?? ''
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** 预置一条 node-0 ↔ node-1 的通道,带两个分组,专门演示单一通道和指定分组 */
|
|
180
|
+
const seedExistingChannel = () => {
|
|
181
|
+
if (!view) {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
view.endAddChannel()
|
|
185
|
+
draft.value = null
|
|
186
|
+
const between = view.getChannelBetween('node-0', 'node-1')
|
|
187
|
+
if (between?.hasChannel) {
|
|
188
|
+
refreshExistingGroups()
|
|
189
|
+
actionText.value = `已有通道 ${String(between.channelId)} 已存在,共 ${existingGroups.value.length} 组;可以直接开始连线`
|
|
190
|
+
pushLog('seed-existing', actionText.value)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
const lineA = 'connect-existing-line-a'
|
|
194
|
+
const lineB = 'connect-existing-line-b'
|
|
195
|
+
const result = view.addChannel({
|
|
196
|
+
id: 'connect-existing-channel',
|
|
197
|
+
name: '演示已有通道(两组)',
|
|
198
|
+
from: 'node-0',
|
|
199
|
+
to: 'node-1'
|
|
200
|
+
}, [
|
|
201
|
+
{ id: lineA, name: '已有通道 · A 组回线', color: '#2f6fd0', width: 2.2 },
|
|
202
|
+
{ id: lineB, name: '已有通道 · B 组回线', color: '#d9544d', width: 2.2 }
|
|
203
|
+
], [
|
|
204
|
+
{ id: 'connect-existing-group-a', name: 'A 组', lineIds: [lineA] },
|
|
205
|
+
{ id: 'connect-existing-group-b', name: 'B 组', lineIds: [lineB] }
|
|
206
|
+
], { select: true })
|
|
207
|
+
refreshExistingGroups()
|
|
208
|
+
refreshState()
|
|
209
|
+
if (!result.ok) {
|
|
210
|
+
actionText.value = `预置已有通道失败:${result.reason ?? '未知原因'}`
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
actionText.value = `已预置已有通道 ${String(result.channelId)}:node-0 ↔ node-1,共 ${existingGroups.value.length} 组;现在可用 singleChannel / newGroup / joinGroup 演示`
|
|
214
|
+
}
|
|
215
|
+
pushLog('addChannel(预置已有通道)', actionText.value)
|
|
216
|
+
emit('notice', actionText.value)
|
|
217
|
+
}
|
|
218
|
+
|
|
146
219
|
const startDraft = () => {
|
|
147
220
|
if (!view) {
|
|
148
221
|
return
|
|
149
222
|
}
|
|
150
223
|
view.endAddChannel()
|
|
151
|
-
|
|
224
|
+
const options = {
|
|
152
225
|
...(startPointId.value ? { from: startPointId.value } : {}),
|
|
153
|
-
kind: isChannel.value ? 'channel' : 'line',
|
|
154
226
|
circuits: circuits.value,
|
|
155
227
|
routing: routing.value,
|
|
156
228
|
select: true,
|
|
157
229
|
channelDraftStyle: currentDraftStyle(),
|
|
158
230
|
lineProps: currentLineProps()
|
|
159
|
-
}
|
|
231
|
+
}
|
|
232
|
+
if (existingChannelPolicy.value !== 'none') {
|
|
233
|
+
options.existingChannel = existingChannelPolicy.value
|
|
234
|
+
}
|
|
235
|
+
if (existingChannelPolicy.value === 'joinGroup' && groupTarget.value !== '') {
|
|
236
|
+
options.groupId = groupTarget.value
|
|
237
|
+
}
|
|
238
|
+
if (isChannel.value) {
|
|
239
|
+
// beginAddChannel 的单一通道模式:已有通道时按 existingChannel 处理,不写时默认 block
|
|
240
|
+
options.singleChannel = singleChannel.value
|
|
241
|
+
view.beginAddChannel(options)
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
// beginAddLine 不认 singleChannel;要挂进已有通道必须显式写 existingChannel
|
|
245
|
+
view.beginAddLine(options)
|
|
246
|
+
}
|
|
247
|
+
const modeText = existingChannelPolicy.value === 'none' ? '未指定已有通道策略' : `existingChannel=${existingChannelPolicy.value}`
|
|
160
248
|
actionText.value = startPointId.value
|
|
161
|
-
?
|
|
162
|
-
:
|
|
163
|
-
pushLog(
|
|
249
|
+
? `${addMethod.value}:起点已指定为 ${startPointLabel.value},下一次点节点只选终点(${isChannel.value ? `通道 ${circuits.value} 回` : '独立线路'} / ${modeText})`
|
|
250
|
+
: `${addMethod.value}:先点起点,再点终点(${isChannel.value ? `通道 ${circuits.value} 回` : '独立线路'} / ${modeText})`
|
|
251
|
+
pushLog(addMethod.value, actionText.value)
|
|
164
252
|
}
|
|
165
253
|
|
|
166
254
|
const cancelDraft = () => {
|
|
@@ -184,17 +272,15 @@ const commitDraft = () => {
|
|
|
184
272
|
if (!view || !canFinish.value) {
|
|
185
273
|
return
|
|
186
274
|
}
|
|
187
|
-
const result = view.commitAddChannel()
|
|
275
|
+
const result = view.commitAddChannel(undefined, { select: true })
|
|
188
276
|
refreshState()
|
|
189
277
|
if (!result.ok) {
|
|
190
|
-
actionText.value = `commitAddChannel
|
|
278
|
+
actionText.value = `commitAddChannel → blocked:${result.reason ?? '未知原因'}`
|
|
191
279
|
}
|
|
192
280
|
else {
|
|
193
281
|
const lines = result.lines?.length ?? (result.line ? 1 : 0)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
view.selectFeatures(result.lineIds)
|
|
197
|
-
}
|
|
282
|
+
const group = result.groupId !== undefined && result.groupId !== null ? ` · 分组 ${String(result.groupId)}` : ''
|
|
283
|
+
actionText.value = `commitAddChannel → ${result.kind === 'line' ? '独立线路' : '通道'} ${String(result.channelId ?? result.lineId ?? '')} · ${lines} 回线${group}`
|
|
198
284
|
}
|
|
199
285
|
pushLog('commitAddChannel', actionText.value)
|
|
200
286
|
emit('notice', actionText.value)
|
|
@@ -238,7 +324,7 @@ const commitWithObject = () => {
|
|
|
238
324
|
groups: [{ id: channelId, lineIds, ...(middle ? { path: middle } : {}) }]
|
|
239
325
|
}
|
|
240
326
|
}
|
|
241
|
-
const result = view.commitAddChannel(payload)
|
|
327
|
+
const result = view.commitAddChannel(current.kind === 'line' ? { line: payload } : { channel: payload }, { select: true })
|
|
242
328
|
refreshState()
|
|
243
329
|
if (!result.ok) {
|
|
244
330
|
actionText.value = `commitAddChannel(对象) 没有落地:${result.reason ?? '未知原因'}`
|
|
@@ -246,9 +332,6 @@ const commitWithObject = () => {
|
|
|
246
332
|
else {
|
|
247
333
|
const lines = result.lines?.length ?? (result.line ? 1 : 0)
|
|
248
334
|
actionText.value = `commitAddChannel(对象) → ${result.kind === 'line' ? '线路对象' : '通道对象'} ${String(result.channelId ?? result.lineId ?? '')} · ${lines} 回线`
|
|
249
|
-
if (result.lineIds?.length) {
|
|
250
|
-
view.selectFeatures(result.lineIds)
|
|
251
|
-
}
|
|
252
335
|
}
|
|
253
336
|
pushLog('commitAddChannel(对象)', actionText.value)
|
|
254
337
|
emit('notice', actionText.value)
|
|
@@ -303,23 +386,27 @@ const addWithChannels = () => {
|
|
|
303
386
|
sequence += 1
|
|
304
387
|
const middle = middlePathOf(current)
|
|
305
388
|
const kind = current.kind === 'line' ? 'line' : 'channel'
|
|
306
|
-
const result =
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
389
|
+
const result = kind === 'line'
|
|
390
|
+
? view.addLine({
|
|
391
|
+
id: `connect-line-${sequence}`,
|
|
392
|
+
name: `独立线路 ${sequence}`,
|
|
393
|
+
from: current.from.id,
|
|
394
|
+
to: current.to.id,
|
|
395
|
+
...(middle ? { path: middle } : {}),
|
|
396
|
+
lineProps: currentLineProps()
|
|
397
|
+
}, undefined, { select: true })
|
|
398
|
+
: view.addChannels([{
|
|
399
|
+
id: `connect-channel-${sequence}`,
|
|
400
|
+
name: `可视化通道 ${sequence}`,
|
|
401
|
+
from: current.from.id,
|
|
402
|
+
to: current.to.id,
|
|
403
|
+
...(middle ? { path: middle } : {}),
|
|
404
|
+
lineData: Array.from({ length: Math.max(Number(current.circuits) || 1, 1) }, () => ({ ...currentLineProps() }))
|
|
405
|
+
}], { select: true })
|
|
316
406
|
refreshState()
|
|
317
407
|
if (result.ok) {
|
|
318
408
|
view.endAddChannel()
|
|
319
409
|
draft.value = null
|
|
320
|
-
if (result.lineIds.length) {
|
|
321
|
-
view.selectFeatures(result.lineIds)
|
|
322
|
-
}
|
|
323
410
|
actionText.value = `addChannels → ${result.count} 条 · ${result.lineIds.length} 回线${result.channelIds.length ? ` · 通道 ${result.channelIds.join(' / ')}` : ' · 未新建通道'}${middle ? ` · path ${middle.length} 拐点` : ''}`
|
|
324
411
|
}
|
|
325
412
|
else {
|
|
@@ -338,6 +425,11 @@ const resetScene = () => {
|
|
|
338
425
|
view.clearHistory()
|
|
339
426
|
view.fitView(56)
|
|
340
427
|
draft.value = null
|
|
428
|
+
existingGroups.value = []
|
|
429
|
+
groupTarget.value = ''
|
|
430
|
+
existingChannelPolicy.value = 'none'
|
|
431
|
+
singleChannel.value = true
|
|
432
|
+
eventText.value = 'channelDraft:尚未触发'
|
|
341
433
|
sequence = 0
|
|
342
434
|
actionText.value = '已恢复为 channels: [] 的纯点 + 线场景'
|
|
343
435
|
refreshState()
|
|
@@ -366,6 +458,15 @@ onMounted(() => {
|
|
|
366
458
|
},
|
|
367
459
|
onChannelDraft: payload => {
|
|
368
460
|
draft.value = payload
|
|
461
|
+
const target = payload.targetGroupId !== undefined && payload.targetGroupId !== null ? ` · targetGroup=${String(payload.targetGroupId)}` : ''
|
|
462
|
+
eventText.value = `channelDraft:phase=${payload.phase} · kind=${payload.kind ?? '—'} · blocked=${payload.blocked === true} · singleChannel=${payload.singleChannel === true} · existingChannel=${payload.existingChannel ?? 'null'}${target}${payload.reason ? ` · reason=${payload.reason}` : ''}`
|
|
463
|
+
if (payload.phase === 'ready' && payload.hasChannel) {
|
|
464
|
+
refreshExistingGroups()
|
|
465
|
+
}
|
|
466
|
+
if (payload.phase === 'blocked') {
|
|
467
|
+
actionText.value = `失败事件(phase=blocked):${payload.reason ?? '当前草稿不允许新增'}`
|
|
468
|
+
emit('notice', actionText.value)
|
|
469
|
+
}
|
|
369
470
|
if (payload.phase === 'cancel') {
|
|
370
471
|
cancelText.value = `取消:phase = ${payload.phase} · reason = ${payload.reason ?? '未提供'} · 起点 ${payload.from?.item?.name ?? payload.from?.id ?? '—'}`
|
|
371
472
|
actionText.value = payload.reason === 'click-outside-node'
|
|
@@ -438,7 +539,36 @@ onBeforeUnmount(() => {
|
|
|
438
539
|
</label>
|
|
439
540
|
<label>回线间距 <input v-model.number="circuitSpacing" type="range" min="6" max="28" step="1" data-demo="connect-spacing" /> {{ circuitSpacing }}px</label>
|
|
440
541
|
</div>
|
|
441
|
-
<p class="dv-hint">草稿样式直接传给 <code>
|
|
542
|
+
<p class="dv-hint">草稿样式直接传给 <code>beginAddLine</code> / <code>beginAddChannel</code>;连线期间改颜色 / 线宽 / 箭头位置 / 回线间距会走 <code>updateAddChannelStyle()</code>,预览立即更新。正交折线会自动补一个 90 度拐点;落地线路用完整 path,通道只写中间拐点。</p>
|
|
543
|
+
</div>
|
|
544
|
+
|
|
545
|
+
<div class="dv-group">
|
|
546
|
+
<h3>单一通道与已有分组</h3>
|
|
547
|
+
<div class="dv-row">
|
|
548
|
+
<button type="button" class="dv-button" data-demo="connect-seed-existing" :disabled="isDraftLive" @click="seedExistingChannel">预置已有通道(两组)</button>
|
|
549
|
+
</div>
|
|
550
|
+
<div class="dv-row">
|
|
551
|
+
<label><input v-model="singleChannel" type="checkbox" data-demo="connect-single-channel" :disabled="!isChannel || isDraftLive" /> singleChannel</label>
|
|
552
|
+
<label>已有通道时
|
|
553
|
+
<select v-model="existingChannelPolicy" data-demo="connect-existing-mode" :disabled="isDraftLive">
|
|
554
|
+
<option value="none">不指定(线路默认独立)</option>
|
|
555
|
+
<option value="auto">auto · 新分组优先</option>
|
|
556
|
+
<option value="newGroup">newGroup · 新分组</option>
|
|
557
|
+
<option value="joinGroup">joinGroup · 指定分组</option>
|
|
558
|
+
<option value="block">block · 直接失败</option>
|
|
559
|
+
</select>
|
|
560
|
+
</label>
|
|
561
|
+
</div>
|
|
562
|
+
<div class="dv-row">
|
|
563
|
+
<label>目标分组
|
|
564
|
+
<select v-model="groupTarget" data-demo="connect-group-target" :disabled="existingChannelPolicy !== 'joinGroup' || isDraftLive">
|
|
565
|
+
<option v-if="!groupOptions.length" value="">先预置已有通道</option>
|
|
566
|
+
<option v-for="group in groupOptions" :key="group.value" :value="group.value">{{ group.label }}</option>
|
|
567
|
+
</select>
|
|
568
|
+
</label>
|
|
569
|
+
</div>
|
|
570
|
+
<p class="dv-readout" data-demo="connect-existing-readout">{{ existingText }}</p>
|
|
571
|
+
<p class="dv-hint"><code>beginAddChannel({ singleChannel: true })</code> 在两端已有通道时默认阻断;显式 <code>existingChannel</code> 可改为新分组 / 指定分组 / 自动。<code>beginAddLine</code> 不读 singleChannel,但显式写 <code>existingChannel: 'newGroup' / 'joinGroup'</code> 时,会把新回线加进已有通道。</p>
|
|
442
572
|
</div>
|
|
443
573
|
|
|
444
574
|
<div class="dv-group">
|
|
@@ -481,6 +611,7 @@ onBeforeUnmount(() => {
|
|
|
481
611
|
<p class="dv-readout" data-demo="connect-draft-readout">{{ draftText }}</p>
|
|
482
612
|
<p class="dv-readout" data-demo="connect-data-readout">{{ dataText }}</p>
|
|
483
613
|
<p class="dv-readout" data-demo="connect-action-readout">{{ actionText }}</p>
|
|
614
|
+
<p class="dv-readout" data-demo="connect-event-readout">{{ eventText }}</p>
|
|
484
615
|
<p class="dv-readout" data-demo="connect-cancel-readout">{{ cancelText }}</p>
|
|
485
616
|
</div>
|
|
486
617
|
|
|
@@ -25,7 +25,8 @@ const copyItemsText = ref('—')
|
|
|
25
25
|
const copyLineChannelsText = ref('—')
|
|
26
26
|
const manualText = ref('—')
|
|
27
27
|
const mergeBackendText = ref('合并变更 → 后端事务:—')
|
|
28
|
-
const splitText = ref('
|
|
28
|
+
const splitText = ref('自动解除合并:先选中一个合并后的节点(不选就找第一个带 mergeInfo 的节点)')
|
|
29
|
+
const lastSplitResult = shallowRef(null)
|
|
29
30
|
const copyBackendText = ref('复制落库:—')
|
|
30
31
|
const mergeSyncText = ref('合并落库:—')
|
|
31
32
|
const backendMode = ref('success')
|
|
@@ -44,8 +45,12 @@ const mergedVoltage = ref('525')
|
|
|
44
45
|
const mergedColor = ref('#ff5d8f')
|
|
45
46
|
const mergedSize = ref('16')
|
|
46
47
|
const mergedSymbol = ref('square')
|
|
47
|
-
const mergedStrokeColor = ref('#ffffff')
|
|
48
|
-
|
|
48
|
+
const mergedStrokeColor = ref('#ffffff')
|
|
49
|
+
/** 合并时取双方最高值的字段;空字符串表示不取任何键 */
|
|
50
|
+
const mergeMaxKeys = ref('')
|
|
51
|
+
/** 拆分 / 自动解除合并后是否选中结果节点 */
|
|
52
|
+
const splitSelect = ref(false)
|
|
53
|
+
const mergeTargetId = ref('')
|
|
49
54
|
const mergeKeep = ref('target')
|
|
50
55
|
const rewriteOnConfirm = ref(true)
|
|
51
56
|
const pointOptions = ref([])
|
|
@@ -72,6 +77,7 @@ let disposed = false
|
|
|
72
77
|
|
|
73
78
|
const pointLabel = item => String(item?.name ?? item?.label ?? item?.id ?? '节点')
|
|
74
79
|
const pointKey = item => String(item?.id ?? item?.label ?? item?.name ?? '—')
|
|
80
|
+
const parsedMergeMaxKeys = () => mergeMaxKeys.value.split(',').map(key => key.trim()).filter(Boolean)
|
|
75
81
|
const itemId = item => (item?.id ?? item?.line_id ?? item?.channel_id ?? '无 id')
|
|
76
82
|
/** 复制载荷的 lines 与 lineChannels 一一对齐:直接列出每条回线复制后属于哪条通道 / 哪一组 */
|
|
77
83
|
const describeLineChannels = (payload) => {
|
|
@@ -101,6 +107,14 @@ const describeMergeChanges = changes => describeMergeChangeSets(changes ? [chang
|
|
|
101
107
|
const describeMergeImpact = impact => (impact
|
|
102
108
|
? `影响 ${impact.lineCount} 条线路(改线 ${impact.linesChanged} / 删线 ${impact.removedLines})· 通道 改 ${impact.channelsChanged} / 删 ${impact.removedChannels} / 并 ${impact.mergedChannels}`
|
|
103
109
|
: '影响读数:当前数据对不上')
|
|
110
|
+
const voltageOf = item => item?.vn_kv ?? item?.voltage ?? item?.voltageKv ?? item?.voltage_level ?? item?.voltageLevel ?? '—'
|
|
111
|
+
/** 合并追溯读数:来源、保留前电压与快照条数,演示解除合并需要的字段都在这里 */
|
|
112
|
+
const describeMergeInfo = (item) => {
|
|
113
|
+
const info = item?.mergeInfo
|
|
114
|
+
if (!info || typeof info !== 'object') return 'mergeInfo —'
|
|
115
|
+
const sourceIds = (info.sourceIds ?? info.points?.map(snapshot => snapshot.id) ?? []).map(id => String(id ?? '无 id')).join(' / ') || '—'
|
|
116
|
+
return `mergeInfo v${info.version} · 保留前 ${info.targetPoint?.name ?? '—'} / ${voltageOf(info.targetPoint)} kV · 来源 [${sourceIds}] · 快照 线 ${info.lines?.length ?? 0} / 通道 ${info.channels?.length ?? 0}`
|
|
117
|
+
}
|
|
104
118
|
const backendBusy = computed(() => backendBusyCount.value > 0)
|
|
105
119
|
const backendStateText = computed(() => `假后端 v${backendSnapshot.value.version} · 已应用 ${backendSnapshot.value.applied} 条 · op 缓存 ${backendSnapshot.value.cached} · 幂等回放 ${backendSnapshot.value.replays ?? 0} 次 · 模拟延迟 ${backendSnapshot.value.latency}ms`)
|
|
106
120
|
const backendModeHint = computed(() => FAKE_BACKEND_MODES.find(item => item.value === backendMode.value)?.hint ?? '')
|
|
@@ -748,42 +762,64 @@ const prepareMerge = () => {
|
|
|
748
762
|
return actionText.value
|
|
749
763
|
}
|
|
750
764
|
|
|
751
|
-
/**
|
|
765
|
+
/** 解除合并:优先用 mergeInfo 自动恢复全部来源;没有合并记录时才退回手工两份拆分 */
|
|
752
766
|
const splitSelectedPoint = () => {
|
|
753
767
|
const data = view?.getData()
|
|
754
768
|
if (!data?.points?.length) return '没有可拆分的节点'
|
|
755
|
-
const
|
|
756
|
-
const
|
|
757
|
-
|
|
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)
|
|
769
|
+
const mergedPoint = selectedPoints.value.find(item => item?.mergeInfo) ?? data.points.find(item => item?.mergeInfo)
|
|
770
|
+
const source = mergedPoint ?? selectedPoints.value[0] ?? data.points[0]
|
|
771
|
+
const auto = Boolean(mergedPoint?.mergeInfo)
|
|
760
772
|
splitRound += 1
|
|
761
773
|
const offset = 0.42 + splitRound * 0.04
|
|
762
|
-
const result =
|
|
763
|
-
{
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
774
|
+
const result = auto
|
|
775
|
+
? view.splitPoint(source.id, null, { label: '解除合并(自动恢复)', select: splitSelect.value })
|
|
776
|
+
: view.splitPoint(source.id, [
|
|
777
|
+
{
|
|
778
|
+
x: Number(source.x) - offset,
|
|
779
|
+
y: Number(source.y) - offset * 0.55,
|
|
780
|
+
name: `${pointLabel(source)} · A`,
|
|
781
|
+
zone_name: '解除合并后 · A'
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
x: Number(source.x) + offset,
|
|
785
|
+
y: Number(source.y) + offset * 0.55,
|
|
786
|
+
name: `${pointLabel(source)} · B`,
|
|
787
|
+
zone_name: '解除合并后 · B'
|
|
788
|
+
}
|
|
789
|
+
], { autoLines: 'alternate', label: '解除合并(手工拆分)', select: splitSelect.value })
|
|
790
|
+
lastSplitResult.value = result
|
|
777
791
|
if (!result.ok) {
|
|
778
|
-
splitText.value =
|
|
792
|
+
splitText.value = `解除合并失败:${result.reason}`
|
|
779
793
|
actionText.value = splitText.value
|
|
780
794
|
pushLog('splitPoint', splitText.value)
|
|
781
795
|
return splitText.value
|
|
782
796
|
}
|
|
783
|
-
|
|
797
|
+
if (result.autoRestored) {
|
|
798
|
+
const restoredNames = (result.items ?? []).map(item => `${pointLabel(item)}(${String(voltageOf(item))} kV)`).join(' / ')
|
|
799
|
+
splitText.value = `splitPoint(自动解除合并):${pointLabel(source)} → ${restoredNames} · 恢复来源 ${(result.mergeSourceIds ?? []).map(id => String(id)).join(' / ') || '—'} · 改线 ${result.linesChanged} / 改通道 ${result.channelsChanged} · ${splitSelect.value ? '已选中拆分结果' : '未选中拆分结果'} · mergeInfo 已从保留节点移除`
|
|
800
|
+
actionText.value = splitText.value
|
|
801
|
+
pushLog('splitPoint/auto', splitText.value)
|
|
802
|
+
refreshState()
|
|
803
|
+
return splitText.value
|
|
804
|
+
}
|
|
805
|
+
const lineSlot = new Map()
|
|
806
|
+
for (const assignment of result.lineAssignments ?? []) {
|
|
807
|
+
for (const lineId of assignment.lineIds ?? []) {
|
|
808
|
+
lineSlot.set(String(lineId), assignment.index)
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const channelsKept = (data.channels ?? []).every((channel) => {
|
|
812
|
+
const slots = new Set()
|
|
813
|
+
for (const lineId of channel.lineIds ?? []) {
|
|
814
|
+
const slot = lineSlot.get(String(lineId))
|
|
815
|
+
if (slot !== undefined) slots.add(slot)
|
|
816
|
+
}
|
|
817
|
+
return slots.size <= 1
|
|
818
|
+
})
|
|
819
|
+
const counts = result.lineAssignments.map(assignment => `${assignment.index === 0 ? 'A' : assignment.index === 1 ? 'B' : String(assignment.index + 1)} ${assignment.lineCount}`).join(' / ')
|
|
820
|
+
splitText.value = `splitPoint(解除合并):${pointLabel(source)} → ${result.pointIds.join(' / ')} · autoLines=${result.autoLines} · 自动分线 ${result.autoAssignedLines}(${counts})· 改线 ${result.linesChanged} / 删线 ${result.removedLines} · 改通道 ${result.channelsChanged} / 删通道 ${result.removedChannels} · 通道成组${channelsKept ? '保持完整' : '发生拆分'} · ${splitSelect.value ? '已选中拆分结果' : '未选中拆分结果'}`
|
|
784
821
|
actionText.value = splitText.value
|
|
785
822
|
pushLog('splitPoint', splitText.value)
|
|
786
|
-
view.selectFeatures(result.pointIds)
|
|
787
823
|
refreshState()
|
|
788
824
|
return splitText.value
|
|
789
825
|
}
|
|
@@ -804,7 +840,7 @@ const mergePatch = () => {
|
|
|
804
840
|
return patch
|
|
805
841
|
}
|
|
806
842
|
|
|
807
|
-
const mergeSelectedByApi = () => {
|
|
843
|
+
const mergeSelectedByApi = () => {
|
|
808
844
|
const points = selectedPoints.value
|
|
809
845
|
if (points.length < 2) return 'mergeSelectedPoints:至少选中两个节点'
|
|
810
846
|
const targetId = mergeTargetOptions.value.some(item => item.value === mergeTargetId.value)
|
|
@@ -829,8 +865,59 @@ const mergeSelectedByApi = () => {
|
|
|
829
865
|
actionText.value = mergeText.value
|
|
830
866
|
pushLog('mergeSelectedPoints', mergeText.value)
|
|
831
867
|
refreshState()
|
|
832
|
-
return mergeText.value
|
|
833
|
-
}
|
|
868
|
+
return mergeText.value
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/** 把面板上的字段清单写进 merge.maxKeys;空清单表示所有业务字段都取保留节点 */
|
|
872
|
+
const applyMergeMaxKeys = () => {
|
|
873
|
+
if (!view) return '画布还没准备好'
|
|
874
|
+
const maxKeys = parsedMergeMaxKeys()
|
|
875
|
+
const merge = view.getOptions().merge ?? {}
|
|
876
|
+
view.updateOptions({ merge: { ...merge, enabled: true, maxKeys } })
|
|
877
|
+
actionText.value = maxKeys.length
|
|
878
|
+
? `merge.maxKeys = [${maxKeys.join(', ')}]:合并时这些数值字段取双方最高值`
|
|
879
|
+
: 'merge.maxKeys = []:合并时所有业务字段保留目标节点(组件不再默认取电压最高)'
|
|
880
|
+
pushLog('merge.maxKeys', actionText.value)
|
|
881
|
+
return actionText.value
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/** 缺省合并:不传 item,按 merge.maxKeys 决定哪些数值字段取双方最高值 */
|
|
885
|
+
const mergeSelectedByDefault = () => {
|
|
886
|
+
const points = selectedPoints.value
|
|
887
|
+
if (points.length < 2) return 'mergeSelectedPoints:至少选中两个节点'
|
|
888
|
+
const targetId = mergeTargetOptions.value.some(item => item.value === mergeTargetId.value)
|
|
889
|
+
? mergeTargetId.value
|
|
890
|
+
: mergeTargetOptions.value[0].value
|
|
891
|
+
const target = points.find(item => pointKey(item) === targetId)
|
|
892
|
+
const sources = points.filter(item => pointKey(item) !== targetId).map(item => item.id)
|
|
893
|
+
const maxKeys = parsedMergeMaxKeys()
|
|
894
|
+
const maxReadout = maxKeys.length
|
|
895
|
+
? maxKeys.map((key) => {
|
|
896
|
+
const values = points.map(item => Number(item[key])).filter(Number.isFinite)
|
|
897
|
+
return `${key}=${values.length ? Math.max(...values) : '无数字'}`
|
|
898
|
+
}).join(' / ')
|
|
899
|
+
: '未配置(全部字段取保留节点)'
|
|
900
|
+
const result = view.mergeSelectedPoints({
|
|
901
|
+
target: { id: target?.id ?? targetId },
|
|
902
|
+
sources,
|
|
903
|
+
label: 'demo 缺省合并(maxKeys)'
|
|
904
|
+
})
|
|
905
|
+
if (result.ok) {
|
|
906
|
+
const item = result.target?.item
|
|
907
|
+
view.selectFeatures(target?.id ?? targetId)
|
|
908
|
+
mergeText.value = `mergeSelectedPoints(缺省规则)· 保留 ${pointLabel(item)} · maxKeys ${maxReadout} · 结果 ${String(voltageOf(item))} kV · 名称 ${item?.name ?? '—'} / 图标 ${String(item?.symbol ?? '—')} · ${describeMergeInfo(item)}`
|
|
909
|
+
pendingText.value = maxKeys.length
|
|
910
|
+
? `这次没有 item 改写:maxKeys 列出的字段取参与场站最高值,其余字段取保留节点`
|
|
911
|
+
: '这次没有 item 改写:maxKeys 为空,全部业务字段(含电压)取保留节点'
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
mergeText.value = `mergeSelectedPoints 失败:${result.reason}`
|
|
915
|
+
}
|
|
916
|
+
actionText.value = mergeText.value
|
|
917
|
+
pushLog('mergeSelectedPoints/default', mergeText.value)
|
|
918
|
+
refreshState()
|
|
919
|
+
return mergeText.value
|
|
920
|
+
}
|
|
834
921
|
|
|
835
922
|
const handlePointMerge = (payload) => {
|
|
836
923
|
if (payload.phase === 'pending') {
|
|
@@ -868,7 +955,7 @@ const handlePointMerge = (payload) => {
|
|
|
868
955
|
if (payload.phase === 'merge') {
|
|
869
956
|
mergeBackendText.value = payload.ok ? describeMergeChanges(payload.changes) : '合并变更 → 后端事务:合并失败,没有变更集'
|
|
870
957
|
mergeText.value = payload.ok
|
|
871
|
-
? `merge · 被并 ${pointLabel(payload.source?.item)} → 保留 ${pointLabel(payload.target?.item)} · 最终 ${pointLabel(payload.item)} · 色 ${payload.item?.color ?? '—'} / 尺寸 ${payload.item?.size ?? '—'} / 图标 ${String(payload.item?.symbol ?? '—')} · 改线 ${payload.linesChanged} / 删线 ${payload.removedLines}`
|
|
958
|
+
? `merge · 被并 ${pointLabel(payload.source?.item)} → 保留 ${pointLabel(payload.target?.item)} · 最终 ${pointLabel(payload.item)} · 电压 ${String(voltageOf(payload.item))} kV · 色 ${payload.item?.color ?? '—'} / 尺寸 ${payload.item?.size ?? '—'} / 图标 ${String(payload.item?.symbol ?? '—')} · 改线 ${payload.linesChanged} / 删线 ${payload.removedLines} · ${describeMergeInfo(payload.item)}`
|
|
872
959
|
: `merge 失败:${payload.reason}`
|
|
873
960
|
pendingMerge.value = null
|
|
874
961
|
pendingText.value = payload.ok ? '合并已落地,拖动来源节点已经收进保留目标' : `合并失败:${payload.reason}`
|
|
@@ -921,13 +1008,16 @@ const resetScene = () => {
|
|
|
921
1008
|
copyText.value = '复制:—'
|
|
922
1009
|
copyLineChannelsText.value = '—'
|
|
923
1010
|
mergeBackendText.value = '合并变更 → 后端事务:—'
|
|
924
|
-
splitText.value = '
|
|
1011
|
+
splitText.value = '自动解除合并:先选中一个合并后的节点(不选就找第一个带 mergeInfo 的节点)'
|
|
925
1012
|
clipboardText.value = '剪贴板:—'
|
|
926
1013
|
copyEventText.value = '还没有 pointCopy 事件'
|
|
927
1014
|
copySourcesText.value = '—'
|
|
928
1015
|
copyItemsText.value = '—'
|
|
929
1016
|
manualText.value = '—'
|
|
930
1017
|
mergeKeep.value = 'target'
|
|
1018
|
+
mergeMaxKeys.value = ''
|
|
1019
|
+
splitSelect.value = false
|
|
1020
|
+
view.updateOptions({ merge: { enabled: true, distance: 12, channelGroups: 'keep', maxKeys: [] } })
|
|
931
1021
|
resetBackendDemo()
|
|
932
1022
|
copyRound = 0
|
|
933
1023
|
pasteRound = 0
|
|
@@ -1000,7 +1090,7 @@ onMounted(() => {
|
|
|
1000
1090
|
},
|
|
1001
1091
|
// Ctrl+C / Ctrl+V 监听的三件套:copy 打开 + keyboard 打开 + 快捷键映射(不写也是这两个缺省值)
|
|
1002
1092
|
shortcuts: { copy: 'ctrl+c', paste: 'ctrl+v' },
|
|
1003
|
-
merge: { enabled: true, distance: 12, channelGroups: 'keep' },
|
|
1093
|
+
merge: { enabled: true, distance: 12, channelGroups: 'keep', maxKeys: [] },
|
|
1004
1094
|
onReady: () => {
|
|
1005
1095
|
nextTick(() => {
|
|
1006
1096
|
view?.fitView(50)
|
|
@@ -1166,22 +1256,34 @@ onBeforeUnmount(() => {
|
|
|
1166
1256
|
<input v-model="mergedStrokeColor" data-demo="merge-stroke-color" type="text" />
|
|
1167
1257
|
</label>
|
|
1168
1258
|
</div>
|
|
1169
|
-
<div class="dv-row">
|
|
1170
|
-
<label><input v-model="rewriteOnConfirm" type="checkbox" /> 手动拖拽合并确认时改写 payload.item</label>
|
|
1171
|
-
</div>
|
|
1172
|
-
<div class="dv-row">
|
|
1173
|
-
<
|
|
1174
|
-
|
|
1259
|
+
<div class="dv-row">
|
|
1260
|
+
<label><input v-model="rewriteOnConfirm" type="checkbox" /> 手动拖拽合并确认时改写 payload.item</label>
|
|
1261
|
+
</div>
|
|
1262
|
+
<div class="dv-row cm-form">
|
|
1263
|
+
<label>
|
|
1264
|
+
maxKeys(逗号分隔)
|
|
1265
|
+
<input v-model="mergeMaxKeys" data-demo="merge-max-keys" type="text" placeholder="例如 vn_kv,value;留空则都不取最高" />
|
|
1266
|
+
</label>
|
|
1267
|
+
<button type="button" class="dv-button" data-demo="merge-apply-max-keys" @click="applyMergeMaxKeys">应用 maxKeys</button>
|
|
1268
|
+
</div>
|
|
1269
|
+
<div class="dv-row">
|
|
1270
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="merge-default" :disabled="!canMerge" @click="mergeSelectedByDefault">按缺省规则合并(按 maxKeys 取最高)</button>
|
|
1271
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="merge-selected" :disabled="!canMerge" @click="mergeSelectedByApi">合并选中(指定保留节点)</button>
|
|
1272
|
+
</div>
|
|
1175
1273
|
<p class="dv-readout" data-demo="merge-readout">{{ mergeText }}</p>
|
|
1176
1274
|
<p class="dv-readout" data-demo="merge-backend-readout">{{ mergeBackendText }}</p>
|
|
1177
|
-
<p class="dv-hint">
|
|
1275
|
+
<p class="dv-hint">「按缺省规则合并」不传 item:只有 <code>merge.maxKeys</code> 列出的数值字段取双方最高值(缺省为空,不取任何键),名称 / 图标等其余业务字段取保留节点;合并后的节点带 <code>mergeInfo</code>(来源快照、线路 / 通道快照)。「指定保留节点」仍可用 item 覆盖字段。拖点相撞走 pending / confirm 两站,确认前也能改写。合并落地后的 changes 可直接作为后端事务。</p>
|
|
1178
1276
|
</div>
|
|
1179
1277
|
<div class="dv-group">
|
|
1180
|
-
<h3>4 ·
|
|
1278
|
+
<h3>4 · 解除合并</h3>
|
|
1279
|
+
<div class="dv-row">
|
|
1280
|
+
<label><input v-model="splitSelect" data-demo="split-select" type="checkbox" /> 解除合并后选中恢复出的节点</label>
|
|
1281
|
+
</div>
|
|
1181
1282
|
<div class="dv-row">
|
|
1182
|
-
<button type="button" class="dv-button dv-button--primary" data-demo="split-selected" @click="splitSelectedPoint"
|
|
1283
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="split-selected" @click="splitSelectedPoint">自动解除合并(恢复来源)</button>
|
|
1183
1284
|
</div>
|
|
1184
1285
|
<p class="dv-readout" data-demo="split-readout">{{ splitText }}</p>
|
|
1286
|
+
<p class="dv-hint"><code>splitPoint(source)</code> 省略 parts 时读取节点上的 <code>mergeInfo</code>,恢复合并前的保留站和全部来源节点,并按快照恢复线路端点、通道端点、分组与回线归属。默认 <code>select: false</code>,拆分后不选中任何恢复节点;勾选这一项才传 <code>select: true</code>。没有合并记录时自动退回 <code>splitPoint(source, [A, B], { autoLines: 'alternate' })</code> 的手工拆分;自动路径返回 <code>autoRestored: true</code> 与 <code>mergeSourceIds</code>。</p>
|
|
1185
1287
|
</div>
|
|
1186
1288
|
<div class="dv-group">
|
|
1187
1289
|
<h3>5 · 待确认合并</h3>
|