gis-view-webgl2 0.1.13 → 0.1.15
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/dist/index.cjs +1 -1
- package/dist/index.d.ts +140 -60
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +73 -5
- package/docs/GisView.md +49 -49
- 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 +87 -16
- package/docs/demos/ApiCoverageDemo.vue +1 -5
- package/docs/demos/BackendSyncDemo.vue +1 -2
- package/docs/demos/BackgroundDemo.vue +68 -1
- package/docs/demos/BasicDemo.vue +76 -76
- package/docs/demos/BatchAddDemo.vue +2 -1
- package/docs/demos/ChannelDemo.vue +1 -3
- package/docs/demos/ClusterDemo.vue +48 -1
- package/docs/demos/CopyMergeDemo.vue +899 -904
- package/docs/demos/DemoShell.vue +336 -336
- package/docs/demos/EventDemo.vue +300 -300
- package/docs/demos/ExtensionDemo.vue +200 -200
- package/docs/demos/GisViewDemo.vue +64 -64
- package/docs/demos/LabelDemo.vue +489 -489
- package/docs/demos/StyleDemo.vue +67 -67
- package/docs/demos/api-index.js +24 -29
- package/docs/demos/demos.js +92 -92
- package/docs/demos/gis-view-demo/useGisViewDemo.js +19 -14
- package/docs/demos/shared/demoData.js +107 -107
- package/docs/demos/shared/demoStyle.css +249 -249
- package/docs/format/02-/350/212/202/347/202/271/344/270/216/347/272/277/350/267/257.md +3 -3
- package/docs/format/03-/346/213/220/347/202/271/350/243/205/351/245/260/344/270/216/346/240/267/345/274/217.md +3 -0
- package/docs/from0/01-/345/205/210/346/212/212/345/234/260/345/233/276/346/203/263/346/270/205/346/245/232.md +8 -7
- package/docs/index.html +3 -3
- package/docs/internals/04-/347/233/270/346/234/272/345/235/220/346/240/207/344/270/216/345/203/217/347/264/240.md +6 -4
- package/docs/internals/14-/350/203/214/346/231/257/345/261/202.md +2 -2
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +36 -16
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +38 -84
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +36 -19
- 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 +1 -1
- 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 +7 -7
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +15 -15
- 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 +1 -1
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +6 -0
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +1 -1
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +2 -0
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +10 -8
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +11 -7
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +27 -8
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +27 -4
- 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 +3 -3
- package/docs/playground/index.html +1 -1
- package/docs/sample-document/345/255/227/346/256/265/350/257/246/350/247/243.md +2 -2
- package/docs/steps/check-doc-truth.mjs +1 -1
- package/docs/steps/selftest-actions.html +4 -4
- package/docs/steps/selftest-data.html +5 -5
- package/docs/steps/selftest-demos.html +1683 -1619
- package/docs/steps/selftest-docs.html +4 -4
- package/docs/steps/selftest-effects.html +1 -1
- package/docs/steps/selftest-features.html +548 -47
- package/docs/steps/selftest-full.html +566 -566
- package/docs/steps/selftest-labels.html +647 -647
- package/docs/steps/selftest-perf.html +2282 -2237
- package/docs/steps/selftest-select.html +53 -14
- package/docs/steps/step-10-full.js +1 -4
- package/docs/steps/step-12-events.js +1 -4
- package/package.json +43 -43
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
/**
|
|
3
|
-
* 批量复制与合并(可交互版):同一张画布上把三条链路走完 ——
|
|
4
|
-
* 框选 → 复制 / 合并;Alt 点击复制 → 从 pointCopy 载荷接管或回滚;
|
|
5
|
-
* 拖点相撞 → 从 pointMerge 的 pending / confirm 载荷指定保留节点并改写最终节点。
|
|
6
|
-
*/
|
|
1
|
+
<script setup>
|
|
2
|
+
/**
|
|
3
|
+
* 批量复制与合并(可交互版):同一张画布上把三条链路走完 ——
|
|
4
|
+
* 框选 → 复制 / 合并;Alt 点击复制 → 从 pointCopy 载荷接管或回滚;
|
|
5
|
+
* 拖点相撞 → 从 pointMerge 的 pending / confirm 载荷指定保留节点并改写最终节点。
|
|
6
|
+
*/
|
|
7
7
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, triggerRef } from 'vue'
|
|
8
8
|
import { createGisView } from '../../src/index.js'
|
|
9
9
|
import { buildDemoData } from './shared/demoData.js'
|
|
10
10
|
import { createFakeBackend, cloneFakeData, FAKE_BACKEND_MODES } from './shared/fakeBackend.js'
|
|
11
11
|
import './shared/demoStyle.css'
|
|
12
|
-
|
|
13
|
-
const emit = defineEmits(['notice'])
|
|
14
|
-
const container = ref(null)
|
|
15
|
-
const selectionText = ref('未选中')
|
|
16
|
-
const actionText = ref('先框选一组节点,再试复制或合并')
|
|
17
|
-
const copyText = ref('复制:—')
|
|
18
|
-
const clipboardText = ref('剪贴板:—')
|
|
19
|
-
const mergeText = ref('合并:—')
|
|
20
|
-
const pendingText = ref('拖动一个节点撞到另一个节点上,这里会出现待确认载荷')
|
|
21
|
-
const mergeDetailText = ref('合并影响详情:—')
|
|
12
|
+
|
|
13
|
+
const emit = defineEmits(['notice'])
|
|
14
|
+
const container = ref(null)
|
|
15
|
+
const selectionText = ref('未选中')
|
|
16
|
+
const actionText = ref('先框选一组节点,再试复制或合并')
|
|
17
|
+
const copyText = ref('复制:—')
|
|
18
|
+
const clipboardText = ref('剪贴板:—')
|
|
19
|
+
const mergeText = ref('合并:—')
|
|
20
|
+
const pendingText = ref('拖动一个节点撞到另一个节点上,这里会出现待确认载荷')
|
|
21
|
+
const mergeDetailText = ref('合并影响详情:—')
|
|
22
22
|
const copyEventText = ref('还没有 pointCopy 事件')
|
|
23
23
|
const copySourcesText = ref('—')
|
|
24
24
|
const copyItemsText = ref('—')
|
|
@@ -39,28 +39,28 @@ const copyBackendRetry = shallowRef(null)
|
|
|
39
39
|
const mergeBackendRetry = shallowRef(null)
|
|
40
40
|
const copyBackendLast = shallowRef(null)
|
|
41
41
|
const mergeBackendLast = shallowRef(null)
|
|
42
|
-
const mergedName = ref('合并后节点(demo 改写)')
|
|
43
|
-
const mergedZone = ref('合并后(item 改写)')
|
|
44
|
-
const mergedVoltage = ref('525')
|
|
45
|
-
const mergedColor = ref('#ff5d8f')
|
|
46
|
-
const mergedSize = ref('16')
|
|
47
|
-
const mergedSymbol = ref('square')
|
|
42
|
+
const mergedName = ref('合并后节点(demo 改写)')
|
|
43
|
+
const mergedZone = ref('合并后(item 改写)')
|
|
44
|
+
const mergedVoltage = ref('525')
|
|
45
|
+
const mergedColor = ref('#ff5d8f')
|
|
46
|
+
const mergedSize = ref('16')
|
|
47
|
+
const mergedSymbol = ref('square')
|
|
48
48
|
const mergedStrokeColor = ref('#ffffff')
|
|
49
49
|
/** 合并时取双方最高值的字段;空字符串表示不取任何键 */
|
|
50
50
|
const mergeMaxKeys = ref('')
|
|
51
51
|
/** 拆分 / 自动解除合并后是否选中结果节点 */
|
|
52
52
|
const splitSelect = ref(false)
|
|
53
53
|
const mergeTargetId = ref('')
|
|
54
|
-
const mergeKeep = ref('target')
|
|
55
|
-
const rewriteOnConfirm = ref(true)
|
|
56
|
-
const pointOptions = ref([])
|
|
57
|
-
const selectedPoints = ref([])
|
|
58
|
-
const selectionCounts = ref({ points: 0, lines: 0, channels: 0 })
|
|
59
|
-
const selectionFilters = ref({ point: true, line: true, channel: true })
|
|
60
|
-
const logLines = ref([])
|
|
61
|
-
const copyToast = shallowRef(null)
|
|
62
|
-
const lastBatch = shallowRef(null)
|
|
63
|
-
const clipboardItem = shallowRef(null)
|
|
54
|
+
const mergeKeep = ref('target')
|
|
55
|
+
const rewriteOnConfirm = ref(true)
|
|
56
|
+
const pointOptions = ref([])
|
|
57
|
+
const selectedPoints = ref([])
|
|
58
|
+
const selectionCounts = ref({ points: 0, lines: 0, channels: 0 })
|
|
59
|
+
const selectionFilters = ref({ point: true, line: true, channel: true })
|
|
60
|
+
const logLines = ref([])
|
|
61
|
+
const copyToast = shallowRef(null)
|
|
62
|
+
const lastBatch = shallowRef(null)
|
|
63
|
+
const clipboardItem = shallowRef(null)
|
|
64
64
|
const pendingMerge = shallowRef(null)
|
|
65
65
|
const copyEvent = shallowRef(null)
|
|
66
66
|
const fakeBackend = createFakeBackend({ initialVersion: 12, latency: 420, prefix: 'copy-merge' })
|
|
@@ -74,7 +74,7 @@ let splitRound = 0
|
|
|
74
74
|
let mergeSyncTimer = null
|
|
75
75
|
let mergeSyncQueue = []
|
|
76
76
|
let disposed = false
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
const pointLabel = item => String(item?.name ?? item?.label ?? item?.id ?? '节点')
|
|
79
79
|
const pointKey = item => String(item?.id ?? item?.label ?? item?.name ?? '—')
|
|
80
80
|
const parsedMergeMaxKeys = () => mergeMaxKeys.value.split(',').map(key => key.trim()).filter(Boolean)
|
|
@@ -103,7 +103,7 @@ const describeMergeChangeSets = (sets) => {
|
|
|
103
103
|
return `后端事务:删节点 [${points('removedPoint')}] → 更新节点 [${points('point')}] · 更新线 [${ids('lines')}] / 删线 [${ids('removedLines')}] · 更新通道 [${ids('channels')}] / 删通道 [${ids('removedChannels')}]`
|
|
104
104
|
}
|
|
105
105
|
const describeMergeChanges = changes => describeMergeChangeSets(changes ? [changes] : [])
|
|
106
|
-
/** 合并载荷里的影响预演:lineCount 是「改写 + 删除」的线路并集,后两项分别与落地结果同口径 */
|
|
106
|
+
/** 合并载荷里的影响预演:lineCount 是「改写 + 删除」的线路并集,后两项分别与落地结果同口径 */
|
|
107
107
|
const describeMergeImpact = impact => (impact
|
|
108
108
|
? `影响 ${impact.lineCount} 条线路(改线 ${impact.linesChanged} / 删线 ${impact.removedLines})· 通道 改 ${impact.channelsChanged} / 删 ${impact.removedChannels} / 并 ${impact.mergedChannels}`
|
|
109
109
|
: '影响读数:当前数据对不上')
|
|
@@ -392,373 +392,373 @@ const resetBackendDemo = () => {
|
|
|
392
392
|
}
|
|
393
393
|
/** pending / confirm 阶段按 impact 里的 id 调现有 getLine / getChannel 查详情;确认落地前查,删掉的通道仍有数据 */
|
|
394
394
|
const showMergeImpactDetails = () => {
|
|
395
|
-
const payload = pendingMerge.value
|
|
396
|
-
const impact = payload?.impact
|
|
397
|
-
if (!impact) {
|
|
398
|
-
mergeDetailText.value = '合并影响详情:当前没有可用的影响载荷'
|
|
399
|
-
return mergeDetailText.value
|
|
400
|
-
}
|
|
401
|
-
const lines = impact.lineIds.map(id => {
|
|
402
|
-
const line = view?.getLine(id)
|
|
403
|
-
if (!line) {
|
|
404
|
-
return `${id}(已不存在)`
|
|
405
|
-
}
|
|
406
|
-
return `${id}(${line.item?.name ?? '未命名'}:${line.item?.from ?? '—'}→${line.item?.to ?? '—'})`
|
|
407
|
-
})
|
|
408
|
-
const channels = impact.channelIds.map(id => {
|
|
409
|
-
const channel = view?.getChannel(id)
|
|
410
|
-
if (!channel) {
|
|
411
|
-
return `${id}(已不存在)`
|
|
412
|
-
}
|
|
413
|
-
return `${id}(${channel.item?.name ?? '未命名'}:${channel.lineIndexes.length} 回)`
|
|
414
|
-
})
|
|
415
|
-
mergeDetailText.value = `影响线路 ${lines.join(' / ') || '无'} · 影响通道 ${channels.join(' / ') || '无'}`
|
|
416
|
-
return mergeDetailText.value
|
|
417
|
-
}
|
|
418
|
-
const selectionKinds = computed(() => ['point', 'line', 'channel'].filter(kind => selectionFilters.value[kind]))
|
|
419
|
-
const selectionKindsText = computed(() => selectionKinds.value.length ? selectionKinds.value.join(' + ') : '全关')
|
|
420
|
-
const mergeTargetOptions = computed(() => selectedPoints.value.map(item => ({
|
|
421
|
-
value: pointKey(item),
|
|
422
|
-
label: `${pointLabel(item)}(${pointKey(item)})`
|
|
423
|
-
})))
|
|
424
|
-
const canMerge = computed(() => selectedPoints.value.length >= 2)
|
|
425
|
-
const copyToastTitle = computed(() => {
|
|
426
|
-
const payload = copyToast.value
|
|
427
|
-
if (!payload) return ''
|
|
428
|
-
if (payload.ok === false) return '复制没有落地'
|
|
429
|
-
if (payload.phase === 'clipboard') return '剪贴板快照已就绪'
|
|
430
|
-
if (payload.phase === 'batch') return payload.via === 'paste' ? 'Ctrl+V 整组粘贴已落地' : '整组复制已落地'
|
|
431
|
-
if (payload.phase === 'end') return payload.via === 'paste' ? 'Ctrl+V 已粘贴' : 'Alt 复制已落地'
|
|
432
|
-
if (payload.phase === 'commit') return '复制正在提交'
|
|
433
|
-
return '复制预览中'
|
|
434
|
-
})
|
|
435
|
-
const copyToastText = computed(() => {
|
|
436
|
-
const payload = copyToast.value
|
|
437
|
-
if (!payload) return ''
|
|
438
|
-
const sources = payload.sources ?? (payload.source ? [payload.source] : [])
|
|
439
|
-
const items = payload.items ?? (payload.item ? [payload.item] : [])
|
|
440
|
-
const sourceText = sources.slice(0, 4).map(entry => pointLabel(entry.item)).join(' / ') || '—'
|
|
441
|
-
const itemText = items.slice(0, 4).map(pointLabel).join(' / ') || '—'
|
|
442
|
-
const supportCount = payload.supportSources?.length ?? sources.filter(entry => entry.support).length
|
|
443
|
-
return `来源 ${sourceText}${supportCount ? `(含 ${supportCount} 个补端点)` : ''} → 结果 ${itemText} · 线路 ${payload.lines?.length ?? 0} / 通道 ${payload.channels?.length ?? 0}`
|
|
444
|
-
})
|
|
445
|
-
|
|
446
|
-
const pushLog = (name, text) => {
|
|
447
|
-
logSeq += 1
|
|
448
|
-
logLines.value = [{ id: logSeq, name, text }, ...logLines.value].slice(0, 18)
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const focusCanvas = () => nextTick(() => {
|
|
452
|
-
container.value?.querySelector('.gis-view')?.focus({ preventScroll: true })
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
const refreshState = () => {
|
|
456
|
-
if (!view) return
|
|
457
|
-
const data = view.getData()
|
|
458
|
-
pointOptions.value = data.points.map((item, index) => ({
|
|
459
|
-
value: pointKey(item) || String(index),
|
|
460
|
-
label: `${pointLabel(item)}(${pointKey(item)})`
|
|
461
|
-
}))
|
|
462
|
-
const selection = view.getSelectionData()
|
|
463
|
-
selectedPoints.value = selection.points ?? []
|
|
464
|
-
selectionCounts.value = { points: selection.pointCount, lines: selection.lineCount, channels: selection.channelCount }
|
|
465
|
-
selectionText.value = selection.count
|
|
466
|
-
? `${selection.pointCount} 节点 / ${selection.lineCount} 线路 / ${selection.channelCount} 通道:${(selection.items ?? []).slice(0, 5).map(entry => pointLabel(entry.item)).join(' / ')}`
|
|
467
|
-
: '未选中'
|
|
468
|
-
if (!mergeTargetOptions.value.some(item => item.value === mergeTargetId.value)) {
|
|
469
|
-
mergeTargetId.value = mergeTargetOptions.value[0]?.value ?? ''
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const describeBatchCopy = payload => {
|
|
474
|
-
const sourceNames = (payload?.sources ?? []).slice(0, 4).map(entry => pointLabel(entry.item)).join(' / ')
|
|
475
|
-
const supportCount = payload?.supportSources?.length ?? 0
|
|
476
|
-
return `phase=${payload?.phase ?? '—'} · via=${payload?.via ?? '—'} · count=${payload?.count ?? 0} · 来源 ${sourceNames || '—'} · 节点 ${payload?.items?.length ?? 0}${supportCount ? `(补端点 ${supportCount})` : ''} / 线路 ${payload?.lines?.length ?? 0} / 通道 ${payload?.channels?.length ?? 0}`
|
|
477
|
-
}
|
|
478
|
-
|
|
395
|
+
const payload = pendingMerge.value
|
|
396
|
+
const impact = payload?.impact
|
|
397
|
+
if (!impact) {
|
|
398
|
+
mergeDetailText.value = '合并影响详情:当前没有可用的影响载荷'
|
|
399
|
+
return mergeDetailText.value
|
|
400
|
+
}
|
|
401
|
+
const lines = impact.lineIds.map(id => {
|
|
402
|
+
const line = view?.getLine(id)
|
|
403
|
+
if (!line) {
|
|
404
|
+
return `${id}(已不存在)`
|
|
405
|
+
}
|
|
406
|
+
return `${id}(${line.item?.name ?? '未命名'}:${line.item?.from ?? '—'}→${line.item?.to ?? '—'})`
|
|
407
|
+
})
|
|
408
|
+
const channels = impact.channelIds.map(id => {
|
|
409
|
+
const channel = view?.getChannel(id)
|
|
410
|
+
if (!channel) {
|
|
411
|
+
return `${id}(已不存在)`
|
|
412
|
+
}
|
|
413
|
+
return `${id}(${channel.item?.name ?? '未命名'}:${channel.lineIndexes.length} 回)`
|
|
414
|
+
})
|
|
415
|
+
mergeDetailText.value = `影响线路 ${lines.join(' / ') || '无'} · 影响通道 ${channels.join(' / ') || '无'}`
|
|
416
|
+
return mergeDetailText.value
|
|
417
|
+
}
|
|
418
|
+
const selectionKinds = computed(() => ['point', 'line', 'channel'].filter(kind => selectionFilters.value[kind]))
|
|
419
|
+
const selectionKindsText = computed(() => selectionKinds.value.length ? selectionKinds.value.join(' + ') : '全关')
|
|
420
|
+
const mergeTargetOptions = computed(() => selectedPoints.value.map(item => ({
|
|
421
|
+
value: pointKey(item),
|
|
422
|
+
label: `${pointLabel(item)}(${pointKey(item)})`
|
|
423
|
+
})))
|
|
424
|
+
const canMerge = computed(() => selectedPoints.value.length >= 2)
|
|
425
|
+
const copyToastTitle = computed(() => {
|
|
426
|
+
const payload = copyToast.value
|
|
427
|
+
if (!payload) return ''
|
|
428
|
+
if (payload.ok === false) return '复制没有落地'
|
|
429
|
+
if (payload.phase === 'clipboard') return '剪贴板快照已就绪'
|
|
430
|
+
if (payload.phase === 'batch') return payload.via === 'paste' ? 'Ctrl+V 整组粘贴已落地' : '整组复制已落地'
|
|
431
|
+
if (payload.phase === 'end') return payload.via === 'paste' ? 'Ctrl+V 已粘贴' : 'Alt 复制已落地'
|
|
432
|
+
if (payload.phase === 'commit') return '复制正在提交'
|
|
433
|
+
return '复制预览中'
|
|
434
|
+
})
|
|
435
|
+
const copyToastText = computed(() => {
|
|
436
|
+
const payload = copyToast.value
|
|
437
|
+
if (!payload) return ''
|
|
438
|
+
const sources = payload.sources ?? (payload.source ? [payload.source] : [])
|
|
439
|
+
const items = payload.items ?? (payload.item ? [payload.item] : [])
|
|
440
|
+
const sourceText = sources.slice(0, 4).map(entry => pointLabel(entry.item)).join(' / ') || '—'
|
|
441
|
+
const itemText = items.slice(0, 4).map(pointLabel).join(' / ') || '—'
|
|
442
|
+
const supportCount = payload.supportSources?.length ?? sources.filter(entry => entry.support).length
|
|
443
|
+
return `来源 ${sourceText}${supportCount ? `(含 ${supportCount} 个补端点)` : ''} → 结果 ${itemText} · 线路 ${payload.lines?.length ?? 0} / 通道 ${payload.channels?.length ?? 0}`
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
const pushLog = (name, text) => {
|
|
447
|
+
logSeq += 1
|
|
448
|
+
logLines.value = [{ id: logSeq, name, text }, ...logLines.value].slice(0, 18)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const focusCanvas = () => nextTick(() => {
|
|
452
|
+
container.value?.querySelector('.gis-view')?.focus({ preventScroll: true })
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
const refreshState = () => {
|
|
456
|
+
if (!view) return
|
|
457
|
+
const data = view.getData()
|
|
458
|
+
pointOptions.value = data.points.map((item, index) => ({
|
|
459
|
+
value: pointKey(item) || String(index),
|
|
460
|
+
label: `${pointLabel(item)}(${pointKey(item)})`
|
|
461
|
+
}))
|
|
462
|
+
const selection = view.getSelectionData()
|
|
463
|
+
selectedPoints.value = selection.points ?? []
|
|
464
|
+
selectionCounts.value = { points: selection.pointCount, lines: selection.lineCount, channels: selection.channelCount }
|
|
465
|
+
selectionText.value = selection.count
|
|
466
|
+
? `${selection.pointCount} 节点 / ${selection.lineCount} 线路 / ${selection.channelCount} 通道:${(selection.items ?? []).slice(0, 5).map(entry => pointLabel(entry.item)).join(' / ')}`
|
|
467
|
+
: '未选中'
|
|
468
|
+
if (!mergeTargetOptions.value.some(item => item.value === mergeTargetId.value)) {
|
|
469
|
+
mergeTargetId.value = mergeTargetOptions.value[0]?.value ?? ''
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const describeBatchCopy = payload => {
|
|
474
|
+
const sourceNames = (payload?.sources ?? []).slice(0, 4).map(entry => pointLabel(entry.item)).join(' / ')
|
|
475
|
+
const supportCount = payload?.supportSources?.length ?? 0
|
|
476
|
+
return `phase=${payload?.phase ?? '—'} · via=${payload?.via ?? '—'} · count=${payload?.count ?? 0} · 来源 ${sourceNames || '—'} · 节点 ${payload?.items?.length ?? 0}${supportCount ? `(补端点 ${supportCount})` : ''} / 线路 ${payload?.lines?.length ?? 0} / 通道 ${payload?.channels?.length ?? 0}`
|
|
477
|
+
}
|
|
478
|
+
|
|
479
479
|
const rememberCopyPayload = payload => {
|
|
480
480
|
copyEvent.value = payload
|
|
481
481
|
copyLineChannelsText.value = describeLineChannels(payload)
|
|
482
482
|
const sources = payload.sources ?? (payload.source ? [payload.source] : [])
|
|
483
|
-
const items = payload.items ?? (payload.item ? [payload.item] : [])
|
|
484
|
-
copyEventText.value = `phase=${payload.phase ?? '—'} · via=${payload.via ?? '—'} · count=${payload.count ?? items.length}`
|
|
485
|
-
copySourcesText.value = sources.length
|
|
486
|
-
? sources.map(entry => `${pointLabel(entry.item)}(${pointKey(entry.item)}${entry.support ? ' · 补端点' : ''})`).join(' / ')
|
|
487
|
-
: '—'
|
|
488
|
-
copyItemsText.value = items.length ? items.map(pointLabel).join(' / ') : '—'
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const handlePointCopy = (payload) => {
|
|
492
|
-
rememberCopyPayload(payload)
|
|
493
|
-
if (payload.phase === 'batch') {
|
|
494
|
-
if (payload.ok === false) {
|
|
495
|
-
copyText.value = `批量复制失败:${payload.reason}`
|
|
496
|
-
actionText.value = copyText.value
|
|
497
|
-
copyToast.value = payload
|
|
498
|
-
pushLog('pointCopy/batch', copyText.value)
|
|
499
|
-
return
|
|
500
|
-
}
|
|
501
|
-
lastBatch.value = payload
|
|
502
|
-
copyText.value = `${payload.via === 'paste' ? 'pastePoints' : 'copySelectedPoints'}:${describeBatchCopy(payload)}`
|
|
503
|
-
actionText.value = copyText.value
|
|
483
|
+
const items = payload.items ?? (payload.item ? [payload.item] : [])
|
|
484
|
+
copyEventText.value = `phase=${payload.phase ?? '—'} · via=${payload.via ?? '—'} · count=${payload.count ?? items.length}`
|
|
485
|
+
copySourcesText.value = sources.length
|
|
486
|
+
? sources.map(entry => `${pointLabel(entry.item)}(${pointKey(entry.item)}${entry.support ? ' · 补端点' : ''})`).join(' / ')
|
|
487
|
+
: '—'
|
|
488
|
+
copyItemsText.value = items.length ? items.map(pointLabel).join(' / ') : '—'
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const handlePointCopy = (payload) => {
|
|
492
|
+
rememberCopyPayload(payload)
|
|
493
|
+
if (payload.phase === 'batch') {
|
|
494
|
+
if (payload.ok === false) {
|
|
495
|
+
copyText.value = `批量复制失败:${payload.reason}`
|
|
496
|
+
actionText.value = copyText.value
|
|
497
|
+
copyToast.value = payload
|
|
498
|
+
pushLog('pointCopy/batch', copyText.value)
|
|
499
|
+
return
|
|
500
|
+
}
|
|
501
|
+
lastBatch.value = payload
|
|
502
|
+
copyText.value = `${payload.via === 'paste' ? 'pastePoints' : 'copySelectedPoints'}:${describeBatchCopy(payload)}`
|
|
503
|
+
actionText.value = copyText.value
|
|
504
504
|
copyToast.value = payload
|
|
505
505
|
pushLog('pointCopy/batch', copyText.value)
|
|
506
506
|
refreshState()
|
|
507
507
|
startCopyBackend(payload)
|
|
508
508
|
return
|
|
509
|
-
}
|
|
510
|
-
if (payload.phase === 'clipboard') {
|
|
511
|
-
clipboardItem.value = view?.getPointClipboard() ?? null
|
|
512
|
-
clipboardText.value = `剪贴板:count=${payload.count ?? 0} · 节点 ${payload.items?.length ?? 0} / 线路 ${payload.lines?.length ?? 0} / 通道 ${payload.channels?.length ?? 0} · sources=${(payload.sources ?? []).map(entry => pointLabel(entry.item)).join(' / ')}`
|
|
513
|
-
actionText.value = clipboardText.value
|
|
514
|
-
copyToast.value = payload
|
|
515
|
-
pushLog('pointCopy/clipboard', clipboardText.value)
|
|
516
|
-
return
|
|
517
|
-
}
|
|
518
|
-
if (payload.phase === 'start' || payload.phase === 'commit') {
|
|
519
|
-
copyText.value = `复制预览 · 来源 ${pointLabel(payload.source?.item)} → ${pointLabel(payload.item)}`
|
|
520
|
-
copyToast.value = payload
|
|
521
|
-
pushLog(`pointCopy/${payload.phase}`, copyText.value)
|
|
522
|
-
return
|
|
523
|
-
}
|
|
524
|
-
if (payload.phase === 'end') {
|
|
525
|
-
copyText.value = `复制落地 · ${payload.via} · 来源 ${pointLabel(payload.source?.item)} → ${pointLabel(payload.item)}(${pointKey(payload.item)})`
|
|
526
|
-
actionText.value = copyText.value
|
|
509
|
+
}
|
|
510
|
+
if (payload.phase === 'clipboard') {
|
|
511
|
+
clipboardItem.value = view?.getPointClipboard() ?? null
|
|
512
|
+
clipboardText.value = `剪贴板:count=${payload.count ?? 0} · 节点 ${payload.items?.length ?? 0} / 线路 ${payload.lines?.length ?? 0} / 通道 ${payload.channels?.length ?? 0} · sources=${(payload.sources ?? []).map(entry => pointLabel(entry.item)).join(' / ')}`
|
|
513
|
+
actionText.value = clipboardText.value
|
|
514
|
+
copyToast.value = payload
|
|
515
|
+
pushLog('pointCopy/clipboard', clipboardText.value)
|
|
516
|
+
return
|
|
517
|
+
}
|
|
518
|
+
if (payload.phase === 'start' || payload.phase === 'commit') {
|
|
519
|
+
copyText.value = `复制预览 · 来源 ${pointLabel(payload.source?.item)} → ${pointLabel(payload.item)}`
|
|
520
|
+
copyToast.value = payload
|
|
521
|
+
pushLog(`pointCopy/${payload.phase}`, copyText.value)
|
|
522
|
+
return
|
|
523
|
+
}
|
|
524
|
+
if (payload.phase === 'end') {
|
|
525
|
+
copyText.value = `复制落地 · ${payload.via} · 来源 ${pointLabel(payload.source?.item)} → ${pointLabel(payload.item)}(${pointKey(payload.item)})`
|
|
526
|
+
actionText.value = copyText.value
|
|
527
527
|
copyToast.value = payload
|
|
528
528
|
pushLog('pointCopy/end', copyText.value)
|
|
529
529
|
refreshState()
|
|
530
530
|
startCopyBackend(payload)
|
|
531
531
|
return
|
|
532
|
-
}
|
|
533
|
-
if (payload.phase === 'cancel') {
|
|
534
|
-
copyText.value = payload.invalidated ? '复制作废:数据已换过' : '复制已取消,预览没有落地'
|
|
535
|
-
actionText.value = copyText.value
|
|
536
|
-
copyToast.value = null
|
|
537
|
-
pushLog('pointCopy/cancel', copyText.value)
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
const sourceIds = () => {
|
|
542
|
-
const points = view?.getData().points ?? []
|
|
543
|
-
const indexes = [0, 7, 12, 5]
|
|
544
|
-
const picked = indexes.map(index => points[index % points.length]?.id).filter(id => id !== undefined && id !== null)
|
|
545
|
-
return [...new Set(picked)]
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const selectBatchSources = () => {
|
|
549
|
-
const ids = sourceIds()
|
|
532
|
+
}
|
|
533
|
+
if (payload.phase === 'cancel') {
|
|
534
|
+
copyText.value = payload.invalidated ? '复制作废:数据已换过' : '复制已取消,预览没有落地'
|
|
535
|
+
actionText.value = copyText.value
|
|
536
|
+
copyToast.value = null
|
|
537
|
+
pushLog('pointCopy/cancel', copyText.value)
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const sourceIds = () => {
|
|
542
|
+
const points = view?.getData().points ?? []
|
|
543
|
+
const indexes = [0, 7, 12, 5]
|
|
544
|
+
const picked = indexes.map(index => points[index % points.length]?.id).filter(id => id !== undefined && id !== null)
|
|
545
|
+
return [...new Set(picked)]
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const selectBatchSources = () => {
|
|
549
|
+
const ids = sourceIds()
|
|
550
550
|
view.selectFeatures(ids)
|
|
551
|
-
refreshState()
|
|
552
|
-
actionText.value = `已选中 ${ids.length} 个来源:${ids.join(' / ')}(画布上也已高亮)`
|
|
553
|
-
pushLog('select', actionText.value)
|
|
554
|
-
return actionText.value
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
const clearSelection = () => {
|
|
551
|
+
refreshState()
|
|
552
|
+
actionText.value = `已选中 ${ids.length} 个来源:${ids.join(' / ')}(画布上也已高亮)`
|
|
553
|
+
pushLog('select', actionText.value)
|
|
554
|
+
return actionText.value
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const clearSelection = () => {
|
|
558
558
|
view.selectFeatures(null)
|
|
559
|
-
refreshState()
|
|
560
|
-
actionText.value = '已清空选中;现在可拖动单个节点撞向另一个节点'
|
|
561
|
-
return actionText.value
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const applySelectionKinds = () => {
|
|
565
|
-
view?.updateOptions({ selectionKinds: selectionKinds.value })
|
|
566
|
-
actionText.value = `框选 / Ctrl 多选类型:${selectionKindsText.value}`
|
|
567
|
-
pushLog('selectionKinds', actionText.value)
|
|
568
|
-
return actionText.value
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
const selectSampleLine = () => {
|
|
572
|
-
view.selectFeatures({ type: 'line', index: 0 })
|
|
573
|
-
refreshState()
|
|
574
|
-
actionText.value = '已选中示例独立线路(不含两端节点);复制会自动补两个端点节点并带上线路'
|
|
575
|
-
pushLog('select/line', actionText.value)
|
|
576
|
-
return actionText.value
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
const selectSampleChannel = () => {
|
|
580
|
-
view.selectFeatures({ type: 'channel', channelId: 'channel-a' })
|
|
581
|
-
refreshState()
|
|
582
|
-
actionText.value = '已选中甲通道整条(全部回线);复制会同时补端点、线路与通道'
|
|
583
|
-
pushLog('select/channel', actionText.value)
|
|
584
|
-
return actionText.value
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
const ensureBatchSelection = () => {
|
|
588
|
-
const current = view?.getSelectionData().points ?? []
|
|
589
|
-
if (current.length) return current
|
|
559
|
+
refreshState()
|
|
560
|
+
actionText.value = '已清空选中;现在可拖动单个节点撞向另一个节点'
|
|
561
|
+
return actionText.value
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const applySelectionKinds = () => {
|
|
565
|
+
view?.updateOptions({ selectionKinds: selectionKinds.value })
|
|
566
|
+
actionText.value = `框选 / Ctrl 多选类型:${selectionKindsText.value}`
|
|
567
|
+
pushLog('selectionKinds', actionText.value)
|
|
568
|
+
return actionText.value
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const selectSampleLine = () => {
|
|
572
|
+
view.selectFeatures({ type: 'line', index: 0 })
|
|
573
|
+
refreshState()
|
|
574
|
+
actionText.value = '已选中示例独立线路(不含两端节点);复制会自动补两个端点节点并带上线路'
|
|
575
|
+
pushLog('select/line', actionText.value)
|
|
576
|
+
return actionText.value
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const selectSampleChannel = () => {
|
|
580
|
+
view.selectFeatures({ type: 'channel', channelId: 'channel-a' })
|
|
581
|
+
refreshState()
|
|
582
|
+
actionText.value = '已选中甲通道整条(全部回线);复制会同时补端点、线路与通道'
|
|
583
|
+
pushLog('select/channel', actionText.value)
|
|
584
|
+
return actionText.value
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const ensureBatchSelection = () => {
|
|
588
|
+
const current = view?.getSelectionData().points ?? []
|
|
589
|
+
if (current.length) return current
|
|
590
590
|
view.selectFeatures(sourceIds())
|
|
591
|
-
refreshState()
|
|
592
|
-
return selectedPoints.value
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
const copySelectedByApi = () => {
|
|
596
|
-
if (!view.getSelectionData().count) ensureBatchSelection()
|
|
597
|
-
copyRound += 1
|
|
598
|
-
const result = view.copySelectedPoints({
|
|
599
|
-
offset: [64 + copyRound * 12, 46 + copyRound * 8],
|
|
600
|
-
select: true,
|
|
601
|
-
label: `demo 框选复制 ${copyRound}`
|
|
602
|
-
})
|
|
603
|
-
if (result.ok) {
|
|
604
|
-
lastBatch.value = result
|
|
605
|
-
copyText.value = `copySelectedPoints:${describeBatchCopy(result)}`
|
|
606
|
-
actionText.value = copyText.value
|
|
607
|
-
}
|
|
608
|
-
else {
|
|
609
|
-
copyText.value = `copySelectedPoints 失败:${result.reason}`
|
|
610
|
-
actionText.value = copyText.value
|
|
611
|
-
}
|
|
612
|
-
refreshState()
|
|
613
|
-
return copyText.value
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
const copyPointsByApi = () => {
|
|
617
|
-
copyRound += 1
|
|
618
|
-
const result = view.copyPoints(sourceIds(), {
|
|
619
|
-
offset: [74 + copyRound * 12, 58 + copyRound * 8],
|
|
620
|
-
select: true,
|
|
621
|
-
label: `demo copyPoints ${copyRound}`
|
|
622
|
-
})
|
|
623
|
-
if (result.ok) {
|
|
624
|
-
lastBatch.value = result
|
|
625
|
-
copyText.value = `copyPoints:${describeBatchCopy(result)}`
|
|
626
|
-
actionText.value = copyText.value
|
|
627
|
-
}
|
|
628
|
-
else {
|
|
629
|
-
copyText.value = `copyPoints 失败:${result.reason}`
|
|
630
|
-
actionText.value = copyText.value
|
|
631
|
-
}
|
|
632
|
-
refreshState()
|
|
633
|
-
return copyText.value
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
const copyClipboardByApi = () => {
|
|
637
|
-
if (!view.getSelectionData().count) ensureBatchSelection()
|
|
638
|
-
const item = view.copyPointsToClipboard(undefined, { via: 'demo-copy-clipboard' })
|
|
639
|
-
clipboardItem.value = item
|
|
640
|
-
clipboardText.value = item
|
|
641
|
-
? `剪贴板:count=${item.count} · 节点 ${item.items.length}${item.supportSources?.length ? `(补端点 ${item.supportSources.length})` : ''} / 线路 ${item.lines.length} / 通道 ${item.channels.length} · sources=${item.sources.map(entry => `${pointLabel(entry.item)}${entry.support ? '·补端点' : ''}`).join(' / ')}`
|
|
642
|
-
: '剪贴板为空'
|
|
643
|
-
actionText.value = item ? `${clipboardText.value};焦点已交给画布,按 Ctrl+V 或点 pastePoints` : '剪贴板为空'
|
|
644
|
-
focusCanvas()
|
|
645
|
-
refreshState()
|
|
646
|
-
return clipboardText.value
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
const pasteFromClipboard = () => {
|
|
650
|
-
clipboardItem.value = view.getPointClipboard() ?? clipboardItem.value
|
|
651
|
-
if (!clipboardItem.value) return 'pastePoints:先复制到剪贴板'
|
|
652
|
-
pasteRound += 1
|
|
653
|
-
const anchor = clipboardItem.value.anchor ?? [0, 0]
|
|
654
|
-
const result = view.pastePoints({
|
|
655
|
-
coordinate: [anchor[0] + 1.05 + pasteRound * 0.18, anchor[1] - 0.72 - pasteRound * 0.12],
|
|
656
|
-
select: true,
|
|
657
|
-
label: `demo 粘贴 ${pasteRound}`
|
|
658
|
-
})
|
|
659
|
-
if (result?.ok) {
|
|
660
|
-
lastBatch.value = result
|
|
661
|
-
copyText.value = `pastePoints:${describeBatchCopy(result)}`
|
|
662
|
-
actionText.value = copyText.value
|
|
663
|
-
}
|
|
664
|
-
else {
|
|
665
|
-
copyText.value = `pastePoints 失败:${result?.reason ?? '剪贴板为空或复制已关闭'}`
|
|
666
|
-
actionText.value = copyText.value
|
|
667
|
-
}
|
|
668
|
-
refreshState()
|
|
669
|
-
return copyText.value
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
const revertLastCopy = () => {
|
|
673
|
-
const payload = copyToast.value ?? copyEvent.value
|
|
674
|
-
if (!payload) return '没有可取消 / 回滚的复制'
|
|
675
|
-
let message = '复制已经不在数据里'
|
|
676
|
-
if (payload.phase === 'clipboard') {
|
|
677
|
-
payload.cancel?.()
|
|
678
|
-
clipboardItem.value = null
|
|
679
|
-
message = '已清空剪贴板'
|
|
680
|
-
}
|
|
681
|
-
else if (typeof payload.revert === 'function') {
|
|
682
|
-
const outcome = payload.revert()
|
|
683
|
-
message = outcome?.ok
|
|
684
|
-
? `${payload.phase === 'batch' ? '已撤销整组复制' : '已回滚本次复制'} · 收回 ${outcome.items?.length ?? outcome.count ?? 0} 个节点`
|
|
685
|
-
: '复制已经不在数据里'
|
|
686
|
-
}
|
|
687
|
-
else if (typeof payload.cancel === 'function') {
|
|
688
|
-
payload.cancel()
|
|
689
|
-
message = '已取消这次复制预览,数据没有落地'
|
|
690
|
-
}
|
|
691
|
-
copyToast.value = null
|
|
692
|
-
if (lastBatch.value === payload) lastBatch.value = null
|
|
693
|
-
actionText.value = message
|
|
694
|
-
pushLog('copy/revert', message)
|
|
695
|
-
refreshState()
|
|
696
|
-
return message
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const manualAddPointCopy = () => {
|
|
700
|
-
const previous = lastBatch.value ?? (copyToast.value?.phase === 'batch' || copyToast.value?.phase === 'end' ? copyToast.value : null)
|
|
701
|
-
let reverted = 0
|
|
702
|
-
if (previous && typeof previous.revert === 'function') {
|
|
703
|
-
const outcome = previous.revert()
|
|
704
|
-
reverted = outcome?.items?.length ?? outcome?.count ?? 0
|
|
705
|
-
if (lastBatch.value === previous) lastBatch.value = null
|
|
706
|
-
if (copyToast.value === previous) copyToast.value = null
|
|
707
|
-
}
|
|
708
|
-
if (!view.getSelectionData().pointCount) ensureBatchSelection()
|
|
709
|
-
if (!view.getSelectionData().pointCount) return '手动 addPoint 只接管节点复制:先选中至少一个节点'
|
|
710
|
-
addPointRound += 1
|
|
711
|
-
// 先让内置复制算出一份真实落点(同时不带上选中线路 / 通道),拿到 items 后立刻整批撤销,
|
|
712
|
-
// 再按这些落点逐个 addPoint:最终数据里只有手动副本,不会残留自动副本。
|
|
713
|
-
const probe = view.copySelectedPoints({
|
|
714
|
-
offset: [84 + addPointRound * 10, 54 + addPointRound * 8],
|
|
715
|
-
withLines: false,
|
|
716
|
-
withChannels: false,
|
|
717
|
-
select: false,
|
|
718
|
-
via: 'manual-addpoint-probe',
|
|
719
|
-
label: 'demo 手动复制落点'
|
|
720
|
-
})
|
|
721
|
-
if (!probe?.ok) {
|
|
722
|
-
manualText.value = `手动 addPoint 准备失败:${probe?.reason ?? '复制没有落地'}`
|
|
723
|
-
actionText.value = manualText.value
|
|
724
|
-
return manualText.value
|
|
725
|
-
}
|
|
726
|
-
const placements = probe.items.map(item => ({ ...item }))
|
|
727
|
-
const probeSources = probe.sources.filter(entry => !entry.support)
|
|
728
|
-
probe.revert()
|
|
729
|
-
lastBatch.value = null
|
|
730
|
-
copyToast.value = null
|
|
731
|
-
const created = []
|
|
732
|
-
for (let index = 0; index < placements.length; index += 1) {
|
|
733
|
-
const placement = placements[index]
|
|
734
|
-
const source = probeSources[index]
|
|
735
|
-
const { id: _oldId, ...rest } = placement
|
|
736
|
-
const result = view.addPoint({
|
|
737
|
-
...rest,
|
|
738
|
-
id: `addpoint-copy-${addPointRound}-${index + 1}`,
|
|
739
|
-
name: `${pointLabel(source?.item ?? placement)} · addPoint 副本`,
|
|
740
|
-
zone_name: 'addPoint 手动复制',
|
|
741
|
-
demo_copy: true,
|
|
742
|
-
demo_copy_via: 'addPoint'
|
|
743
|
-
})
|
|
744
|
-
if (result.ok && result.item) created.push(result.item)
|
|
745
|
-
}
|
|
591
|
+
refreshState()
|
|
592
|
+
return selectedPoints.value
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const copySelectedByApi = () => {
|
|
596
|
+
if (!view.getSelectionData().count) ensureBatchSelection()
|
|
597
|
+
copyRound += 1
|
|
598
|
+
const result = view.copySelectedPoints({
|
|
599
|
+
offset: [64 + copyRound * 12, 46 + copyRound * 8],
|
|
600
|
+
select: true,
|
|
601
|
+
label: `demo 框选复制 ${copyRound}`
|
|
602
|
+
})
|
|
603
|
+
if (result.ok) {
|
|
604
|
+
lastBatch.value = result
|
|
605
|
+
copyText.value = `copySelectedPoints:${describeBatchCopy(result)}`
|
|
606
|
+
actionText.value = copyText.value
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
copyText.value = `copySelectedPoints 失败:${result.reason}`
|
|
610
|
+
actionText.value = copyText.value
|
|
611
|
+
}
|
|
612
|
+
refreshState()
|
|
613
|
+
return copyText.value
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const copyPointsByApi = () => {
|
|
617
|
+
copyRound += 1
|
|
618
|
+
const result = view.copyPoints(sourceIds(), {
|
|
619
|
+
offset: [74 + copyRound * 12, 58 + copyRound * 8],
|
|
620
|
+
select: true,
|
|
621
|
+
label: `demo copyPoints ${copyRound}`
|
|
622
|
+
})
|
|
623
|
+
if (result.ok) {
|
|
624
|
+
lastBatch.value = result
|
|
625
|
+
copyText.value = `copyPoints:${describeBatchCopy(result)}`
|
|
626
|
+
actionText.value = copyText.value
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
copyText.value = `copyPoints 失败:${result.reason}`
|
|
630
|
+
actionText.value = copyText.value
|
|
631
|
+
}
|
|
632
|
+
refreshState()
|
|
633
|
+
return copyText.value
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const copyClipboardByApi = () => {
|
|
637
|
+
if (!view.getSelectionData().count) ensureBatchSelection()
|
|
638
|
+
const item = view.copyPointsToClipboard(undefined, { via: 'demo-copy-clipboard' })
|
|
639
|
+
clipboardItem.value = item
|
|
640
|
+
clipboardText.value = item
|
|
641
|
+
? `剪贴板:count=${item.count} · 节点 ${item.items.length}${item.supportSources?.length ? `(补端点 ${item.supportSources.length})` : ''} / 线路 ${item.lines.length} / 通道 ${item.channels.length} · sources=${item.sources.map(entry => `${pointLabel(entry.item)}${entry.support ? '·补端点' : ''}`).join(' / ')}`
|
|
642
|
+
: '剪贴板为空'
|
|
643
|
+
actionText.value = item ? `${clipboardText.value};焦点已交给画布,按 Ctrl+V 或点 pastePoints` : '剪贴板为空'
|
|
644
|
+
focusCanvas()
|
|
645
|
+
refreshState()
|
|
646
|
+
return clipboardText.value
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const pasteFromClipboard = () => {
|
|
650
|
+
clipboardItem.value = view.getPointClipboard() ?? clipboardItem.value
|
|
651
|
+
if (!clipboardItem.value) return 'pastePoints:先复制到剪贴板'
|
|
652
|
+
pasteRound += 1
|
|
653
|
+
const anchor = clipboardItem.value.anchor ?? [0, 0]
|
|
654
|
+
const result = view.pastePoints({
|
|
655
|
+
coordinate: [anchor[0] + 1.05 + pasteRound * 0.18, anchor[1] - 0.72 - pasteRound * 0.12],
|
|
656
|
+
select: true,
|
|
657
|
+
label: `demo 粘贴 ${pasteRound}`
|
|
658
|
+
})
|
|
659
|
+
if (result?.ok) {
|
|
660
|
+
lastBatch.value = result
|
|
661
|
+
copyText.value = `pastePoints:${describeBatchCopy(result)}`
|
|
662
|
+
actionText.value = copyText.value
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
copyText.value = `pastePoints 失败:${result?.reason ?? '剪贴板为空或复制已关闭'}`
|
|
666
|
+
actionText.value = copyText.value
|
|
667
|
+
}
|
|
668
|
+
refreshState()
|
|
669
|
+
return copyText.value
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const revertLastCopy = () => {
|
|
673
|
+
const payload = copyToast.value ?? copyEvent.value
|
|
674
|
+
if (!payload) return '没有可取消 / 回滚的复制'
|
|
675
|
+
let message = '复制已经不在数据里'
|
|
676
|
+
if (payload.phase === 'clipboard') {
|
|
677
|
+
payload.cancel?.()
|
|
678
|
+
clipboardItem.value = null
|
|
679
|
+
message = '已清空剪贴板'
|
|
680
|
+
}
|
|
681
|
+
else if (typeof payload.revert === 'function') {
|
|
682
|
+
const outcome = payload.revert()
|
|
683
|
+
message = outcome?.ok
|
|
684
|
+
? `${payload.phase === 'batch' ? '已撤销整组复制' : '已回滚本次复制'} · 收回 ${outcome.items?.length ?? outcome.count ?? 0} 个节点`
|
|
685
|
+
: '复制已经不在数据里'
|
|
686
|
+
}
|
|
687
|
+
else if (typeof payload.cancel === 'function') {
|
|
688
|
+
payload.cancel()
|
|
689
|
+
message = '已取消这次复制预览,数据没有落地'
|
|
690
|
+
}
|
|
691
|
+
copyToast.value = null
|
|
692
|
+
if (lastBatch.value === payload) lastBatch.value = null
|
|
693
|
+
actionText.value = message
|
|
694
|
+
pushLog('copy/revert', message)
|
|
695
|
+
refreshState()
|
|
696
|
+
return message
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const manualAddPointCopy = () => {
|
|
700
|
+
const previous = lastBatch.value ?? (copyToast.value?.phase === 'batch' || copyToast.value?.phase === 'end' ? copyToast.value : null)
|
|
701
|
+
let reverted = 0
|
|
702
|
+
if (previous && typeof previous.revert === 'function') {
|
|
703
|
+
const outcome = previous.revert()
|
|
704
|
+
reverted = outcome?.items?.length ?? outcome?.count ?? 0
|
|
705
|
+
if (lastBatch.value === previous) lastBatch.value = null
|
|
706
|
+
if (copyToast.value === previous) copyToast.value = null
|
|
707
|
+
}
|
|
708
|
+
if (!view.getSelectionData().pointCount) ensureBatchSelection()
|
|
709
|
+
if (!view.getSelectionData().pointCount) return '手动 addPoint 只接管节点复制:先选中至少一个节点'
|
|
710
|
+
addPointRound += 1
|
|
711
|
+
// 先让内置复制算出一份真实落点(同时不带上选中线路 / 通道),拿到 items 后立刻整批撤销,
|
|
712
|
+
// 再按这些落点逐个 addPoint:最终数据里只有手动副本,不会残留自动副本。
|
|
713
|
+
const probe = view.copySelectedPoints({
|
|
714
|
+
offset: [84 + addPointRound * 10, 54 + addPointRound * 8],
|
|
715
|
+
withLines: false,
|
|
716
|
+
withChannels: false,
|
|
717
|
+
select: false,
|
|
718
|
+
via: 'manual-addpoint-probe',
|
|
719
|
+
label: 'demo 手动复制落点'
|
|
720
|
+
})
|
|
721
|
+
if (!probe?.ok) {
|
|
722
|
+
manualText.value = `手动 addPoint 准备失败:${probe?.reason ?? '复制没有落地'}`
|
|
723
|
+
actionText.value = manualText.value
|
|
724
|
+
return manualText.value
|
|
725
|
+
}
|
|
726
|
+
const placements = probe.items.map(item => ({ ...item }))
|
|
727
|
+
const probeSources = probe.sources.filter(entry => !entry.support)
|
|
728
|
+
probe.revert()
|
|
729
|
+
lastBatch.value = null
|
|
730
|
+
copyToast.value = null
|
|
731
|
+
const created = []
|
|
732
|
+
for (let index = 0; index < placements.length; index += 1) {
|
|
733
|
+
const placement = placements[index]
|
|
734
|
+
const source = probeSources[index]
|
|
735
|
+
const { id: _oldId, ...rest } = placement
|
|
736
|
+
const result = view.addPoint({
|
|
737
|
+
...rest,
|
|
738
|
+
id: `addpoint-copy-${addPointRound}-${index + 1}`,
|
|
739
|
+
name: `${pointLabel(source?.item ?? placement)} · addPoint 副本`,
|
|
740
|
+
zone_name: 'addPoint 手动复制',
|
|
741
|
+
demo_copy: true,
|
|
742
|
+
demo_copy_via: 'addPoint'
|
|
743
|
+
})
|
|
744
|
+
if (result.ok && result.item) created.push(result.item)
|
|
745
|
+
}
|
|
746
746
|
view.selectFeatures(created.map(item => item.id))
|
|
747
|
-
manualText.value = `addPoint ×${created.length}:${created.map(item => `${pointLabel(item)}(${pointKey(item)})`).join(' / ') || '没有落地'}${reverted ? ` · 已删自动副本 ${reverted} 个` : ''} · 最终只多一份`
|
|
748
|
-
actionText.value = manualText.value
|
|
749
|
-
pushLog('addPoint', manualText.value)
|
|
750
|
-
refreshState()
|
|
751
|
-
return manualText.value
|
|
752
|
-
}
|
|
753
|
-
|
|
747
|
+
manualText.value = `addPoint ×${created.length}:${created.map(item => `${pointLabel(item)}(${pointKey(item)})`).join(' / ') || '没有落地'}${reverted ? ` · 已删自动副本 ${reverted} 个` : ''} · 最终只多一份`
|
|
748
|
+
actionText.value = manualText.value
|
|
749
|
+
pushLog('addPoint', manualText.value)
|
|
750
|
+
refreshState()
|
|
751
|
+
return manualText.value
|
|
752
|
+
}
|
|
753
|
+
|
|
754
754
|
const prepareMerge = () => {
|
|
755
|
-
if (!selectedPoints.value.length) selectBatchSources()
|
|
756
|
-
if (!mergeTargetOptions.value.some(item => item.value === mergeTargetId.value)) {
|
|
757
|
-
mergeTargetId.value = mergeTargetOptions.value[0]?.value ?? ''
|
|
758
|
-
}
|
|
759
|
-
refreshState()
|
|
760
|
-
actionText.value = `已准备 ${selectedPoints.value.length} 个选中节点;保留目标 ${mergeTargetId.value || '—'},可在画布继续 Ctrl 点选`
|
|
761
|
-
pushLog('merge/prepare', actionText.value)
|
|
755
|
+
if (!selectedPoints.value.length) selectBatchSources()
|
|
756
|
+
if (!mergeTargetOptions.value.some(item => item.value === mergeTargetId.value)) {
|
|
757
|
+
mergeTargetId.value = mergeTargetOptions.value[0]?.value ?? ''
|
|
758
|
+
}
|
|
759
|
+
refreshState()
|
|
760
|
+
actionText.value = `已准备 ${selectedPoints.value.length} 个选中节点;保留目标 ${mergeTargetId.value || '—'},可在画布继续 Ctrl 点选`
|
|
761
|
+
pushLog('merge/prepare', actionText.value)
|
|
762
762
|
return actionText.value
|
|
763
763
|
}
|
|
764
764
|
|
|
@@ -823,48 +823,48 @@ const splitSelectedPoint = () => {
|
|
|
823
823
|
refreshState()
|
|
824
824
|
return splitText.value
|
|
825
825
|
}
|
|
826
|
-
|
|
827
|
-
const mergePatch = () => {
|
|
828
|
-
const patch = {
|
|
829
|
-
name: mergedName.value.trim() || '合并后节点',
|
|
830
|
-
zone_name: mergedZone.value.trim() || '合并后(item 改写)',
|
|
831
|
-
demo_merged: true
|
|
832
|
-
}
|
|
833
|
-
const voltage = Number(mergedVoltage.value)
|
|
834
|
-
if (Number.isFinite(voltage)) patch.vn_kv = voltage
|
|
835
|
-
const size = Number(mergedSize.value)
|
|
836
|
-
if (Number.isFinite(size) && size > 0) patch.size = size
|
|
837
|
-
if (mergedColor.value.trim()) patch.color = mergedColor.value.trim()
|
|
838
|
-
if (mergedSymbol.value.trim()) patch.symbol = mergedSymbol.value.trim()
|
|
839
|
-
if (mergedStrokeColor.value.trim()) patch.strokeColor = mergedStrokeColor.value.trim()
|
|
840
|
-
return patch
|
|
841
|
-
}
|
|
842
|
-
|
|
826
|
+
|
|
827
|
+
const mergePatch = () => {
|
|
828
|
+
const patch = {
|
|
829
|
+
name: mergedName.value.trim() || '合并后节点',
|
|
830
|
+
zone_name: mergedZone.value.trim() || '合并后(item 改写)',
|
|
831
|
+
demo_merged: true
|
|
832
|
+
}
|
|
833
|
+
const voltage = Number(mergedVoltage.value)
|
|
834
|
+
if (Number.isFinite(voltage)) patch.vn_kv = voltage
|
|
835
|
+
const size = Number(mergedSize.value)
|
|
836
|
+
if (Number.isFinite(size) && size > 0) patch.size = size
|
|
837
|
+
if (mergedColor.value.trim()) patch.color = mergedColor.value.trim()
|
|
838
|
+
if (mergedSymbol.value.trim()) patch.symbol = mergedSymbol.value.trim()
|
|
839
|
+
if (mergedStrokeColor.value.trim()) patch.strokeColor = mergedStrokeColor.value.trim()
|
|
840
|
+
return patch
|
|
841
|
+
}
|
|
842
|
+
|
|
843
843
|
const mergeSelectedByApi = () => {
|
|
844
|
-
const points = selectedPoints.value
|
|
845
|
-
if (points.length < 2) return 'mergeSelectedPoints:至少选中两个节点'
|
|
846
|
-
const targetId = mergeTargetOptions.value.some(item => item.value === mergeTargetId.value)
|
|
847
|
-
? mergeTargetId.value
|
|
848
|
-
: mergeTargetOptions.value[0].value
|
|
849
|
-
const target = points.find(item => pointKey(item) === targetId)
|
|
850
|
-
const sources = points.filter(item => pointKey(item) !== targetId).map(item => item.id)
|
|
851
|
-
const result = view.mergeSelectedPoints({
|
|
852
|
-
target: { id: target?.id ?? targetId },
|
|
853
|
-
sources,
|
|
854
|
-
item: mergePatch(),
|
|
855
|
-
label: 'demo 合并选中节点'
|
|
856
|
-
})
|
|
857
|
-
if (result.ok) {
|
|
844
|
+
const points = selectedPoints.value
|
|
845
|
+
if (points.length < 2) return 'mergeSelectedPoints:至少选中两个节点'
|
|
846
|
+
const targetId = mergeTargetOptions.value.some(item => item.value === mergeTargetId.value)
|
|
847
|
+
? mergeTargetId.value
|
|
848
|
+
: mergeTargetOptions.value[0].value
|
|
849
|
+
const target = points.find(item => pointKey(item) === targetId)
|
|
850
|
+
const sources = points.filter(item => pointKey(item) !== targetId).map(item => item.id)
|
|
851
|
+
const result = view.mergeSelectedPoints({
|
|
852
|
+
target: { id: target?.id ?? targetId },
|
|
853
|
+
sources,
|
|
854
|
+
item: mergePatch(),
|
|
855
|
+
label: 'demo 合并选中节点'
|
|
856
|
+
})
|
|
857
|
+
if (result.ok) {
|
|
858
858
|
view.selectFeatures(target?.id ?? targetId)
|
|
859
|
-
mergeText.value = `mergeSelectedPoints · 保留 ${pointLabel(result.target?.item)} · 并掉 ${result.sourcesMerged} 个 · 改线 ${result.linesChanged} / 删线 ${result.removedLines} / 并通道 ${result.mergedChannels ?? 0} · 最终 ${pointLabel(result.target?.item)}`
|
|
860
|
-
pendingText.value = '这次走 API 直接合并,没有 pending 阶段'
|
|
861
|
-
}
|
|
862
|
-
else {
|
|
863
|
-
mergeText.value = `mergeSelectedPoints 失败:${result.reason}`
|
|
864
|
-
}
|
|
865
|
-
actionText.value = mergeText.value
|
|
866
|
-
pushLog('mergeSelectedPoints', mergeText.value)
|
|
867
|
-
refreshState()
|
|
859
|
+
mergeText.value = `mergeSelectedPoints · 保留 ${pointLabel(result.target?.item)} · 并掉 ${result.sourcesMerged} 个 · 改线 ${result.linesChanged} / 删线 ${result.removedLines} / 并通道 ${result.mergedChannels ?? 0} · 最终 ${pointLabel(result.target?.item)}`
|
|
860
|
+
pendingText.value = '这次走 API 直接合并,没有 pending 阶段'
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
mergeText.value = `mergeSelectedPoints 失败:${result.reason}`
|
|
864
|
+
}
|
|
865
|
+
actionText.value = mergeText.value
|
|
866
|
+
pushLog('mergeSelectedPoints', mergeText.value)
|
|
867
|
+
refreshState()
|
|
868
868
|
return mergeText.value
|
|
869
869
|
}
|
|
870
870
|
|
|
@@ -918,34 +918,34 @@ const mergeSelectedByDefault = () => {
|
|
|
918
918
|
refreshState()
|
|
919
919
|
return mergeText.value
|
|
920
920
|
}
|
|
921
|
-
|
|
922
|
-
const handlePointMerge = (payload) => {
|
|
921
|
+
|
|
922
|
+
const handlePointMerge = (payload) => {
|
|
923
923
|
if (payload.phase === 'pending') {
|
|
924
924
|
pendingMerge.value = payload
|
|
925
925
|
mergeBackendText.value = '合并变更 → 后端事务:数据还没动;确认落地后 changes 会给出完整事务入参'
|
|
926
|
-
mergeDetailText.value = '合并影响详情:点「查看影响详情」按 id 调 getLine / getChannel'
|
|
927
|
-
pendingText.value = `待确认:拖动来源 ${pointLabel(payload.source.item)} → 碰撞目标 ${pointLabel(payload.target.item)} · ${describeMergeImpact(payload.impact)} · 数据还没动`
|
|
928
|
-
mergeText.value = pendingText.value
|
|
929
|
-
actionText.value = mergeText.value
|
|
930
|
-
pushLog('pointMerge/pending', pendingText.value)
|
|
931
|
-
return
|
|
932
|
-
}
|
|
933
|
-
if (payload.phase === 'confirm') {
|
|
934
|
-
if (rewriteOnConfirm.value && payload.item) {
|
|
935
|
-
payload.item = { ...payload.item, ...mergePatch() }
|
|
936
|
-
}
|
|
937
|
-
pendingMerge.value = payload
|
|
938
|
-
mergeText.value = `confirm · 保留 ${payload.keep === 'source' ? '拖动来源' : '碰撞目标'} · ${describeMergeImpact(payload.impact)} · 来源 ${pointLabel(payload.source.item)} / 目标 ${pointLabel(payload.target.item)} · 最终节点 ${pointLabel(payload.item)} · 色 ${payload.item?.color ?? '—'} / 尺寸 ${payload.item?.size ?? '—'} / 图标 ${String(payload.item?.symbol ?? '—')}`
|
|
939
|
-
actionText.value = mergeText.value
|
|
940
|
-
pushLog('pointMerge/confirm', mergeText.value)
|
|
941
|
-
return
|
|
942
|
-
}
|
|
926
|
+
mergeDetailText.value = '合并影响详情:点「查看影响详情」按 id 调 getLine / getChannel'
|
|
927
|
+
pendingText.value = `待确认:拖动来源 ${pointLabel(payload.source.item)} → 碰撞目标 ${pointLabel(payload.target.item)} · ${describeMergeImpact(payload.impact)} · 数据还没动`
|
|
928
|
+
mergeText.value = pendingText.value
|
|
929
|
+
actionText.value = mergeText.value
|
|
930
|
+
pushLog('pointMerge/pending', pendingText.value)
|
|
931
|
+
return
|
|
932
|
+
}
|
|
933
|
+
if (payload.phase === 'confirm') {
|
|
934
|
+
if (rewriteOnConfirm.value && payload.item) {
|
|
935
|
+
payload.item = { ...payload.item, ...mergePatch() }
|
|
936
|
+
}
|
|
937
|
+
pendingMerge.value = payload
|
|
938
|
+
mergeText.value = `confirm · 保留 ${payload.keep === 'source' ? '拖动来源' : '碰撞目标'} · ${describeMergeImpact(payload.impact)} · 来源 ${pointLabel(payload.source.item)} / 目标 ${pointLabel(payload.target.item)} · 最终节点 ${pointLabel(payload.item)} · 色 ${payload.item?.color ?? '—'} / 尺寸 ${payload.item?.size ?? '—'} / 图标 ${String(payload.item?.symbol ?? '—')}`
|
|
939
|
+
actionText.value = mergeText.value
|
|
940
|
+
pushLog('pointMerge/confirm', mergeText.value)
|
|
941
|
+
return
|
|
942
|
+
}
|
|
943
943
|
if (payload.phase === 'mergeSelected') {
|
|
944
944
|
const mergedNames = (payload.merged ?? []).map(result => pointLabel(result.source.item)).join(' / ') || '—'
|
|
945
945
|
mergeBackendText.value = describeMergeChangeSets((payload.merged ?? []).map(result => result.changes))
|
|
946
946
|
mergeText.value = payload.ok
|
|
947
|
-
? `mergeSelected · 保留 ${pointLabel(payload.target?.item)} · 并掉 ${mergedNames} · 最终 ${pointLabel(payload.target?.item)} · 改线 ${payload.linesChanged} / 删线 ${payload.removedLines} / 通道 ${payload.channelsChanged}`
|
|
948
|
-
: `mergeSelected 失败:${payload.reason}`
|
|
947
|
+
? `mergeSelected · 保留 ${pointLabel(payload.target?.item)} · 并掉 ${mergedNames} · 最终 ${pointLabel(payload.target?.item)} · 改线 ${payload.linesChanged} / 删线 ${payload.removedLines} / 通道 ${payload.channelsChanged}`
|
|
948
|
+
: `mergeSelected 失败:${payload.reason}`
|
|
949
949
|
actionText.value = mergeText.value
|
|
950
950
|
pushLog('pointMerge/mergeSelected', mergeText.value)
|
|
951
951
|
cancelScheduledMergeBackend()
|
|
@@ -956,63 +956,63 @@ const handlePointMerge = (payload) => {
|
|
|
956
956
|
mergeBackendText.value = payload.ok ? describeMergeChanges(payload.changes) : '合并变更 → 后端事务:合并失败,没有变更集'
|
|
957
957
|
mergeText.value = payload.ok
|
|
958
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)}`
|
|
959
|
-
: `merge 失败:${payload.reason}`
|
|
960
|
-
pendingMerge.value = null
|
|
961
|
-
pendingText.value = payload.ok ? '合并已落地,拖动来源节点已经收进保留目标' : `合并失败:${payload.reason}`
|
|
959
|
+
: `merge 失败:${payload.reason}`
|
|
960
|
+
pendingMerge.value = null
|
|
961
|
+
pendingText.value = payload.ok ? '合并已落地,拖动来源节点已经收进保留目标' : `合并失败:${payload.reason}`
|
|
962
962
|
actionText.value = mergeText.value
|
|
963
963
|
pushLog('pointMerge/merge', mergeText.value)
|
|
964
964
|
refreshState()
|
|
965
965
|
if (payload.ok) scheduleMergeBackend(payload)
|
|
966
966
|
return
|
|
967
|
-
}
|
|
967
|
+
}
|
|
968
968
|
if (payload.phase === 'cancel') {
|
|
969
969
|
pendingMerge.value = null
|
|
970
970
|
mergeBackendText.value = '合并变更 → 后端事务:合并已取消,没有变更集'
|
|
971
|
-
pendingText.value = '已取消:拖动的节点回到原位,数据没动'
|
|
972
|
-
mergeText.value = pendingText.value
|
|
973
|
-
actionText.value = mergeText.value
|
|
974
|
-
pushLog('pointMerge/cancel', pendingText.value)
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
const confirmPendingMerge = () => {
|
|
979
|
-
const options = { keep: mergeKeep.value }
|
|
980
|
-
if (rewriteOnConfirm.value) options.item = mergePatch()
|
|
971
|
+
pendingText.value = '已取消:拖动的节点回到原位,数据没动'
|
|
972
|
+
mergeText.value = pendingText.value
|
|
973
|
+
actionText.value = mergeText.value
|
|
974
|
+
pushLog('pointMerge/cancel', pendingText.value)
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const confirmPendingMerge = () => {
|
|
979
|
+
const options = { keep: mergeKeep.value }
|
|
980
|
+
if (rewriteOnConfirm.value) options.item = mergePatch()
|
|
981
981
|
const result = view.resolvePointMerge(true, options)
|
|
982
|
-
if (!result) return '当前没有待确认的合并'
|
|
982
|
+
if (!result) return '当前没有待确认的合并'
|
|
983
983
|
mergeText.value = `resolvePointMerge(true, { keep: '${mergeKeep.value}', item }) · ${rewriteOnConfirm.value ? '名称 / 分区 / 电压 / 样式已随 item 写回' : '使用引擎缺省合并结果'}`
|
|
984
|
-
actionText.value = mergeText.value
|
|
984
|
+
actionText.value = mergeText.value
|
|
985
985
|
pushLog('resolvePointMerge(true)', mergeText.value)
|
|
986
|
-
return mergeText.value
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
const cancelPendingMerge = () => {
|
|
986
|
+
return mergeText.value
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const cancelPendingMerge = () => {
|
|
990
990
|
const result = view.resolvePointMerge(false)
|
|
991
|
-
pendingMerge.value = null
|
|
992
|
-
return result ? '已取消:拖动的节点回到原位,数据没动' : '当前没有待确认的合并'
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
const resetScene = () => {
|
|
996
|
-
const fresh = buildDemoData({ count: 14, channels: 2 })
|
|
997
|
-
view.updateData({ points: fresh.points, lines: fresh.lines, channels: fresh.channels })
|
|
991
|
+
pendingMerge.value = null
|
|
992
|
+
return result ? '已取消:拖动的节点回到原位,数据没动' : '当前没有待确认的合并'
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const resetScene = () => {
|
|
996
|
+
const fresh = buildDemoData({ count: 14, channels: 2 })
|
|
997
|
+
view.updateData({ points: fresh.points, lines: fresh.lines, channels: fresh.channels })
|
|
998
998
|
view.selectFeatures(null)
|
|
999
|
-
view.clearHistory()
|
|
1000
|
-
view.fitView(50)
|
|
1001
|
-
lastBatch.value = null
|
|
1002
|
-
clipboardItem.value = null
|
|
1003
|
-
pendingMerge.value = null
|
|
1004
|
-
copyEvent.value = null
|
|
1005
|
-
copyToast.value = null
|
|
1006
|
-
pendingText.value = '拖动一个节点撞到另一个节点上,这里会出现待确认载荷'
|
|
999
|
+
view.clearHistory()
|
|
1000
|
+
view.fitView(50)
|
|
1001
|
+
lastBatch.value = null
|
|
1002
|
+
clipboardItem.value = null
|
|
1003
|
+
pendingMerge.value = null
|
|
1004
|
+
copyEvent.value = null
|
|
1005
|
+
copyToast.value = null
|
|
1006
|
+
pendingText.value = '拖动一个节点撞到另一个节点上,这里会出现待确认载荷'
|
|
1007
1007
|
mergeText.value = '合并:—'
|
|
1008
1008
|
copyText.value = '复制:—'
|
|
1009
1009
|
copyLineChannelsText.value = '—'
|
|
1010
1010
|
mergeBackendText.value = '合并变更 → 后端事务:—'
|
|
1011
1011
|
splitText.value = '自动解除合并:先选中一个合并后的节点(不选就找第一个带 mergeInfo 的节点)'
|
|
1012
|
-
clipboardText.value = '剪贴板:—'
|
|
1013
|
-
copyEventText.value = '还没有 pointCopy 事件'
|
|
1014
|
-
copySourcesText.value = '—'
|
|
1015
|
-
copyItemsText.value = '—'
|
|
1012
|
+
clipboardText.value = '剪贴板:—'
|
|
1013
|
+
copyEventText.value = '还没有 pointCopy 事件'
|
|
1014
|
+
copySourcesText.value = '—'
|
|
1015
|
+
copyItemsText.value = '—'
|
|
1016
1016
|
manualText.value = '—'
|
|
1017
1017
|
mergeKeep.value = 'target'
|
|
1018
1018
|
mergeMaxKeys.value = ''
|
|
@@ -1020,242 +1020,237 @@ const resetScene = () => {
|
|
|
1020
1020
|
view.updateOptions({ merge: { enabled: true, distance: 12, channelGroups: 'keep', maxKeys: [] } })
|
|
1021
1021
|
resetBackendDemo()
|
|
1022
1022
|
copyRound = 0
|
|
1023
|
-
pasteRound = 0
|
|
1023
|
+
pasteRound = 0
|
|
1024
1024
|
addPointRound = 0
|
|
1025
1025
|
splitRound = 0
|
|
1026
|
-
refreshState()
|
|
1027
|
-
actionText.value = `已恢复 ${fresh.points.length} 节点 / ${fresh.lines.length} 线路 / ${fresh.channels.length} 通道`
|
|
1028
|
-
return actionText.value
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
onMounted(() => {
|
|
1032
|
-
const dataset = buildDemoData({ count: 14, channels: 2 })
|
|
1033
|
-
view = createGisView(container.value, {
|
|
1034
|
-
points: dataset.points,
|
|
1035
|
-
lines: dataset.lines,
|
|
1036
|
-
channels: dataset.channels,
|
|
1037
|
-
channelWidth: 2.2,
|
|
1038
|
-
channelInset: 1.3,
|
|
1039
|
-
pointStyle: { size: 9, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.6 },
|
|
1040
|
-
lineStyle: { width: 1.5, color: 'rgba(120, 168, 226, 0.66)' },
|
|
1041
|
-
pointSelectStyle: { sizeScale: 1.5, color: '#ffd166', strokeColor: '#a33c00', strokeWidth: 3 },
|
|
1042
|
-
pointMultiSelectStyle: { sizeScale: 1.45, color: '#ffb703', strokeColor: '#7a2d00', strokeWidth: 2.5 },
|
|
1043
|
-
pointBoxSelectStyle: { sizeScale: 1.55, color: '#ff9f1c', strokeColor: '#7a2d00', strokeWidth: 3 },
|
|
1044
|
-
lineSelectStyle: { width: 3.2, color: '#ffb703' },
|
|
1045
|
-
lineMultiSelectStyle: { width: 2.6, color: '#ff9f1c' },
|
|
1046
|
-
selectionBoxStyle: { fill: '#3678bf', fillOpacity: 0.18, stroke: '#1d4ed8', strokeWidth: 2 },
|
|
1047
|
-
selectionOnTop: true,
|
|
1048
|
-
label: {
|
|
1049
|
-
content: '{name}',
|
|
1050
|
-
position: 'top',
|
|
1051
|
-
gap: 7,
|
|
1052
|
-
maxCount: 26,
|
|
1053
|
-
avoidOverlap: true,
|
|
1054
|
-
style: { color: '#27405f', fontSize: '11px' }
|
|
1055
|
-
},
|
|
1056
|
-
showGrid: true,
|
|
1057
|
-
fitPadding: 52,
|
|
1058
|
-
history: { limit: 60 },
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
drag: true,
|
|
1073
|
-
copy: true,
|
|
1074
|
-
merge: true,
|
|
1075
|
-
keyboard: true,
|
|
1076
|
-
waypoint: false,
|
|
1026
|
+
refreshState()
|
|
1027
|
+
actionText.value = `已恢复 ${fresh.points.length} 节点 / ${fresh.lines.length} 线路 / ${fresh.channels.length} 通道`
|
|
1028
|
+
return actionText.value
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
onMounted(() => {
|
|
1032
|
+
const dataset = buildDemoData({ count: 14, channels: 2 })
|
|
1033
|
+
view = createGisView(container.value, {
|
|
1034
|
+
points: dataset.points,
|
|
1035
|
+
lines: dataset.lines,
|
|
1036
|
+
channels: dataset.channels,
|
|
1037
|
+
channelWidth: 2.2,
|
|
1038
|
+
channelInset: 1.3,
|
|
1039
|
+
pointStyle: { size: 9, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.6 },
|
|
1040
|
+
lineStyle: { width: 1.5, color: 'rgba(120, 168, 226, 0.66)' },
|
|
1041
|
+
pointSelectStyle: { sizeScale: 1.5, color: '#ffd166', strokeColor: '#a33c00', strokeWidth: 3 },
|
|
1042
|
+
pointMultiSelectStyle: { sizeScale: 1.45, color: '#ffb703', strokeColor: '#7a2d00', strokeWidth: 2.5 },
|
|
1043
|
+
pointBoxSelectStyle: { sizeScale: 1.55, color: '#ff9f1c', strokeColor: '#7a2d00', strokeWidth: 3 },
|
|
1044
|
+
lineSelectStyle: { width: 3.2, color: '#ffb703' },
|
|
1045
|
+
lineMultiSelectStyle: { width: 2.6, color: '#ff9f1c' },
|
|
1046
|
+
selectionBoxStyle: { fill: '#3678bf', fillOpacity: 0.18, stroke: '#1d4ed8', strokeWidth: 2 },
|
|
1047
|
+
selectionOnTop: true,
|
|
1048
|
+
label: {
|
|
1049
|
+
content: '{name}',
|
|
1050
|
+
position: 'top',
|
|
1051
|
+
gap: 7,
|
|
1052
|
+
maxCount: 26,
|
|
1053
|
+
avoidOverlap: true,
|
|
1054
|
+
style: { color: '#27405f', fontSize: '11px' }
|
|
1055
|
+
},
|
|
1056
|
+
showGrid: true,
|
|
1057
|
+
fitPadding: 52,
|
|
1058
|
+
history: { limit: 60 },
|
|
1059
|
+
selectionKinds: ['point', 'line', 'channel'],
|
|
1060
|
+
channelSelectMode: 'channel',
|
|
1061
|
+
features: {
|
|
1062
|
+
pan: true,
|
|
1063
|
+
zoom: true,
|
|
1064
|
+
hover: true,
|
|
1065
|
+
select: true,
|
|
1066
|
+
boxSelect: true,
|
|
1067
|
+
drag: true,
|
|
1068
|
+
copy: true,
|
|
1069
|
+
merge: true,
|
|
1070
|
+
keyboard: true,
|
|
1071
|
+
waypoint: false,
|
|
1077
1072
|
remove: false,
|
|
1078
1073
|
channelDraft: false,
|
|
1079
1074
|
contextMenu: false
|
|
1080
|
-
},
|
|
1075
|
+
},
|
|
1081
1076
|
copy: {
|
|
1082
|
-
enabled: true,
|
|
1083
|
-
offset: [64, 46],
|
|
1084
|
-
createItem: (item, context) => ({
|
|
1085
|
-
...item,
|
|
1086
|
-
name: `${pointLabel(context?.sourceItem ?? item)} · 副本`,
|
|
1087
|
-
zone_name: '内置复制副本',
|
|
1088
|
-
demo_copy: true
|
|
1089
|
-
})
|
|
1077
|
+
enabled: true,
|
|
1078
|
+
offset: [64, 46],
|
|
1079
|
+
createItem: (item, context) => ({
|
|
1080
|
+
...item,
|
|
1081
|
+
name: `${pointLabel(context?.sourceItem ?? item)} · 副本`,
|
|
1082
|
+
zone_name: '内置复制副本',
|
|
1083
|
+
demo_copy: true
|
|
1084
|
+
})
|
|
1090
1085
|
},
|
|
1091
1086
|
// Ctrl+C / Ctrl+V 监听的三件套:copy 打开 + keyboard 打开 + 快捷键映射(不写也是这两个缺省值)
|
|
1092
1087
|
shortcuts: { copy: 'ctrl+c', paste: 'ctrl+v' },
|
|
1093
1088
|
merge: { enabled: true, distance: 12, channelGroups: 'keep', maxKeys: [] },
|
|
1094
|
-
onReady: () => {
|
|
1095
|
-
nextTick(() => {
|
|
1096
|
-
view?.fitView(50)
|
|
1097
|
-
refreshState()
|
|
1098
|
-
})
|
|
1089
|
+
onReady: () => {
|
|
1090
|
+
nextTick(() => {
|
|
1091
|
+
view?.fitView(50)
|
|
1092
|
+
refreshState()
|
|
1093
|
+
})
|
|
1099
1094
|
emit('notice', '批量复制与合并:先点一下画布取得焦点,再框选 / Ctrl+C / Ctrl+V;也可拖动节点相撞合并')
|
|
1100
|
-
},
|
|
1101
|
-
onPointCopy: handlePointCopy,
|
|
1102
|
-
onPointMerge: handlePointMerge,
|
|
1103
|
-
onSelectChange: () => refreshState(),
|
|
1104
|
-
onDataChange: payload => {
|
|
1105
|
-
pushLog(payload.type, `${payload.counts?.points ?? 0} 点 / ${payload.counts?.lines ?? 0} 线 / ${payload.counts?.channels ?? 0} 通道`)
|
|
1106
|
-
refreshState()
|
|
1107
|
-
}
|
|
1108
|
-
})
|
|
1109
|
-
offEvents = view.on('*', (name, payload) => {
|
|
1110
|
-
if (name === 'dataChange' && payload?.type === 'batch') {
|
|
1111
|
-
pushLog('batch', `一次批量收口 · ${payload.changes?.length ?? 0} 笔`)
|
|
1112
|
-
}
|
|
1113
|
-
})
|
|
1114
|
-
})
|
|
1115
|
-
|
|
1095
|
+
},
|
|
1096
|
+
onPointCopy: handlePointCopy,
|
|
1097
|
+
onPointMerge: handlePointMerge,
|
|
1098
|
+
onSelectChange: () => refreshState(),
|
|
1099
|
+
onDataChange: payload => {
|
|
1100
|
+
pushLog(payload.type, `${payload.counts?.points ?? 0} 点 / ${payload.counts?.lines ?? 0} 线 / ${payload.counts?.channels ?? 0} 通道`)
|
|
1101
|
+
refreshState()
|
|
1102
|
+
}
|
|
1103
|
+
})
|
|
1104
|
+
offEvents = view.on('*', (name, payload) => {
|
|
1105
|
+
if (name === 'dataChange' && payload?.type === 'batch') {
|
|
1106
|
+
pushLog('batch', `一次批量收口 · ${payload.changes?.length ?? 0} 笔`)
|
|
1107
|
+
}
|
|
1108
|
+
})
|
|
1109
|
+
})
|
|
1110
|
+
|
|
1116
1111
|
onBeforeUnmount(() => {
|
|
1117
1112
|
disposed = true
|
|
1118
1113
|
cancelScheduledMergeBackend()
|
|
1119
1114
|
offEvents?.()
|
|
1120
|
-
offEvents = null
|
|
1121
|
-
view?.destroy()
|
|
1122
|
-
view = null
|
|
1123
|
-
})
|
|
1124
|
-
</script>
|
|
1125
|
-
|
|
1126
|
-
<template>
|
|
1127
|
-
<section class="dv-demo cm-demo">
|
|
1128
|
-
<div class="dv-stage">
|
|
1129
|
-
<div ref="container" class="dv-canvas"></div>
|
|
1130
|
-
<div class="dv-toolbar">
|
|
1131
|
-
<button type="button" class="dv-button" @click="view?.fitView(50)">适应全部</button>
|
|
1132
|
-
<button type="button" class="dv-button" @click="resetScene">恢复初始数据</button>
|
|
1133
|
-
</div>
|
|
1134
|
-
<div class="cm-guide">
|
|
1115
|
+
offEvents = null
|
|
1116
|
+
view?.destroy()
|
|
1117
|
+
view = null
|
|
1118
|
+
})
|
|
1119
|
+
</script>
|
|
1120
|
+
|
|
1121
|
+
<template>
|
|
1122
|
+
<section class="dv-demo cm-demo">
|
|
1123
|
+
<div class="dv-stage">
|
|
1124
|
+
<div ref="container" class="dv-canvas"></div>
|
|
1125
|
+
<div class="dv-toolbar">
|
|
1126
|
+
<button type="button" class="dv-button" @click="view?.fitView(50)">适应全部</button>
|
|
1127
|
+
<button type="button" class="dv-button" @click="resetScene">恢复初始数据</button>
|
|
1128
|
+
</div>
|
|
1129
|
+
<div class="cm-guide">
|
|
1135
1130
|
<span><b>1</b><kbd>Shift</kbd> + 拖动框选节点 / 线路 / 通道(按右侧类型过滤)</span>
|
|
1136
|
-
<span><b>2</b><kbd>Alt</kbd> + 点击节点复制;<kbd>Ctrl</kbd> + <kbd>V</kbd> 粘贴</span>
|
|
1137
|
-
<span><b>3</b>拖动一个节点撞向另一个节点:可保留来源 / 目标并改写信息与样式</span>
|
|
1138
|
-
</div>
|
|
1139
|
-
<div v-if="copyToast" class="cm-toast cm-toast--copy">
|
|
1140
|
-
<div class="cm-toast__head">
|
|
1141
|
-
<strong>{{ copyToastTitle }}</strong>
|
|
1142
|
-
<span class="cm-tag">{{ copyToast.phase }} · {{ copyToast.via }}</span>
|
|
1143
|
-
</div>
|
|
1144
|
-
<p>{{ copyToastText }}</p>
|
|
1145
|
-
<div class="cm-toast__actions">
|
|
1146
|
-
<button v-if="copyToast.phase === 'clipboard'" type="button" @click="pasteFromClipboard">粘贴整组</button>
|
|
1147
|
-
<button type="button" @click="manualAddPointCopy">手动 addPoint</button>
|
|
1148
|
-
<button type="button" @click="revertLastCopy">取消 / 回滚</button>
|
|
1149
|
-
</div>
|
|
1150
|
-
</div>
|
|
1151
|
-
<div v-if="pendingMerge" class="cm-toast cm-toast--merge">
|
|
1152
|
-
<div class="cm-toast__head">
|
|
1153
|
-
<strong>合并待确认</strong>
|
|
1154
|
-
<span class="cm-tag">{{ pendingMerge.phase }}</span>
|
|
1155
|
-
</div>
|
|
1156
|
-
<p>拖动来源 {{ pointLabel(pendingMerge.source.item) }} → 碰撞目标 {{ pointLabel(pendingMerge.target.item) }} · 数据还没动</p>
|
|
1157
|
-
<p class="cm-toast__hint">保留 {{ mergeKeep === 'source' ? '拖动来源' : '碰撞目标' }};确认时把名称 / 分区 / 电压 / 颜色 / 尺寸 / 图标写进 payload.item</p>
|
|
1158
|
-
<div class="cm-toast__actions">
|
|
1159
|
-
<button v-if="pendingMerge.phase === 'pending'" type="button" class="is-primary" @click="confirmPendingMerge">确认并改写</button>
|
|
1160
|
-
<button type="button" @click="showMergeImpactDetails">查看影响详情</button>
|
|
1161
|
-
<button type="button" @click="cancelPendingMerge">取消并复位</button>
|
|
1162
|
-
</div>
|
|
1163
|
-
</div>
|
|
1164
|
-
<div class="cm-dock">
|
|
1165
|
-
<span>选中 <b>{{ selectionCounts.points }}</b> 节点 / <b>{{ selectionCounts.lines }}</b> 线路 / <b>{{ selectionCounts.channels }}</b> 通道</span>
|
|
1166
|
-
<button type="button" @click="selectBatchSources">选中示例组</button>
|
|
1167
|
-
<button type="button" @click="copySelectedByApi">复制选中</button>
|
|
1168
|
-
<button type="button" @click="prepareMerge">准备合并</button>
|
|
1169
|
-
<button type="button" @click="clearSelection">清空选中</button>
|
|
1170
|
-
</div>
|
|
1171
|
-
</div>
|
|
1172
|
-
<aside class="dv-panel">
|
|
1173
|
-
<div class="dv-group">
|
|
1174
|
-
<h3>1 · 框选来源</h3>
|
|
1175
|
-
<p class="dv-readout" data-demo="copy-merge-selection">{{ selectionText }}</p>
|
|
1131
|
+
<span><b>2</b><kbd>Alt</kbd> + 点击节点复制;<kbd>Ctrl</kbd> + <kbd>V</kbd> 粘贴</span>
|
|
1132
|
+
<span><b>3</b>拖动一个节点撞向另一个节点:可保留来源 / 目标并改写信息与样式</span>
|
|
1133
|
+
</div>
|
|
1134
|
+
<div v-if="copyToast" class="cm-toast cm-toast--copy">
|
|
1135
|
+
<div class="cm-toast__head">
|
|
1136
|
+
<strong>{{ copyToastTitle }}</strong>
|
|
1137
|
+
<span class="cm-tag">{{ copyToast.phase }} · {{ copyToast.via }}</span>
|
|
1138
|
+
</div>
|
|
1139
|
+
<p>{{ copyToastText }}</p>
|
|
1140
|
+
<div class="cm-toast__actions">
|
|
1141
|
+
<button v-if="copyToast.phase === 'clipboard'" type="button" @click="pasteFromClipboard">粘贴整组</button>
|
|
1142
|
+
<button type="button" @click="manualAddPointCopy">手动 addPoint</button>
|
|
1143
|
+
<button type="button" @click="revertLastCopy">取消 / 回滚</button>
|
|
1144
|
+
</div>
|
|
1145
|
+
</div>
|
|
1146
|
+
<div v-if="pendingMerge" class="cm-toast cm-toast--merge">
|
|
1147
|
+
<div class="cm-toast__head">
|
|
1148
|
+
<strong>合并待确认</strong>
|
|
1149
|
+
<span class="cm-tag">{{ pendingMerge.phase }}</span>
|
|
1150
|
+
</div>
|
|
1151
|
+
<p>拖动来源 {{ pointLabel(pendingMerge.source.item) }} → 碰撞目标 {{ pointLabel(pendingMerge.target.item) }} · 数据还没动</p>
|
|
1152
|
+
<p class="cm-toast__hint">保留 {{ mergeKeep === 'source' ? '拖动来源' : '碰撞目标' }};确认时把名称 / 分区 / 电压 / 颜色 / 尺寸 / 图标写进 payload.item</p>
|
|
1153
|
+
<div class="cm-toast__actions">
|
|
1154
|
+
<button v-if="pendingMerge.phase === 'pending'" type="button" class="is-primary" @click="confirmPendingMerge">确认并改写</button>
|
|
1155
|
+
<button type="button" @click="showMergeImpactDetails">查看影响详情</button>
|
|
1156
|
+
<button type="button" @click="cancelPendingMerge">取消并复位</button>
|
|
1157
|
+
</div>
|
|
1158
|
+
</div>
|
|
1159
|
+
<div class="cm-dock">
|
|
1160
|
+
<span>选中 <b>{{ selectionCounts.points }}</b> 节点 / <b>{{ selectionCounts.lines }}</b> 线路 / <b>{{ selectionCounts.channels }}</b> 通道</span>
|
|
1161
|
+
<button type="button" @click="selectBatchSources">选中示例组</button>
|
|
1162
|
+
<button type="button" @click="copySelectedByApi">复制选中</button>
|
|
1163
|
+
<button type="button" @click="prepareMerge">准备合并</button>
|
|
1164
|
+
<button type="button" @click="clearSelection">清空选中</button>
|
|
1165
|
+
</div>
|
|
1166
|
+
</div>
|
|
1167
|
+
<aside class="dv-panel">
|
|
1168
|
+
<div class="dv-group">
|
|
1169
|
+
<h3>1 · 框选来源</h3>
|
|
1170
|
+
<p class="dv-readout" data-demo="copy-merge-selection">{{ selectionText }}</p>
|
|
1176
1171
|
<p class="dv-readout" data-demo="selection-kinds-readout">框选(Shift)/ Ctrl 多选:{{ selectionKindsText }}</p>
|
|
1177
|
-
<div class="dv-row cm-checks">
|
|
1178
|
-
<label><input v-model="selectionFilters.point" data-demo="selection-point" type="checkbox" @change="applySelectionKinds" /> 节点</label>
|
|
1179
|
-
<label><input v-model="selectionFilters.line" data-demo="selection-line" type="checkbox" @change="applySelectionKinds" /> 线路</label>
|
|
1180
|
-
<label><input v-model="selectionFilters.channel" data-demo="selection-channel" type="checkbox" @change="applySelectionKinds" /> 通道</label>
|
|
1181
|
-
</div>
|
|
1182
|
-
<div class="dv-row">
|
|
1183
|
-
<button type="button" class="dv-button dv-button--primary" data-demo="copy-select-sources" @click="selectBatchSources">选中示例组</button>
|
|
1184
|
-
<button type="button" class="dv-button" data-demo="merge-select-sources" @click="prepareMerge">选中并准备合并</button>
|
|
1185
|
-
<button type="button" class="dv-button" @click="clearSelection">清空</button>
|
|
1186
|
-
</div>
|
|
1187
|
-
<div class="dv-row">
|
|
1188
|
-
<button type="button" class="dv-button" data-demo="select-sample-line" @click="selectSampleLine">选中示例线路</button>
|
|
1189
|
-
<button type="button" class="dv-button" data-demo="select-sample-channel" @click="selectSampleChannel">选中示例通道</button>
|
|
1190
|
-
</div>
|
|
1172
|
+
<div class="dv-row cm-checks">
|
|
1173
|
+
<label><input v-model="selectionFilters.point" data-demo="selection-point" type="checkbox" @change="applySelectionKinds" /> 节点</label>
|
|
1174
|
+
<label><input v-model="selectionFilters.line" data-demo="selection-line" type="checkbox" @change="applySelectionKinds" /> 线路</label>
|
|
1175
|
+
<label><input v-model="selectionFilters.channel" data-demo="selection-channel" type="checkbox" @change="applySelectionKinds" /> 通道</label>
|
|
1176
|
+
</div>
|
|
1177
|
+
<div class="dv-row">
|
|
1178
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="copy-select-sources" @click="selectBatchSources">选中示例组</button>
|
|
1179
|
+
<button type="button" class="dv-button" data-demo="merge-select-sources" @click="prepareMerge">选中并准备合并</button>
|
|
1180
|
+
<button type="button" class="dv-button" @click="clearSelection">清空</button>
|
|
1181
|
+
</div>
|
|
1182
|
+
<div class="dv-row">
|
|
1183
|
+
<button type="button" class="dv-button" data-demo="select-sample-line" @click="selectSampleLine">选中示例线路</button>
|
|
1184
|
+
<button type="button" class="dv-button" data-demo="select-sample-channel" @click="selectSampleChannel">选中示例通道</button>
|
|
1185
|
+
</div>
|
|
1191
1186
|
<p class="dv-hint">画布上按住 Shift 从空白处拖动框选;按住 Ctrl 点击仍是累加多选。复选框决定两种选中方式能收哪些类型。只选节点时复制只新增节点,选中线路 / 通道时复制会同时补端点、线路与通道。</p>
|
|
1192
|
-
</div>
|
|
1193
|
-
<div class="dv-group">
|
|
1194
|
-
<h3>2 · 复制事件</h3>
|
|
1187
|
+
</div>
|
|
1188
|
+
<div class="dv-group">
|
|
1189
|
+
<h3>2 · 复制事件</h3>
|
|
1195
1190
|
<p class="dv-readout" data-demo="copy-event-readout">{{ copyEventText }}</p>
|
|
1196
1191
|
<p class="dv-readout" data-demo="copy-sources-readout">来源:{{ copySourcesText }}</p>
|
|
1197
1192
|
<p class="dv-readout" data-demo="copy-items-readout">结果:{{ copyItemsText }}</p>
|
|
1198
1193
|
<p class="dv-readout" data-demo="copy-line-channels-readout">线路 → 通道:{{ copyLineChannelsText }}</p>
|
|
1199
|
-
<div class="dv-row">
|
|
1200
|
-
<button type="button" class="dv-button dv-button--primary" data-demo="copy-selected" @click="copySelectedByApi">copySelectedPoints</button>
|
|
1201
|
-
<button type="button" class="dv-button" data-demo="copy-points" @click="copyPointsByApi">copyPoints(sources)</button>
|
|
1202
|
-
<button type="button" class="dv-button" data-demo="copy-clipboard" @click="copyClipboardByApi">copyPointsToClipboard</button>
|
|
1203
|
-
<button type="button" class="dv-button" data-demo="paste-points" :disabled="!clipboardItem" @click="pasteFromClipboard">pastePoints</button>
|
|
1204
|
-
</div>
|
|
1205
|
-
<div class="dv-row">
|
|
1206
|
-
<button type="button" class="dv-button" data-demo="manual-add-point" @click="manualAddPointCopy">手动 addPoint 复制</button>
|
|
1207
|
-
<button type="button" class="dv-button dv-button--danger" data-demo="copy-revert" @click="revertLastCopy">取消 / 回滚本次复制</button>
|
|
1208
|
-
</div>
|
|
1209
|
-
<p class="dv-readout" data-demo="copy-readout">{{ copyText }}</p>
|
|
1210
|
-
<p class="dv-readout" data-demo="clipboard-readout">{{ clipboardText }}</p>
|
|
1211
|
-
<p class="dv-readout" data-demo="copy-action">{{ actionText }}</p>
|
|
1212
|
-
<p class="dv-readout" data-demo="manual-add-point-readout">{{ manualText }}</p>
|
|
1194
|
+
<div class="dv-row">
|
|
1195
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="copy-selected" @click="copySelectedByApi">copySelectedPoints</button>
|
|
1196
|
+
<button type="button" class="dv-button" data-demo="copy-points" @click="copyPointsByApi">copyPoints(sources)</button>
|
|
1197
|
+
<button type="button" class="dv-button" data-demo="copy-clipboard" @click="copyClipboardByApi">copyPointsToClipboard</button>
|
|
1198
|
+
<button type="button" class="dv-button" data-demo="paste-points" :disabled="!clipboardItem" @click="pasteFromClipboard">pastePoints</button>
|
|
1199
|
+
</div>
|
|
1200
|
+
<div class="dv-row">
|
|
1201
|
+
<button type="button" class="dv-button" data-demo="manual-add-point" @click="manualAddPointCopy">手动 addPoint 复制</button>
|
|
1202
|
+
<button type="button" class="dv-button dv-button--danger" data-demo="copy-revert" @click="revertLastCopy">取消 / 回滚本次复制</button>
|
|
1203
|
+
</div>
|
|
1204
|
+
<p class="dv-readout" data-demo="copy-readout">{{ copyText }}</p>
|
|
1205
|
+
<p class="dv-readout" data-demo="clipboard-readout">{{ clipboardText }}</p>
|
|
1206
|
+
<p class="dv-readout" data-demo="copy-action">{{ actionText }}</p>
|
|
1207
|
+
<p class="dv-readout" data-demo="manual-add-point-readout">{{ manualText }}</p>
|
|
1213
1208
|
<p class="dv-hint">「手动 addPoint」先拿到内置复制的真实落点,立刻撤销自动副本,再在同一坐标逐个 addPoint;最终只多一份手动副本。Ctrl+C / Ctrl+V 需要 copy 打开、features.keyboard: true,并保持画布焦点。复制事件里的 lineChannels 与 lines 一一对应;support 项是线路为了独立成立而补的端点。</p>
|
|
1214
|
-
</div>
|
|
1215
|
-
<div class="dv-group">
|
|
1216
|
-
<h3>3 · 合并事件</h3>
|
|
1217
|
-
<div class="dv-row cm-form">
|
|
1218
|
-
<label>
|
|
1219
|
-
保留节点
|
|
1220
|
-
<select v-model="mergeTargetId" data-demo="merge-target" :disabled="!mergeTargetOptions.length">
|
|
1221
|
-
<option v-for="item in mergeTargetOptions" :key="item.value" :value="item.value">{{ item.label }}</option>
|
|
1222
|
-
</select>
|
|
1223
|
-
</label>
|
|
1224
|
-
<label>
|
|
1225
|
-
最终名称
|
|
1226
|
-
<input v-model="mergedName" data-demo="merge-name" type="text" />
|
|
1227
|
-
</label>
|
|
1228
|
-
</div>
|
|
1229
|
-
<div class="dv-row cm-form">
|
|
1230
|
-
<label>
|
|
1231
|
-
最终分区
|
|
1232
|
-
<input v-model="mergedZone" data-demo="merge-zone" type="text" />
|
|
1233
|
-
</label>
|
|
1234
|
-
<label>
|
|
1235
|
-
最终电压
|
|
1236
|
-
<input v-model="mergedVoltage" type="number" step="0.1" />
|
|
1237
|
-
</label>
|
|
1238
|
-
</div>
|
|
1239
|
-
<div class="dv-row cm-form">
|
|
1240
|
-
<label>
|
|
1241
|
-
最终颜色
|
|
1242
|
-
<input v-model="mergedColor" data-demo="merge-color" type="text" />
|
|
1243
|
-
</label>
|
|
1244
|
-
<label>
|
|
1245
|
-
最终尺寸
|
|
1246
|
-
<input v-model="mergedSize" data-demo="merge-size" type="number" min="1" step="1" />
|
|
1247
|
-
</label>
|
|
1248
|
-
</div>
|
|
1249
|
-
<div class="dv-row cm-form">
|
|
1250
|
-
<label>
|
|
1251
|
-
最终图标
|
|
1252
|
-
<input v-model="mergedSymbol" data-demo="merge-symbol" type="text" />
|
|
1253
|
-
</label>
|
|
1254
|
-
<label>
|
|
1255
|
-
描边颜色
|
|
1256
|
-
<input v-model="mergedStrokeColor" data-demo="merge-stroke-color" type="text" />
|
|
1257
|
-
</label>
|
|
1258
|
-
</div>
|
|
1209
|
+
</div>
|
|
1210
|
+
<div class="dv-group">
|
|
1211
|
+
<h3>3 · 合并事件</h3>
|
|
1212
|
+
<div class="dv-row cm-form">
|
|
1213
|
+
<label>
|
|
1214
|
+
保留节点
|
|
1215
|
+
<select v-model="mergeTargetId" data-demo="merge-target" :disabled="!mergeTargetOptions.length">
|
|
1216
|
+
<option v-for="item in mergeTargetOptions" :key="item.value" :value="item.value">{{ item.label }}</option>
|
|
1217
|
+
</select>
|
|
1218
|
+
</label>
|
|
1219
|
+
<label>
|
|
1220
|
+
最终名称
|
|
1221
|
+
<input v-model="mergedName" data-demo="merge-name" type="text" />
|
|
1222
|
+
</label>
|
|
1223
|
+
</div>
|
|
1224
|
+
<div class="dv-row cm-form">
|
|
1225
|
+
<label>
|
|
1226
|
+
最终分区
|
|
1227
|
+
<input v-model="mergedZone" data-demo="merge-zone" type="text" />
|
|
1228
|
+
</label>
|
|
1229
|
+
<label>
|
|
1230
|
+
最终电压
|
|
1231
|
+
<input v-model="mergedVoltage" type="number" step="0.1" />
|
|
1232
|
+
</label>
|
|
1233
|
+
</div>
|
|
1234
|
+
<div class="dv-row cm-form">
|
|
1235
|
+
<label>
|
|
1236
|
+
最终颜色
|
|
1237
|
+
<input v-model="mergedColor" data-demo="merge-color" type="text" />
|
|
1238
|
+
</label>
|
|
1239
|
+
<label>
|
|
1240
|
+
最终尺寸
|
|
1241
|
+
<input v-model="mergedSize" data-demo="merge-size" type="number" min="1" step="1" />
|
|
1242
|
+
</label>
|
|
1243
|
+
</div>
|
|
1244
|
+
<div class="dv-row cm-form">
|
|
1245
|
+
<label>
|
|
1246
|
+
最终图标
|
|
1247
|
+
<input v-model="mergedSymbol" data-demo="merge-symbol" type="text" />
|
|
1248
|
+
</label>
|
|
1249
|
+
<label>
|
|
1250
|
+
描边颜色
|
|
1251
|
+
<input v-model="mergedStrokeColor" data-demo="merge-stroke-color" type="text" />
|
|
1252
|
+
</label>
|
|
1253
|
+
</div>
|
|
1259
1254
|
<div class="dv-row">
|
|
1260
1255
|
<label><input v-model="rewriteOnConfirm" type="checkbox" /> 手动拖拽合并确认时改写 payload.item</label>
|
|
1261
1256
|
</div>
|
|
@@ -1273,7 +1268,7 @@ onBeforeUnmount(() => {
|
|
|
1273
1268
|
<p class="dv-readout" data-demo="merge-readout">{{ mergeText }}</p>
|
|
1274
1269
|
<p class="dv-readout" data-demo="merge-backend-readout">{{ mergeBackendText }}</p>
|
|
1275
1270
|
<p class="dv-hint">「按缺省规则合并」不传 item:只有 <code>merge.maxKeys</code> 列出的数值字段取双方最高值(缺省为空,不取任何键),名称 / 图标等其余业务字段取保留节点;合并后的节点带 <code>mergeInfo</code>(来源快照、线路 / 通道快照)。「指定保留节点」仍可用 item 覆盖字段。拖点相撞走 pending / confirm 两站,确认前也能改写。合并落地后的 changes 可直接作为后端事务。</p>
|
|
1276
|
-
</div>
|
|
1271
|
+
</div>
|
|
1277
1272
|
<div class="dv-group">
|
|
1278
1273
|
<h3>4 · 解除合并</h3>
|
|
1279
1274
|
<div class="dv-row">
|
|
@@ -1287,24 +1282,24 @@ onBeforeUnmount(() => {
|
|
|
1287
1282
|
</div>
|
|
1288
1283
|
<div class="dv-group">
|
|
1289
1284
|
<h3>5 · 待确认合并</h3>
|
|
1290
|
-
<p class="dv-readout" data-demo="merge-pending-readout">{{ pendingText }}</p>
|
|
1291
|
-
<div class="dv-row cm-form">
|
|
1292
|
-
<label>
|
|
1293
|
-
拖拽合并保留
|
|
1294
|
-
<select v-model="mergeKeep" data-demo="merge-keep">
|
|
1295
|
-
<option value="target">保留碰撞目标(缺省)</option>
|
|
1296
|
-
<option value="source">保留拖动来源</option>
|
|
1297
|
-
</select>
|
|
1298
|
-
</label>
|
|
1299
|
-
</div>
|
|
1300
|
-
<div class="dv-row">
|
|
1285
|
+
<p class="dv-readout" data-demo="merge-pending-readout">{{ pendingText }}</p>
|
|
1286
|
+
<div class="dv-row cm-form">
|
|
1287
|
+
<label>
|
|
1288
|
+
拖拽合并保留
|
|
1289
|
+
<select v-model="mergeKeep" data-demo="merge-keep">
|
|
1290
|
+
<option value="target">保留碰撞目标(缺省)</option>
|
|
1291
|
+
<option value="source">保留拖动来源</option>
|
|
1292
|
+
</select>
|
|
1293
|
+
</label>
|
|
1294
|
+
</div>
|
|
1295
|
+
<div class="dv-row">
|
|
1301
1296
|
<button type="button" class="dv-button dv-button--primary" data-demo="merge-confirm" :disabled="!pendingMerge" @click="confirmPendingMerge">resolvePointMerge(true, { keep, item })</button>
|
|
1302
1297
|
<button type="button" class="dv-button" data-demo="merge-cancel" :disabled="!pendingMerge" @click="cancelPendingMerge">resolvePointMerge(false)</button>
|
|
1303
|
-
</div>
|
|
1304
|
-
<div class="dv-row">
|
|
1305
|
-
<button type="button" class="dv-button" data-demo="merge-impact-details" :disabled="!pendingMerge?.impact" @click="showMergeImpactDetails">按 id 查线路 / 通道详情</button>
|
|
1306
|
-
</div>
|
|
1307
|
-
<p class="dv-readout" data-demo="merge-impact-detail-readout">{{ mergeDetailText }}</p>
|
|
1298
|
+
</div>
|
|
1299
|
+
<div class="dv-row">
|
|
1300
|
+
<button type="button" class="dv-button" data-demo="merge-impact-details" :disabled="!pendingMerge?.impact" @click="showMergeImpactDetails">按 id 查线路 / 通道详情</button>
|
|
1301
|
+
</div>
|
|
1302
|
+
<p class="dv-readout" data-demo="merge-impact-detail-readout">{{ mergeDetailText }}</p>
|
|
1308
1303
|
<p class="dv-hint">清空选中后,把画布上的一个节点拖到另一个节点上松手;选保留拖动来源 / 碰撞目标,再点确认。pending 载荷的 impact 已按「保留碰撞目标」预演影响线路数,confirm 时按最终 keep 重算;名称、分区、电压和样式都随 item 写回最终节点。</p>
|
|
1309
1304
|
</div>
|
|
1310
1305
|
<div class="dv-group">
|
|
@@ -1338,188 +1333,188 @@ onBeforeUnmount(() => {
|
|
|
1338
1333
|
<pre class="cm-request" data-demo="copy-merge-backend-request">{{ backendPreview }}</pre>
|
|
1339
1334
|
</div>
|
|
1340
1335
|
<div class="dv-group">
|
|
1341
|
-
<h3>事件日志</h3>
|
|
1342
|
-
<ul class="dv-log" data-demo="copy-merge-log">
|
|
1343
|
-
<li v-for="line in logLines" :key="line.id"><strong>{{ line.name }}</strong> · {{ line.text }}</li>
|
|
1344
|
-
<li v-if="!logLines.length">还没有事件</li>
|
|
1345
|
-
</ul>
|
|
1346
|
-
</div>
|
|
1347
|
-
</aside>
|
|
1348
|
-
</section>
|
|
1349
|
-
</template>
|
|
1350
|
-
|
|
1351
|
-
<style scoped>
|
|
1352
|
-
.cm-demo .dv-stage {
|
|
1353
|
-
isolation: isolate;
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
.cm-guide {
|
|
1357
|
-
position: absolute;
|
|
1358
|
-
top: 10px;
|
|
1359
|
-
left: 10px;
|
|
1360
|
-
z-index: 4;
|
|
1361
|
-
display: flex;
|
|
1362
|
-
flex-direction: column;
|
|
1363
|
-
gap: 5px;
|
|
1364
|
-
padding: 8px 10px;
|
|
1365
|
-
border: 1px solid rgba(74, 121, 177, 0.18);
|
|
1366
|
-
border-radius: 8px;
|
|
1367
|
-
background: rgba(255, 255, 255, 0.88);
|
|
1368
|
-
box-shadow: 0 5px 18px rgba(31, 62, 105, 0.1);
|
|
1369
|
-
backdrop-filter: blur(6px);
|
|
1370
|
-
pointer-events: none;
|
|
1371
|
-
color: #526b8d;
|
|
1372
|
-
font-size: 11px;
|
|
1373
|
-
line-height: 1.5;
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
.cm-guide span {
|
|
1377
|
-
display: flex;
|
|
1378
|
-
align-items: center;
|
|
1379
|
-
gap: 5px;
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
.cm-guide b {
|
|
1383
|
-
display: inline-grid;
|
|
1384
|
-
place-items: center;
|
|
1385
|
-
width: 16px;
|
|
1386
|
-
height: 16px;
|
|
1387
|
-
border-radius: 50%;
|
|
1388
|
-
background: #3678bf;
|
|
1389
|
-
color: #fff;
|
|
1390
|
-
font-size: 10px;
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
.cm-guide kbd {
|
|
1394
|
-
padding: 0 4px;
|
|
1395
|
-
border: 1px solid #cfdcec;
|
|
1396
|
-
border-bottom-width: 2px;
|
|
1397
|
-
border-radius: 4px;
|
|
1398
|
-
background: #f7faff;
|
|
1399
|
-
color: #2f5e94;
|
|
1400
|
-
font: inherit;
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
.cm-toast {
|
|
1404
|
-
position: absolute;
|
|
1405
|
-
left: 10px;
|
|
1406
|
-
z-index: 5;
|
|
1407
|
-
width: min(440px, calc(100% - 150px));
|
|
1408
|
-
padding: 10px 12px;
|
|
1409
|
-
border: 1px solid #b9d2ee;
|
|
1410
|
-
border-radius: 9px;
|
|
1411
|
-
background: rgba(249, 252, 255, 0.96);
|
|
1412
|
-
box-shadow: 0 8px 24px rgba(31, 62, 105, 0.16);
|
|
1413
|
-
backdrop-filter: blur(8px);
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
.cm-toast--copy {
|
|
1417
|
-
top: 104px;
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
.cm-toast--merge {
|
|
1421
|
-
bottom: 62px;
|
|
1422
|
-
border-color: #efc98f;
|
|
1423
|
-
background: rgba(255, 251, 244, 0.97);
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
.cm-toast__head {
|
|
1427
|
-
display: flex;
|
|
1428
|
-
align-items: center;
|
|
1429
|
-
justify-content: space-between;
|
|
1430
|
-
gap: 8px;
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
.cm-toast__head strong {
|
|
1434
|
-
color: #173a63;
|
|
1435
|
-
font-size: 12px;
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
.cm-toast p {
|
|
1439
|
-
margin: 6px 0 0;
|
|
1440
|
-
color: #526b8d;
|
|
1441
|
-
font-size: 11px;
|
|
1442
|
-
line-height: 1.55;
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
.cm-toast .cm-toast__hint {
|
|
1446
|
-
color: #8b6a35;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
.cm-tag {
|
|
1450
|
-
padding: 1px 6px;
|
|
1451
|
-
border-radius: 999px;
|
|
1452
|
-
background: #eaf2fc;
|
|
1453
|
-
color: #2f5e94;
|
|
1454
|
-
font-size: 10px;
|
|
1455
|
-
white-space: nowrap;
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
.cm-toast__actions {
|
|
1459
|
-
display: flex;
|
|
1460
|
-
flex-wrap: wrap;
|
|
1461
|
-
gap: 6px;
|
|
1462
|
-
margin-top: 8px;
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
.cm-toast__actions button,
|
|
1466
|
-
.cm-dock button {
|
|
1467
|
-
padding: 4px 8px;
|
|
1468
|
-
border: 1px solid #cfdcec;
|
|
1469
|
-
border-radius: 5px;
|
|
1470
|
-
background: #fff;
|
|
1471
|
-
color: #2f5e94;
|
|
1472
|
-
font-size: 11px;
|
|
1473
|
-
cursor: pointer;
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
.cm-toast__actions button:hover,
|
|
1477
|
-
.cm-dock button:hover {
|
|
1478
|
-
background: #e9f1fb;
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
.cm-toast__actions .is-primary {
|
|
1482
|
-
border-color: #3678bf;
|
|
1483
|
-
background: #3678bf;
|
|
1484
|
-
color: #fff;
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
.cm-dock {
|
|
1488
|
-
position: absolute;
|
|
1489
|
-
left: 10px;
|
|
1490
|
-
bottom: 10px;
|
|
1491
|
-
z-index: 5;
|
|
1492
|
-
display: flex;
|
|
1493
|
-
align-items: center;
|
|
1494
|
-
flex-wrap: wrap;
|
|
1495
|
-
gap: 6px;
|
|
1496
|
-
padding: 5px 7px;
|
|
1497
|
-
border: 1px solid rgba(74, 121, 177, 0.16);
|
|
1498
|
-
border-radius: 7px;
|
|
1499
|
-
background: rgba(255, 255, 255, 0.9);
|
|
1500
|
-
box-shadow: 0 4px 16px rgba(31, 62, 105, 0.12);
|
|
1501
|
-
color: #526b8d;
|
|
1502
|
-
font-size: 11px;
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
.cm-dock b {
|
|
1506
|
-
color: #173a63;
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
.cm-form {
|
|
1510
|
-
align-items: flex-end;
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
.cm-form input[type='text'],
|
|
1514
|
-
.cm-form input[type='number'] {
|
|
1515
|
-
width: 132px;
|
|
1516
|
-
padding: 4px 6px;
|
|
1517
|
-
border: 1px solid #d6e0ec;
|
|
1518
|
-
border-radius: 6px;
|
|
1519
|
-
color: #1f2a44;
|
|
1520
|
-
font-size: 12px;
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1336
|
+
<h3>事件日志</h3>
|
|
1337
|
+
<ul class="dv-log" data-demo="copy-merge-log">
|
|
1338
|
+
<li v-for="line in logLines" :key="line.id"><strong>{{ line.name }}</strong> · {{ line.text }}</li>
|
|
1339
|
+
<li v-if="!logLines.length">还没有事件</li>
|
|
1340
|
+
</ul>
|
|
1341
|
+
</div>
|
|
1342
|
+
</aside>
|
|
1343
|
+
</section>
|
|
1344
|
+
</template>
|
|
1345
|
+
|
|
1346
|
+
<style scoped>
|
|
1347
|
+
.cm-demo .dv-stage {
|
|
1348
|
+
isolation: isolate;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
.cm-guide {
|
|
1352
|
+
position: absolute;
|
|
1353
|
+
top: 10px;
|
|
1354
|
+
left: 10px;
|
|
1355
|
+
z-index: 4;
|
|
1356
|
+
display: flex;
|
|
1357
|
+
flex-direction: column;
|
|
1358
|
+
gap: 5px;
|
|
1359
|
+
padding: 8px 10px;
|
|
1360
|
+
border: 1px solid rgba(74, 121, 177, 0.18);
|
|
1361
|
+
border-radius: 8px;
|
|
1362
|
+
background: rgba(255, 255, 255, 0.88);
|
|
1363
|
+
box-shadow: 0 5px 18px rgba(31, 62, 105, 0.1);
|
|
1364
|
+
backdrop-filter: blur(6px);
|
|
1365
|
+
pointer-events: none;
|
|
1366
|
+
color: #526b8d;
|
|
1367
|
+
font-size: 11px;
|
|
1368
|
+
line-height: 1.5;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
.cm-guide span {
|
|
1372
|
+
display: flex;
|
|
1373
|
+
align-items: center;
|
|
1374
|
+
gap: 5px;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
.cm-guide b {
|
|
1378
|
+
display: inline-grid;
|
|
1379
|
+
place-items: center;
|
|
1380
|
+
width: 16px;
|
|
1381
|
+
height: 16px;
|
|
1382
|
+
border-radius: 50%;
|
|
1383
|
+
background: #3678bf;
|
|
1384
|
+
color: #fff;
|
|
1385
|
+
font-size: 10px;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
.cm-guide kbd {
|
|
1389
|
+
padding: 0 4px;
|
|
1390
|
+
border: 1px solid #cfdcec;
|
|
1391
|
+
border-bottom-width: 2px;
|
|
1392
|
+
border-radius: 4px;
|
|
1393
|
+
background: #f7faff;
|
|
1394
|
+
color: #2f5e94;
|
|
1395
|
+
font: inherit;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
.cm-toast {
|
|
1399
|
+
position: absolute;
|
|
1400
|
+
left: 10px;
|
|
1401
|
+
z-index: 5;
|
|
1402
|
+
width: min(440px, calc(100% - 150px));
|
|
1403
|
+
padding: 10px 12px;
|
|
1404
|
+
border: 1px solid #b9d2ee;
|
|
1405
|
+
border-radius: 9px;
|
|
1406
|
+
background: rgba(249, 252, 255, 0.96);
|
|
1407
|
+
box-shadow: 0 8px 24px rgba(31, 62, 105, 0.16);
|
|
1408
|
+
backdrop-filter: blur(8px);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
.cm-toast--copy {
|
|
1412
|
+
top: 104px;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
.cm-toast--merge {
|
|
1416
|
+
bottom: 62px;
|
|
1417
|
+
border-color: #efc98f;
|
|
1418
|
+
background: rgba(255, 251, 244, 0.97);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
.cm-toast__head {
|
|
1422
|
+
display: flex;
|
|
1423
|
+
align-items: center;
|
|
1424
|
+
justify-content: space-between;
|
|
1425
|
+
gap: 8px;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
.cm-toast__head strong {
|
|
1429
|
+
color: #173a63;
|
|
1430
|
+
font-size: 12px;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
.cm-toast p {
|
|
1434
|
+
margin: 6px 0 0;
|
|
1435
|
+
color: #526b8d;
|
|
1436
|
+
font-size: 11px;
|
|
1437
|
+
line-height: 1.55;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
.cm-toast .cm-toast__hint {
|
|
1441
|
+
color: #8b6a35;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
.cm-tag {
|
|
1445
|
+
padding: 1px 6px;
|
|
1446
|
+
border-radius: 999px;
|
|
1447
|
+
background: #eaf2fc;
|
|
1448
|
+
color: #2f5e94;
|
|
1449
|
+
font-size: 10px;
|
|
1450
|
+
white-space: nowrap;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
.cm-toast__actions {
|
|
1454
|
+
display: flex;
|
|
1455
|
+
flex-wrap: wrap;
|
|
1456
|
+
gap: 6px;
|
|
1457
|
+
margin-top: 8px;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
.cm-toast__actions button,
|
|
1461
|
+
.cm-dock button {
|
|
1462
|
+
padding: 4px 8px;
|
|
1463
|
+
border: 1px solid #cfdcec;
|
|
1464
|
+
border-radius: 5px;
|
|
1465
|
+
background: #fff;
|
|
1466
|
+
color: #2f5e94;
|
|
1467
|
+
font-size: 11px;
|
|
1468
|
+
cursor: pointer;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
.cm-toast__actions button:hover,
|
|
1472
|
+
.cm-dock button:hover {
|
|
1473
|
+
background: #e9f1fb;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
.cm-toast__actions .is-primary {
|
|
1477
|
+
border-color: #3678bf;
|
|
1478
|
+
background: #3678bf;
|
|
1479
|
+
color: #fff;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
.cm-dock {
|
|
1483
|
+
position: absolute;
|
|
1484
|
+
left: 10px;
|
|
1485
|
+
bottom: 10px;
|
|
1486
|
+
z-index: 5;
|
|
1487
|
+
display: flex;
|
|
1488
|
+
align-items: center;
|
|
1489
|
+
flex-wrap: wrap;
|
|
1490
|
+
gap: 6px;
|
|
1491
|
+
padding: 5px 7px;
|
|
1492
|
+
border: 1px solid rgba(74, 121, 177, 0.16);
|
|
1493
|
+
border-radius: 7px;
|
|
1494
|
+
background: rgba(255, 255, 255, 0.9);
|
|
1495
|
+
box-shadow: 0 4px 16px rgba(31, 62, 105, 0.12);
|
|
1496
|
+
color: #526b8d;
|
|
1497
|
+
font-size: 11px;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
.cm-dock b {
|
|
1501
|
+
color: #173a63;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
.cm-form {
|
|
1505
|
+
align-items: flex-end;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
.cm-form input[type='text'],
|
|
1509
|
+
.cm-form input[type='number'] {
|
|
1510
|
+
width: 132px;
|
|
1511
|
+
padding: 4px 6px;
|
|
1512
|
+
border: 1px solid #d6e0ec;
|
|
1513
|
+
border-radius: 6px;
|
|
1514
|
+
color: #1f2a44;
|
|
1515
|
+
font-size: 12px;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1523
1518
|
.cm-form input[type='number'] {
|
|
1524
1519
|
width: 76px;
|
|
1525
1520
|
}
|
|
@@ -1539,13 +1534,13 @@ onBeforeUnmount(() => {
|
|
|
1539
1534
|
}
|
|
1540
1535
|
|
|
1541
1536
|
@media (max-width: 720px) {
|
|
1542
|
-
.cm-toast,
|
|
1543
|
-
.cm-dock {
|
|
1544
|
-
width: calc(100% - 20px);
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
.cm-toast--copy {
|
|
1548
|
-
top: 132px;
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
</style>
|
|
1537
|
+
.cm-toast,
|
|
1538
|
+
.cm-dock {
|
|
1539
|
+
width: calc(100% - 20px);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
.cm-toast--copy {
|
|
1543
|
+
top: 132px;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
</style>
|