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
package/docs/demos/EventDemo.vue
CHANGED
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
/**
|
|
3
|
-
* 事件与方法演示:一条 `view.on('*')` 把全部事件收进日志,旁边一排按钮调常用方法(合并 / 复制有确认条),
|
|
4
|
-
* 「谁发的、载荷长什么样、方法返回什么」都写在面板上。
|
|
5
|
-
*
|
|
6
|
-
* 高频事件(pointer / stats / render 这些)默认不写日志 —— 每帧都来的东西会把日志冲掉,
|
|
7
|
-
* 想看就把「含高频事件」勾上,日志会按 name 累计次数。
|
|
8
|
-
*/
|
|
9
|
-
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
|
|
10
|
-
import { createGisView } from '../../src/index.js'
|
|
11
|
-
import { buildDemoData } from './shared/demoData.js'
|
|
12
|
-
import './shared/demoStyle.css'
|
|
13
|
-
|
|
14
|
-
const emit = defineEmits(['notice'])
|
|
15
|
-
const container = ref(null)
|
|
16
|
-
const dataset = buildDemoData({ count: 18 })
|
|
17
|
-
let view = null
|
|
18
|
-
let offAll = null
|
|
19
|
-
|
|
20
|
-
const showHighFrequency = ref(false)
|
|
21
|
-
const logLines = ref([])
|
|
22
|
-
const counts = ref({})
|
|
23
|
-
const actionText = ref('—')
|
|
24
|
-
let logSeq = 0
|
|
25
|
-
|
|
1
|
+
<script setup>
|
|
2
|
+
/**
|
|
3
|
+
* 事件与方法演示:一条 `view.on('*')` 把全部事件收进日志,旁边一排按钮调常用方法(合并 / 复制有确认条),
|
|
4
|
+
* 「谁发的、载荷长什么样、方法返回什么」都写在面板上。
|
|
5
|
+
*
|
|
6
|
+
* 高频事件(pointer / stats / render 这些)默认不写日志 —— 每帧都来的东西会把日志冲掉,
|
|
7
|
+
* 想看就把「含高频事件」勾上,日志会按 name 累计次数。
|
|
8
|
+
*/
|
|
9
|
+
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
|
|
10
|
+
import { createGisView } from '../../src/index.js'
|
|
11
|
+
import { buildDemoData } from './shared/demoData.js'
|
|
12
|
+
import './shared/demoStyle.css'
|
|
13
|
+
|
|
14
|
+
const emit = defineEmits(['notice'])
|
|
15
|
+
const container = ref(null)
|
|
16
|
+
const dataset = buildDemoData({ count: 18 })
|
|
17
|
+
let view = null
|
|
18
|
+
let offAll = null
|
|
19
|
+
|
|
20
|
+
const showHighFrequency = ref(false)
|
|
21
|
+
const logLines = ref([])
|
|
22
|
+
const counts = ref({})
|
|
23
|
+
const actionText = ref('—')
|
|
24
|
+
let logSeq = 0
|
|
25
|
+
|
|
26
26
|
/**
|
|
27
27
|
* 合并 / 复制的确认条:合并会停在「还没落地」的中间态等使用方裁决,复制则是**结束之后**才弹 ——
|
|
28
28
|
* 这里把载荷存下来,条上的按钮就是调载荷给的 confirm() / cancel() / revert(),
|
|
29
29
|
* 与 DragDropDemo 的落点草稿是同一套写法。载荷里挂着引擎的节点对象,不必深层响应式,用 shallowRef 收着。
|
|
30
|
-
*/
|
|
31
|
-
const mergeState = shallowRef(null)
|
|
32
|
-
const copyState = shallowRef(null)
|
|
33
|
-
/** 光标读数:直接读画布上的 cursor —— 「拖到一起鼠标为什么会变」在这里看得见 */
|
|
34
|
-
const cursorText = ref('—')
|
|
35
|
-
let cursorTimer = null
|
|
36
|
-
|
|
37
|
-
/** 高频事件名单与库里的口径一致:这些默认只计数不写日志 */
|
|
38
|
-
const HIGH_FREQUENCY = new Set(['pointer', 'stats', 'render', 'cameraChange', 'viewport', 'cursor', 'hover'])
|
|
39
|
-
|
|
40
|
-
const pushLog = (name, text) => {
|
|
41
|
-
logSeq += 1
|
|
42
|
-
logLines.value = [{ id: logSeq, name, text }, ...logLines.value].slice(0, 14)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** 载荷压成一行:不同事件的字段差别大,这里只挑最常看的几个 */
|
|
46
|
-
const describePayload = (payload) => {
|
|
47
|
-
if (payload === undefined || payload === null) {
|
|
48
|
-
return '(无载荷)'
|
|
49
|
-
}
|
|
50
|
-
if (typeof payload !== 'object') {
|
|
51
|
-
return String(payload)
|
|
52
|
-
}
|
|
53
|
-
const parts = []
|
|
54
|
-
if (payload.type !== undefined) parts.push(`type=${payload.type}`)
|
|
55
|
-
if (payload.kind !== undefined) parts.push(`kind=${payload.kind}`)
|
|
56
|
-
if (payload.phase !== undefined) parts.push(`phase=${payload.phase}`)
|
|
57
|
-
if (payload.id !== undefined) parts.push(`id=${payload.id}`)
|
|
58
|
-
if (payload.index !== undefined) parts.push(`index=${payload.index}`)
|
|
59
|
-
if (payload.labelId !== undefined) parts.push(`labelId=${payload.labelId}`)
|
|
60
|
-
if (payload.count !== undefined) parts.push(`count=${payload.count}`)
|
|
30
|
+
*/
|
|
31
|
+
const mergeState = shallowRef(null)
|
|
32
|
+
const copyState = shallowRef(null)
|
|
33
|
+
/** 光标读数:直接读画布上的 cursor —— 「拖到一起鼠标为什么会变」在这里看得见 */
|
|
34
|
+
const cursorText = ref('—')
|
|
35
|
+
let cursorTimer = null
|
|
36
|
+
|
|
37
|
+
/** 高频事件名单与库里的口径一致:这些默认只计数不写日志 */
|
|
38
|
+
const HIGH_FREQUENCY = new Set(['pointer', 'stats', 'render', 'cameraChange', 'viewport', 'cursor', 'hover'])
|
|
39
|
+
|
|
40
|
+
const pushLog = (name, text) => {
|
|
41
|
+
logSeq += 1
|
|
42
|
+
logLines.value = [{ id: logSeq, name, text }, ...logLines.value].slice(0, 14)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 载荷压成一行:不同事件的字段差别大,这里只挑最常看的几个 */
|
|
46
|
+
const describePayload = (payload) => {
|
|
47
|
+
if (payload === undefined || payload === null) {
|
|
48
|
+
return '(无载荷)'
|
|
49
|
+
}
|
|
50
|
+
if (typeof payload !== 'object') {
|
|
51
|
+
return String(payload)
|
|
52
|
+
}
|
|
53
|
+
const parts = []
|
|
54
|
+
if (payload.type !== undefined) parts.push(`type=${payload.type}`)
|
|
55
|
+
if (payload.kind !== undefined) parts.push(`kind=${payload.kind}`)
|
|
56
|
+
if (payload.phase !== undefined) parts.push(`phase=${payload.phase}`)
|
|
57
|
+
if (payload.id !== undefined) parts.push(`id=${payload.id}`)
|
|
58
|
+
if (payload.index !== undefined) parts.push(`index=${payload.index}`)
|
|
59
|
+
if (payload.labelId !== undefined) parts.push(`labelId=${payload.labelId}`)
|
|
60
|
+
if (payload.count !== undefined) parts.push(`count=${payload.count}`)
|
|
61
61
|
if (payload.channelId !== undefined) parts.push(`channel=${payload.channelId}`)
|
|
62
62
|
if (payload.camera !== undefined) parts.push('camera=…')
|
|
63
63
|
if (payload.idleTime !== undefined) parts.push(`idleTime=${Math.round(payload.idleTime)}ms`)
|
|
64
64
|
if (payload.timeout !== undefined) parts.push(`timeout=${payload.timeout}ms`)
|
|
65
|
-
if (payload.screen !== undefined) parts.push(`screen=[${payload.screen.map(value => Number(value).toFixed(0)).join(', ')}]`)
|
|
66
|
-
if (payload.action) {
|
|
67
|
-
const action = payload.action
|
|
65
|
+
if (payload.screen !== undefined) parts.push(`screen=[${payload.screen.map(value => Number(value).toFixed(0)).join(', ')}]`)
|
|
66
|
+
if (payload.action) {
|
|
67
|
+
const action = payload.action
|
|
68
68
|
parts.push(`action=${action.type}/${action.target}${action.moved ? '+moved' : ''}${action.double ? '+double' : ''}@${payload.originalEvent?.button ?? '-'}`)
|
|
69
69
|
parts.push(`data=${dataLabel(hitDataOf(payload))}`)
|
|
70
|
-
}
|
|
71
|
-
if (!parts.length) parts.push(`keys=[${Object.keys(payload).slice(0, 6).join(', ')}]`)
|
|
72
|
-
return parts.join(' · ')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
70
|
+
}
|
|
71
|
+
if (!parts.length) parts.push(`keys=[${Object.keys(payload).slice(0, 6).join(', ')}]`)
|
|
72
|
+
return parts.join(' · ')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
76
|
* 命中的数据在载荷顶层:节点 / 线路看 item,通道看 channel,选中集报 items 条数,
|
|
77
77
|
* 标签(labelClick / labelDrag / labelContextMenu / labelDblClick)看 label / parent。
|
|
78
78
|
*/
|
|
79
|
-
const dataLabel = (value) => {
|
|
80
|
-
if (Array.isArray(value)) return `${value.length} 项`
|
|
81
|
-
if (!value || typeof value !== 'object') return String(value)
|
|
82
|
-
if (value.label) {
|
|
83
|
-
const parent = value.parent
|
|
84
|
-
const where = parent?.id !== undefined && parent?.id !== null
|
|
85
|
-
? `${parent.type ?? '要素'} ${parent.id}`
|
|
86
|
-
: parent?.index !== undefined ? `${parent.type ?? '要素'} #${parent.index}` : ''
|
|
87
|
-
return `标签「${value.label.content || value.label.id || '—'}」${where ? ` @ ${where}` : ''}`
|
|
88
|
-
}
|
|
89
|
-
return value.id ?? value.channelId ?? value.name ?? `对象 keys=[${Object.keys(value).slice(0, 4).join(', ')}]`
|
|
90
|
-
}
|
|
91
|
-
|
|
79
|
+
const dataLabel = (value) => {
|
|
80
|
+
if (Array.isArray(value)) return `${value.length} 项`
|
|
81
|
+
if (!value || typeof value !== 'object') return String(value)
|
|
82
|
+
if (value.label) {
|
|
83
|
+
const parent = value.parent
|
|
84
|
+
const where = parent?.id !== undefined && parent?.id !== null
|
|
85
|
+
? `${parent.type ?? '要素'} ${parent.id}`
|
|
86
|
+
: parent?.index !== undefined ? `${parent.type ?? '要素'} #${parent.index}` : ''
|
|
87
|
+
return `标签「${value.label.content || value.label.id || '—'}」${where ? ` @ ${where}` : ''}`
|
|
88
|
+
}
|
|
89
|
+
return value.id ?? value.channelId ?? value.name ?? `对象 keys=[${Object.keys(value).slice(0, 4).join(', ')}]`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
92
|
/** 一次事件里可读的那份顶层数据:不把通道 / 分组上下文再打成一个 action.data */
|
|
93
93
|
const hitDataOf = payload => payload.item ?? payload.channel ?? payload.label ?? payload.line
|
|
94
94
|
?? (payload.type === 'cluster' ? { id: payload.id, count: payload.count } : null)
|
|
95
95
|
?? (Array.isArray(payload.items) ? payload.items : null)
|
|
96
|
-
|
|
97
|
-
/** 会带 action 的事件名:* 订阅里从载荷上认出「这一次是什么操作」 */
|
|
96
|
+
|
|
97
|
+
/** 会带 action 的事件名:* 订阅里从载荷上认出「这一次是什么操作」 */
|
|
98
98
|
const ACTION_EVENTS = new Set(['click', 'dblClick', 'pointerDown', 'pointerUp', 'contextMenu', 'labelClick', 'labelDrag', 'labelContextMenu', 'labelDblClick', 'selectChange', 'waypointChange'])
|
|
99
|
-
|
|
100
|
-
/** 最近一次操作的判读:面板上的「动作判读」直接读它 */
|
|
101
|
-
const lastAction = ref('—')
|
|
102
|
-
|
|
103
|
-
/** 画布中心的客户端坐标与「中间半屏」的像素矩形:给 pickAt / getFeaturesInRect 当参数 */
|
|
104
|
-
const canvasProbe = () => {
|
|
105
|
-
const canvas = container.value?.querySelector?.('.gis-view__canvas') ?? container.value
|
|
106
|
-
const box = canvas?.getBoundingClientRect?.() ?? { left: 0, top: 0, width: 0, height: 0 }
|
|
107
|
-
return {
|
|
108
|
-
center: { clientX: box.left + box.width / 2, clientY: box.top + box.height / 2 },
|
|
109
|
-
rect: { x: box.width * 0.25, y: box.height * 0.25, width: box.width * 0.5, height: box.height * 0.5 }
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const buildOptions = () => ({
|
|
114
|
-
points: dataset.points,
|
|
115
|
-
lines: dataset.lines,
|
|
116
|
-
channels: dataset.channels,
|
|
117
|
-
pointStyle: { size: 7, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.5 },
|
|
118
|
-
lineStyle: { width: 1.5, color: 'rgba(120, 168, 226, 0.55)' },
|
|
99
|
+
|
|
100
|
+
/** 最近一次操作的判读:面板上的「动作判读」直接读它 */
|
|
101
|
+
const lastAction = ref('—')
|
|
102
|
+
|
|
103
|
+
/** 画布中心的客户端坐标与「中间半屏」的像素矩形:给 pickAt / getFeaturesInRect 当参数 */
|
|
104
|
+
const canvasProbe = () => {
|
|
105
|
+
const canvas = container.value?.querySelector?.('.gis-view__canvas') ?? container.value
|
|
106
|
+
const box = canvas?.getBoundingClientRect?.() ?? { left: 0, top: 0, width: 0, height: 0 }
|
|
107
|
+
return {
|
|
108
|
+
center: { clientX: box.left + box.width / 2, clientY: box.top + box.height / 2 },
|
|
109
|
+
rect: { x: box.width * 0.25, y: box.height * 0.25, width: box.width * 0.5, height: box.height * 0.5 }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const buildOptions = () => ({
|
|
114
|
+
points: dataset.points,
|
|
115
|
+
lines: dataset.lines,
|
|
116
|
+
channels: dataset.channels,
|
|
117
|
+
pointStyle: { size: 7, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.5 },
|
|
118
|
+
lineStyle: { width: 1.5, color: 'rgba(120, 168, 226, 0.55)' },
|
|
119
119
|
showGrid: true,
|
|
120
120
|
fitPadding: 60,
|
|
121
121
|
// 停手 3 秒发一次 idle,事件日志与计数里都能看到;
|
|
@@ -129,82 +129,82 @@ const buildOptions = () => ({
|
|
|
129
129
|
// 复制走缺省口径:Alt 点击出预览 → 跟着鼠标走 → **松开左键**落地(不是松开 Alt),
|
|
130
130
|
// 落地之后确认条给一次撤销机会(copy.autoCommit: false 才会停在预览态等确认,演示不用那一档)
|
|
131
131
|
onReady: () => {
|
|
132
|
-
emit('notice', '事件日志已接上:点、拖、滚、选都会记一行')
|
|
133
|
-
pushLog('ready', '视图就绪')
|
|
134
|
-
}
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
/** 方法按钮:每个都调一个实例方法,把返回值写进读数 */
|
|
138
|
-
const call = (name, run) => {
|
|
139
|
-
const started = performance.now()
|
|
140
|
-
let result
|
|
141
|
-
try {
|
|
142
|
-
result = run()
|
|
143
|
-
}
|
|
144
|
-
catch (error) {
|
|
145
|
-
actionText.value = `${name} 抛错:${error instanceof Error ? error.message : String(error)}`
|
|
146
|
-
return
|
|
147
|
-
}
|
|
148
|
-
const elapsed = (performance.now() - started).toFixed(1)
|
|
149
|
-
const text = summarize(result)
|
|
150
|
-
actionText.value = `${name} → ${text}(${elapsed} ms)`
|
|
151
|
-
pushLog('method', `${name} → ${text}`)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const summarize = (value) => {
|
|
155
|
-
if (value === undefined) return 'undefined'
|
|
156
|
-
if (value === null) return 'null'
|
|
157
|
-
if (Array.isArray(value)) return `数组 ${value.length} 项`
|
|
158
|
-
if (typeof value === 'object') return `对象 keys=[${Object.keys(value).slice(0, 6).join(', ')}]`
|
|
159
|
-
return String(value)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const methods = [
|
|
163
|
-
{ name: 'fitView()', run: () => view?.fitView(60) },
|
|
164
|
-
{ name: 'zoomBy(1.3)', run: () => view?.zoomBy(1.3) },
|
|
132
|
+
emit('notice', '事件日志已接上:点、拖、滚、选都会记一行')
|
|
133
|
+
pushLog('ready', '视图就绪')
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
/** 方法按钮:每个都调一个实例方法,把返回值写进读数 */
|
|
138
|
+
const call = (name, run) => {
|
|
139
|
+
const started = performance.now()
|
|
140
|
+
let result
|
|
141
|
+
try {
|
|
142
|
+
result = run()
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
actionText.value = `${name} 抛错:${error instanceof Error ? error.message : String(error)}`
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
const elapsed = (performance.now() - started).toFixed(1)
|
|
149
|
+
const text = summarize(result)
|
|
150
|
+
actionText.value = `${name} → ${text}(${elapsed} ms)`
|
|
151
|
+
pushLog('method', `${name} → ${text}`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const summarize = (value) => {
|
|
155
|
+
if (value === undefined) return 'undefined'
|
|
156
|
+
if (value === null) return 'null'
|
|
157
|
+
if (Array.isArray(value)) return `数组 ${value.length} 项`
|
|
158
|
+
if (typeof value === 'object') return `对象 keys=[${Object.keys(value).slice(0, 6).join(', ')}]`
|
|
159
|
+
return String(value)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const methods = [
|
|
163
|
+
{ name: 'fitView()', run: () => view?.fitView(60) },
|
|
164
|
+
{ name: 'zoomBy(1.3)', run: () => view?.zoomBy(1.3) },
|
|
165
165
|
{ name: 'selectFeatures(node-0)', run: () => view?.selectFeatures('node-0') },
|
|
166
166
|
{ name: 'selectFeatures(null)', run: () => view?.selectFeatures(null) },
|
|
167
|
-
{ name: 'getSelections()', run: () => view?.getSelections() },
|
|
168
|
-
{ name: 'getSelectionData()', run: () => view?.getSelectionData() },
|
|
169
|
-
{ name: 'isSelected(node-0)', run: () => view?.isSelected('node-0') },
|
|
170
|
-
{ name: 'getVisibleData()', run: () => view?.getVisibleData() },
|
|
171
|
-
{ name: 'listPoints({ visibleOnly: true })', run: () => view?.listPoints({ visibleOnly: true }) },
|
|
172
|
-
// 聚焦一批:数组取整体包围盒(这几个点的中心就是屏幕中心),select: true 顺带把它们选上
|
|
173
|
-
{ name: 'focusOn(前三个节点, { select: true })', run: () => { const ids = (view?.getData().points ?? []).slice(0, 3).map(point => point.id ?? 0); return ids.length > 0 ? view?.focusOn(ids, { select: true }) : null } },
|
|
174
|
-
{ name: 'getStats()', run: () => view?.getStats() },
|
|
175
|
-
{ name: 'getQuality()', run: () => view?.getQuality() },
|
|
167
|
+
{ name: 'getSelections()', run: () => view?.getSelections() },
|
|
168
|
+
{ name: 'getSelectionData()', run: () => view?.getSelectionData() },
|
|
169
|
+
{ name: 'isSelected(node-0)', run: () => view?.isSelected('node-0') },
|
|
170
|
+
{ name: 'getVisibleData()', run: () => view?.getVisibleData() },
|
|
171
|
+
{ name: 'listPoints({ visibleOnly: true })', run: () => view?.listPoints({ visibleOnly: true }) },
|
|
172
|
+
// 聚焦一批:数组取整体包围盒(这几个点的中心就是屏幕中心),select: true 顺带把它们选上
|
|
173
|
+
{ name: 'focusOn(前三个节点, { select: true })', run: () => { const ids = (view?.getData().points ?? []).slice(0, 3).map(point => point.id ?? 0); return ids.length > 0 ? view?.focusOn(ids, { select: true }) : null } },
|
|
174
|
+
{ name: 'getStats()', run: () => view?.getStats() },
|
|
175
|
+
{ name: 'getQuality()', run: () => view?.getQuality() },
|
|
176
176
|
{ name: 'getCamera()', run: () => view?.getCamera() },
|
|
177
177
|
{ name: 'getScale()', run: () => view?.getScale() },
|
|
178
178
|
{ name: 'setScale(当前 × 1.25)', run: () => view?.setScale(view.getScale() * 1.25) },
|
|
179
179
|
{ name: 'getIdleTime()', run: () => view?.getIdleTime() },
|
|
180
|
-
{ name: 'getBounds()', run: () => view?.getBounds() },
|
|
181
|
-
{ name: 'getFilterResult()', run: () => view?.getFilterResult() },
|
|
182
|
-
{ name: 'getLabelStates()', run: () => view?.getLabelStates() },
|
|
183
|
-
{ name: 'getChannels()', run: () => view?.getChannels() },
|
|
184
|
-
{ name: 'getChannelsInView()', run: () => view?.getChannelsInView() },
|
|
185
|
-
// 回线属于哪条通道:线上点一条回线之后,想知道它归哪条通道 / 哪一组就问它
|
|
186
|
-
{ name: 'getLineChannel(第一条回线)', run: () => { const line = view?.listLines().find(entry => view.getLineChannel(entry.index)); return line ? view.getLineChannel(line.index) : '没有通道回线' } },
|
|
187
|
-
// 导出当前数据:format 'input' 就是 createGisView 入参那一份,可以直接回灌
|
|
188
|
-
{ name: 'exportData(input)', run: () => view?.exportData({ format: 'input' }) },
|
|
189
|
-
{ name: 'getViewState()', run: () => view?.getViewState() },
|
|
190
|
-
{ name: 'getPointCopy()', run: () => view?.getPointCopy() },
|
|
191
|
-
{ name: 'getPointMerge()', run: () => view?.getPointMerge() },
|
|
192
|
-
{ name: 'canUndo()', run: () => view?.canUndo() },
|
|
193
|
-
{ name: 'undo()', run: () => view?.undo() },
|
|
194
|
-
{ name: 'refresh()', run: () => view?.refresh() },
|
|
195
|
-
{ name: 'resize()', run: () => view?.resize() },
|
|
196
|
-
{ name: 'pickAt(画布中心)', run: () => { const probe = canvasProbe(); return view?.pickAt(probe.center.clientX, probe.center.clientY) } },
|
|
197
|
-
{ name: 'getFeaturesInRect(中心半屏)', run: () => view?.getFeaturesInRect(canvasProbe().rect) }
|
|
198
|
-
]
|
|
199
|
-
|
|
200
|
-
/** 确认条上写「哪个节点」:优先业务名,其次是 id,都没有就报下标 */
|
|
201
|
-
const pointLabel = (entry) => {
|
|
202
|
-
if (!entry?.item) {
|
|
203
|
-
return entry?.index === undefined ? '—' : '下标 ' + entry.index
|
|
204
|
-
}
|
|
205
|
-
return entry.item.name ?? entry.item.id ?? '下标 ' + (entry.index ?? '—')
|
|
206
|
-
}
|
|
207
|
-
|
|
180
|
+
{ name: 'getBounds()', run: () => view?.getBounds() },
|
|
181
|
+
{ name: 'getFilterResult()', run: () => view?.getFilterResult() },
|
|
182
|
+
{ name: 'getLabelStates()', run: () => view?.getLabelStates() },
|
|
183
|
+
{ name: 'getChannels()', run: () => view?.getChannels() },
|
|
184
|
+
{ name: 'getChannelsInView()', run: () => view?.getChannelsInView() },
|
|
185
|
+
// 回线属于哪条通道:线上点一条回线之后,想知道它归哪条通道 / 哪一组就问它
|
|
186
|
+
{ name: 'getLineChannel(第一条回线)', run: () => { const line = view?.listLines().find(entry => view.getLineChannel(entry.index)); return line ? view.getLineChannel(line.index) : '没有通道回线' } },
|
|
187
|
+
// 导出当前数据:format 'input' 就是 createGisView 入参那一份,可以直接回灌
|
|
188
|
+
{ name: 'exportData(input)', run: () => view?.exportData({ format: 'input' }) },
|
|
189
|
+
{ name: 'getViewState()', run: () => view?.getViewState() },
|
|
190
|
+
{ name: 'getPointCopy()', run: () => view?.getPointCopy() },
|
|
191
|
+
{ name: 'getPointMerge()', run: () => view?.getPointMerge() },
|
|
192
|
+
{ name: 'canUndo()', run: () => view?.canUndo() },
|
|
193
|
+
{ name: 'undo()', run: () => view?.undo() },
|
|
194
|
+
{ name: 'refresh()', run: () => view?.refresh() },
|
|
195
|
+
{ name: 'resize()', run: () => view?.resize() },
|
|
196
|
+
{ name: 'pickAt(画布中心)', run: () => { const probe = canvasProbe(); return view?.pickAt(probe.center.clientX, probe.center.clientY) } },
|
|
197
|
+
{ name: 'getFeaturesInRect(中心半屏)', run: () => view?.getFeaturesInRect(canvasProbe().rect) }
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
/** 确认条上写「哪个节点」:优先业务名,其次是 id,都没有就报下标 */
|
|
201
|
+
const pointLabel = (entry) => {
|
|
202
|
+
if (!entry?.item) {
|
|
203
|
+
return entry?.index === undefined ? '—' : '下标 ' + entry.index
|
|
204
|
+
}
|
|
205
|
+
return entry.item.name ?? entry.item.id ?? '下标 ' + (entry.index ?? '—')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
208
|
/**
|
|
209
209
|
* 实时光标:读画布上的 cursor 就够 —— 拖动是 move、拖到一起进「待确认合并」是 copy、
|
|
210
210
|
* 复制预览与等待选点也走 copy(都取 cursorStyle 里那一档,缺省是系统 copy 光标)。
|
|
@@ -222,13 +222,13 @@ const copyCoordinateLabel = () => {
|
|
|
222
222
|
const coordinate = copyState.value?.coordinate
|
|
223
223
|
return Array.isArray(coordinate) ? coordinate.map(value => Number(value).toFixed(2)).join(', ') : '—'
|
|
224
224
|
}
|
|
225
|
-
|
|
226
|
-
/** 合并的中间态:pending 停在条上等裁决,confirm / merge / cancel 到了就收掉(落地读数看日志) */
|
|
227
|
-
const onMergeEvent = (payload) => {
|
|
228
|
-
mergeState.value = payload?.phase === 'pending' ? payload : null
|
|
229
|
-
syncCursor()
|
|
230
|
-
}
|
|
231
|
-
|
|
225
|
+
|
|
226
|
+
/** 合并的中间态:pending 停在条上等裁决,confirm / merge / cancel 到了就收掉(落地读数看日志) */
|
|
227
|
+
const onMergeEvent = (payload) => {
|
|
228
|
+
mergeState.value = payload?.phase === 'pending' ? payload : null
|
|
229
|
+
syncCursor()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
232
|
/** 复制的收尾条:只在 end(复制结束)那一拍出现 —— start / move 是复制进行中,不弹框 */
|
|
233
233
|
const onCopyEvent = (payload) => {
|
|
234
234
|
copyState.value = payload?.phase === 'end' ? payload : null
|
|
@@ -239,17 +239,17 @@ const onCopyEvent = (payload) => {
|
|
|
239
239
|
* 确认条上的按钮:直接调载荷给的入口。
|
|
240
240
|
* 合并的 confirm 阶段数据还没动(还能改 payload.item 或再 cancel 掉);
|
|
241
241
|
* 复制这一条是结束后才出现的,给的是 payload.revert() —— 数据已经写了,整份撤回去。
|
|
242
|
-
*/
|
|
243
|
-
const confirmMerge = () => {
|
|
244
|
-
mergeState.value?.confirm()
|
|
245
|
-
mergeState.value = null
|
|
246
|
-
syncCursor()
|
|
247
|
-
}
|
|
248
|
-
const cancelMerge = () => {
|
|
249
|
-
mergeState.value?.cancel()
|
|
250
|
-
mergeState.value = null
|
|
251
|
-
syncCursor()
|
|
252
|
-
}
|
|
242
|
+
*/
|
|
243
|
+
const confirmMerge = () => {
|
|
244
|
+
mergeState.value?.confirm()
|
|
245
|
+
mergeState.value = null
|
|
246
|
+
syncCursor()
|
|
247
|
+
}
|
|
248
|
+
const cancelMerge = () => {
|
|
249
|
+
mergeState.value?.cancel()
|
|
250
|
+
mergeState.value = null
|
|
251
|
+
syncCursor()
|
|
252
|
+
}
|
|
253
253
|
const revertCopy = () => {
|
|
254
254
|
copyState.value?.revert?.()
|
|
255
255
|
copyState.value = null
|
|
@@ -259,62 +259,62 @@ const dismissCopy = () => {
|
|
|
259
259
|
copyState.value = null
|
|
260
260
|
syncCursor()
|
|
261
261
|
}
|
|
262
|
-
|
|
263
|
-
onMounted(() => {
|
|
264
|
-
view = createGisView(container.value, buildOptions())
|
|
262
|
+
|
|
263
|
+
onMounted(() => {
|
|
264
|
+
view = createGisView(container.value, buildOptions())
|
|
265
265
|
// 一条 '*' 订阅全部事件:回调拿到的第二参就是事件名;载荷与 options 里的 onXxx 完全同一份
|
|
266
266
|
offAll = view.on('*', (payload, name) => {
|
|
267
|
-
counts.value = { ...counts.value, [name]: (counts.value[name] ?? 0) + 1 }
|
|
268
|
-
// 合并 / 复制:同一条 * 订阅里收中间态,确认条与日志读的是同一份载荷
|
|
269
|
-
if (name === 'pointMerge') {
|
|
270
|
-
onMergeEvent(payload)
|
|
271
|
-
}
|
|
272
|
-
else if (name === 'pointCopy') {
|
|
273
|
-
onCopyEvent(payload)
|
|
274
|
-
}
|
|
275
|
-
if (name === 'cursor' || name === 'pointerDown' || name === 'pointerUp' || name === 'pointMerge' || name === 'pointCopy') {
|
|
276
|
-
syncCursor()
|
|
277
|
-
}
|
|
278
|
-
if (ACTION_EVENTS.has(name) && payload?.action) {
|
|
279
|
-
// 一次操作的判读:什么操作 / 点到了什么 / 拿到了什么数据 —— 左键与右键都走这一套
|
|
280
|
-
const action = payload.action
|
|
267
|
+
counts.value = { ...counts.value, [name]: (counts.value[name] ?? 0) + 1 }
|
|
268
|
+
// 合并 / 复制:同一条 * 订阅里收中间态,确认条与日志读的是同一份载荷
|
|
269
|
+
if (name === 'pointMerge') {
|
|
270
|
+
onMergeEvent(payload)
|
|
271
|
+
}
|
|
272
|
+
else if (name === 'pointCopy') {
|
|
273
|
+
onCopyEvent(payload)
|
|
274
|
+
}
|
|
275
|
+
if (name === 'cursor' || name === 'pointerDown' || name === 'pointerUp' || name === 'pointMerge' || name === 'pointCopy') {
|
|
276
|
+
syncCursor()
|
|
277
|
+
}
|
|
278
|
+
if (ACTION_EVENTS.has(name) && payload?.action) {
|
|
279
|
+
// 一次操作的判读:什么操作 / 点到了什么 / 拿到了什么数据 —— 左键与右键都走这一套
|
|
280
|
+
const action = payload.action
|
|
281
281
|
const event = payload.originalEvent
|
|
282
282
|
const held = [event?.ctrlKey && 'ctrl', event?.metaKey && 'meta', event?.shiftKey && 'shift', event?.altKey && 'alt'].filter(Boolean).join('+')
|
|
283
283
|
const data = hitDataOf(payload)
|
|
284
284
|
lastAction.value = `${name}:${action.type} → ${action.target}${data === null || data === undefined ? '' : ` · ${dataLabel(data)}`}${event ? ` · button ${event.button}` : ''}${held ? ` · ${held}` : ''}${action.moved ? ' · 拖动过' : ''}${action.double ? ' · 双击第二下' : ''}`
|
|
285
|
-
}
|
|
286
|
-
if (!showHighFrequency.value && HIGH_FREQUENCY.has(name)) {
|
|
287
|
-
return
|
|
288
|
-
}
|
|
289
|
-
pushLog(name, describePayload(payload))
|
|
290
|
-
})
|
|
291
|
-
// 光标在很多条路径上都会换(拖动 / 修饰键 / 合并与复制进出),确认条上的按钮又不经过画布 —— 每 250ms 读一次最省心
|
|
292
|
-
cursorTimer = window.setInterval(syncCursor, 250)
|
|
293
|
-
})
|
|
294
|
-
onBeforeUnmount(() => {
|
|
295
|
-
window.clearInterval(cursorTimer)
|
|
296
|
-
cursorTimer = null
|
|
297
|
-
offAll?.()
|
|
298
|
-
offAll = null
|
|
299
|
-
view?.destroy()
|
|
300
|
-
view = null
|
|
301
|
-
})
|
|
302
|
-
</script>
|
|
303
|
-
|
|
304
|
-
<template>
|
|
305
|
-
<section class="dv-demo">
|
|
306
|
-
<div class="dv-stage">
|
|
307
|
-
<div ref="container" class="dv-canvas"></div>
|
|
308
|
-
<div class="dv-toolbar">
|
|
309
|
-
<button type="button" class="dv-button" @click="view?.fitView(60)">适应全部</button>
|
|
310
|
-
</div>
|
|
311
|
-
<div v-if="mergeState" class="dv-confirm">
|
|
312
|
-
<span class="dv-confirm__text">
|
|
313
|
-
合并待确认:{{ pointLabel(mergeState.source) }} → {{ pointLabel(mergeState.target) }} · 拖动的节点停在落点上,数据还没动
|
|
314
|
-
</span>
|
|
315
|
-
<button type="button" class="dv-button dv-button--primary" @click="confirmMerge">确认合并(payload.confirm())</button>
|
|
316
|
-
<button type="button" class="dv-button" @click="cancelMerge">取消(Esc / 右键)</button>
|
|
317
|
-
</div>
|
|
285
|
+
}
|
|
286
|
+
if (!showHighFrequency.value && HIGH_FREQUENCY.has(name)) {
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
pushLog(name, describePayload(payload))
|
|
290
|
+
})
|
|
291
|
+
// 光标在很多条路径上都会换(拖动 / 修饰键 / 合并与复制进出),确认条上的按钮又不经过画布 —— 每 250ms 读一次最省心
|
|
292
|
+
cursorTimer = window.setInterval(syncCursor, 250)
|
|
293
|
+
})
|
|
294
|
+
onBeforeUnmount(() => {
|
|
295
|
+
window.clearInterval(cursorTimer)
|
|
296
|
+
cursorTimer = null
|
|
297
|
+
offAll?.()
|
|
298
|
+
offAll = null
|
|
299
|
+
view?.destroy()
|
|
300
|
+
view = null
|
|
301
|
+
})
|
|
302
|
+
</script>
|
|
303
|
+
|
|
304
|
+
<template>
|
|
305
|
+
<section class="dv-demo">
|
|
306
|
+
<div class="dv-stage">
|
|
307
|
+
<div ref="container" class="dv-canvas"></div>
|
|
308
|
+
<div class="dv-toolbar">
|
|
309
|
+
<button type="button" class="dv-button" @click="view?.fitView(60)">适应全部</button>
|
|
310
|
+
</div>
|
|
311
|
+
<div v-if="mergeState" class="dv-confirm">
|
|
312
|
+
<span class="dv-confirm__text">
|
|
313
|
+
合并待确认:{{ pointLabel(mergeState.source) }} → {{ pointLabel(mergeState.target) }} · 拖动的节点停在落点上,数据还没动
|
|
314
|
+
</span>
|
|
315
|
+
<button type="button" class="dv-button dv-button--primary" @click="confirmMerge">确认合并(payload.confirm())</button>
|
|
316
|
+
<button type="button" class="dv-button" @click="cancelMerge">取消(Esc / 右键)</button>
|
|
317
|
+
</div>
|
|
318
318
|
<div v-else-if="copyState" class="dv-confirm">
|
|
319
319
|
<span class="dv-confirm__text">
|
|
320
320
|
复制结束:{{ pointLabel(copyState.source) }} → ({{ copyCoordinateLabel() }}) · 松开左键才算结束,数据已经写进去了
|
|
@@ -322,73 +322,73 @@ onBeforeUnmount(() => {
|
|
|
322
322
|
<button type="button" class="dv-button dv-button--primary" @click="revertCopy">撤销(payload.revert())</button>
|
|
323
323
|
<button type="button" class="dv-button" @click="dismissCopy">知道了</button>
|
|
324
324
|
</div>
|
|
325
|
-
</div>
|
|
326
|
-
<aside class="dv-panel">
|
|
327
|
-
<div class="dv-group">
|
|
328
|
-
<h3>方法</h3>
|
|
329
|
-
<div class="dv-row">
|
|
330
|
-
<button v-for="item in methods" :key="item.name" type="button" class="dv-button" @click="call(item.name, item.run)">{{ item.name }}</button>
|
|
331
|
-
</div>
|
|
332
|
-
<p class="dv-readout">{{ actionText }}</p>
|
|
333
|
-
<p class="dv-hint">每个按钮只调一个方法,返回值与耗时都打在读数里;返回值是数组的只报条数,详情看日志。</p>
|
|
334
|
-
</div>
|
|
335
|
-
<div class="dv-group">
|
|
325
|
+
</div>
|
|
326
|
+
<aside class="dv-panel">
|
|
327
|
+
<div class="dv-group">
|
|
328
|
+
<h3>方法</h3>
|
|
329
|
+
<div class="dv-row">
|
|
330
|
+
<button v-for="item in methods" :key="item.name" type="button" class="dv-button" @click="call(item.name, item.run)">{{ item.name }}</button>
|
|
331
|
+
</div>
|
|
332
|
+
<p class="dv-readout">{{ actionText }}</p>
|
|
333
|
+
<p class="dv-hint">每个按钮只调一个方法,返回值与耗时都打在读数里;返回值是数组的只报条数,详情看日志。</p>
|
|
334
|
+
</div>
|
|
335
|
+
<div class="dv-group">
|
|
336
336
|
<h3>光标读数<span class="dv-chip">{{ mergeState ? '等裁决' : copyState ? '复制结束' : '常态' }}</span></h3>
|
|
337
|
-
<p class="dv-readout">{{ cursorText }}</p>
|
|
337
|
+
<p class="dv-readout">{{ cursorText }}</p>
|
|
338
338
|
<p class="dv-hint">
|
|
339
339
|
拖一个节点压到另一个节点上松手,鼠标会从 move 变成 copy:引擎进入「待确认合并」后 updateCursor() 把光标切成
|
|
340
340
|
cursorStyle.copy 这一档,复制预览与等待选点走的是同一档(按住 Ctrl / ⌘ 还能用 cursorStyle.ctrl.copy 再换一份)。
|
|
341
341
|
合并没配 confirm,停在原地等下面那条确认条(调载荷的 confirm() / cancel());复制是**松开左键就结束**的:
|
|
342
342
|
Alt 点击出预览 → 跟着鼠标走 → 抬左键落地,弹出来的条是「复制结束」的收尾条,可以 payload.revert() 整份撤销。
|
|
343
343
|
</p>
|
|
344
|
-
</div>
|
|
345
|
-
<div class="dv-group">
|
|
346
|
-
<h3>
|
|
347
|
-
事件日志
|
|
348
|
-
<label class="dv-hint"><input v-model="showHighFrequency" type="checkbox" /> 含高频事件</label>
|
|
349
|
-
</h3>
|
|
350
|
-
<ul class="dv-log">
|
|
351
|
-
<li v-for="line in logLines" :key="line.id"><strong>{{ line.name }}</strong> · {{ line.text }}</li>
|
|
352
|
-
<li v-if="!logLines.length">还没有事件:在画布上点一下 / 拖一段 / 滚一下滚轮</li>
|
|
353
|
-
</ul>
|
|
354
|
-
</div>
|
|
355
|
-
<div class="dv-group">
|
|
356
|
-
<h3>动作判读(action)</h3>
|
|
357
|
-
<p class="dv-readout">{{ lastAction }}</p>
|
|
344
|
+
</div>
|
|
345
|
+
<div class="dv-group">
|
|
346
|
+
<h3>
|
|
347
|
+
事件日志
|
|
348
|
+
<label class="dv-hint"><input v-model="showHighFrequency" type="checkbox" /> 含高频事件</label>
|
|
349
|
+
</h3>
|
|
350
|
+
<ul class="dv-log">
|
|
351
|
+
<li v-for="line in logLines" :key="line.id"><strong>{{ line.name }}</strong> · {{ line.text }}</li>
|
|
352
|
+
<li v-if="!logLines.length">还没有事件:在画布上点一下 / 拖一段 / 滚一下滚轮</li>
|
|
353
|
+
</ul>
|
|
354
|
+
</div>
|
|
355
|
+
<div class="dv-group">
|
|
356
|
+
<h3>动作判读(action)</h3>
|
|
357
|
+
<p class="dv-readout">{{ lastAction }}</p>
|
|
358
358
|
<p class="dv-hint">在画布上点一下 / 右键 / 拖一段 / 框选一下:action.type 说清「是什么操作」、action.target 说清「点到了什么」,命中的数据直接读载荷顶层的 item / channel / label 等字段;按键与修饰键读 originalEvent。</p>
|
|
359
|
-
</div>
|
|
360
|
-
<div class="dv-group">
|
|
361
|
-
<h3>事件计数(* 订阅)</h3>
|
|
362
|
-
<p class="dv-readout">
|
|
363
|
-
<template v-for="(value, name) in counts" :key="name">{{ name }}: {{ value }} </template>
|
|
364
|
-
</p>
|
|
365
|
-
</div>
|
|
366
|
-
</aside>
|
|
367
|
-
</section>
|
|
368
|
-
</template>
|
|
369
|
-
|
|
370
|
-
<style scoped>
|
|
371
|
-
/* 合并 / 复制的确认条:与 DragDropDemo 的「落点草稿」同一套外观,贴在画布下沿正中 */
|
|
372
|
-
.dv-confirm {
|
|
373
|
-
position: absolute;
|
|
374
|
-
left: 50%;
|
|
375
|
-
bottom: 12px;
|
|
376
|
-
z-index: 5;
|
|
377
|
-
display: flex;
|
|
378
|
-
align-items: center;
|
|
379
|
-
flex-wrap: wrap;
|
|
380
|
-
gap: 8px;
|
|
381
|
-
max-width: calc(100% - 24px);
|
|
382
|
-
padding: 6px 8px;
|
|
383
|
-
border-radius: 10px;
|
|
384
|
-
background: rgba(255, 255, 255, 0.95);
|
|
385
|
-
box-shadow: 0 10px 26px rgba(31, 62, 105, 0.2);
|
|
386
|
-
transform: translateX(-50%);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
.dv-confirm__text {
|
|
390
|
-
color: #526b8d;
|
|
391
|
-
font-size: 12px;
|
|
392
|
-
line-height: 1.5;
|
|
393
|
-
}
|
|
394
|
-
</style>
|
|
359
|
+
</div>
|
|
360
|
+
<div class="dv-group">
|
|
361
|
+
<h3>事件计数(* 订阅)</h3>
|
|
362
|
+
<p class="dv-readout">
|
|
363
|
+
<template v-for="(value, name) in counts" :key="name">{{ name }}: {{ value }} </template>
|
|
364
|
+
</p>
|
|
365
|
+
</div>
|
|
366
|
+
</aside>
|
|
367
|
+
</section>
|
|
368
|
+
</template>
|
|
369
|
+
|
|
370
|
+
<style scoped>
|
|
371
|
+
/* 合并 / 复制的确认条:与 DragDropDemo 的「落点草稿」同一套外观,贴在画布下沿正中 */
|
|
372
|
+
.dv-confirm {
|
|
373
|
+
position: absolute;
|
|
374
|
+
left: 50%;
|
|
375
|
+
bottom: 12px;
|
|
376
|
+
z-index: 5;
|
|
377
|
+
display: flex;
|
|
378
|
+
align-items: center;
|
|
379
|
+
flex-wrap: wrap;
|
|
380
|
+
gap: 8px;
|
|
381
|
+
max-width: calc(100% - 24px);
|
|
382
|
+
padding: 6px 8px;
|
|
383
|
+
border-radius: 10px;
|
|
384
|
+
background: rgba(255, 255, 255, 0.95);
|
|
385
|
+
box-shadow: 0 10px 26px rgba(31, 62, 105, 0.2);
|
|
386
|
+
transform: translateX(-50%);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.dv-confirm__text {
|
|
390
|
+
color: #526b8d;
|
|
391
|
+
font-size: 12px;
|
|
392
|
+
line-height: 1.5;
|
|
393
|
+
}
|
|
394
|
+
</style>
|