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/LabelDemo.vue
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
/**
|
|
3
|
-
* 标签交互演示:这一轮两条需求的落地页。
|
|
4
|
-
*
|
|
5
|
-
* 1) 标签自己也接指针:点一下发 labelClick(面板按它选中这一条),
|
|
6
|
-
* 右键发 labelContextMenu(自绘菜单:改内容 / 切位置 / 隐藏 / 重置偏移 / 删除,全部落在 updateLabel / removeLabel 上),
|
|
7
|
-
* 双击发 labelDblClick(这里接成「重置偏移」)。拖动照旧走 labelDrag,松手写回偏移状态。
|
|
8
|
-
* 2) 加标签两条路都演示:
|
|
9
|
-
* - 统一 API:addLabels(kind, refs, 定义) 一次给「全部 / 当前选中 / 整条通道」加;
|
|
10
|
-
* - 点选即加:勾上「点选即加标签」后,在画布上点节点 / 线路 / 通道,选中集一变就给它们加一条。
|
|
11
|
-
*
|
|
12
|
-
* 另外把 labelChange 也订阅上:面板里每一次批量加减改都会在事件日志里留一行,
|
|
13
|
-
* 说明「哪一批、加了几条、跳过几条」。
|
|
14
|
-
*/
|
|
15
|
-
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
16
|
-
import { createGisView } from '../../src/index.js'
|
|
17
|
-
import { buildDemoData } from './shared/demoData.js'
|
|
18
|
-
import './shared/demoStyle.css'
|
|
19
|
-
|
|
20
|
-
const emit = defineEmits(['notice'])
|
|
21
|
-
const container = ref(null)
|
|
22
|
-
const contentInput = ref(null)
|
|
23
|
-
const dataset = buildDemoData({ count: 16 })
|
|
24
|
-
let view = null
|
|
25
|
-
let offEvents = []
|
|
26
|
-
|
|
27
|
-
/** 批量 API 瞄准哪一类要素;右键 / 点选拿到的 labelId 也记在这里,编辑表单按它认人 */
|
|
28
|
-
const labelKind = ref('point')
|
|
29
|
-
const batchLabelId = ref('备注1')
|
|
30
|
-
const hit = ref(null)
|
|
31
|
-
const selectedIds = ref([])
|
|
32
|
-
const selectionText = ref('未选中(先在画布上点一个节点 / 线路 / 通道)')
|
|
1
|
+
<script setup>
|
|
2
|
+
/**
|
|
3
|
+
* 标签交互演示:这一轮两条需求的落地页。
|
|
4
|
+
*
|
|
5
|
+
* 1) 标签自己也接指针:点一下发 labelClick(面板按它选中这一条),
|
|
6
|
+
* 右键发 labelContextMenu(自绘菜单:改内容 / 切位置 / 隐藏 / 重置偏移 / 删除,全部落在 updateLabel / removeLabel 上),
|
|
7
|
+
* 双击发 labelDblClick(这里接成「重置偏移」)。拖动照旧走 labelDrag,松手写回偏移状态。
|
|
8
|
+
* 2) 加标签两条路都演示:
|
|
9
|
+
* - 统一 API:addLabels(kind, refs, 定义) 一次给「全部 / 当前选中 / 整条通道」加;
|
|
10
|
+
* - 点选即加:勾上「点选即加标签」后,在画布上点节点 / 线路 / 通道,选中集一变就给它们加一条。
|
|
11
|
+
*
|
|
12
|
+
* 另外把 labelChange 也订阅上:面板里每一次批量加减改都会在事件日志里留一行,
|
|
13
|
+
* 说明「哪一批、加了几条、跳过几条」。
|
|
14
|
+
*/
|
|
15
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
16
|
+
import { createGisView } from '../../src/index.js'
|
|
17
|
+
import { buildDemoData } from './shared/demoData.js'
|
|
18
|
+
import './shared/demoStyle.css'
|
|
19
|
+
|
|
20
|
+
const emit = defineEmits(['notice'])
|
|
21
|
+
const container = ref(null)
|
|
22
|
+
const contentInput = ref(null)
|
|
23
|
+
const dataset = buildDemoData({ count: 16 })
|
|
24
|
+
let view = null
|
|
25
|
+
let offEvents = []
|
|
26
|
+
|
|
27
|
+
/** 批量 API 瞄准哪一类要素;右键 / 点选拿到的 labelId 也记在这里,编辑表单按它认人 */
|
|
28
|
+
const labelKind = ref('point')
|
|
29
|
+
const batchLabelId = ref('备注1')
|
|
30
|
+
const hit = ref(null)
|
|
31
|
+
const selectedIds = ref([])
|
|
32
|
+
const selectionText = ref('未选中(先在画布上点一个节点 / 线路 / 通道)')
|
|
33
33
|
const autoAddOnSelect = ref(false)
|
|
34
34
|
const overlapMode = ref('shift')
|
|
35
35
|
const logLines = ref([])
|
|
36
|
-
const logSeq = ref(0)
|
|
37
|
-
const form = reactive({ content: '', position: 'bottom-right', color: '#ffe08a', hidden: false })
|
|
38
|
-
const menu = reactive({ open: false, x: 0, y: 0 })
|
|
39
|
-
|
|
40
|
-
const CHANNEL_POSITIONS = [0.2, 0.35, 0.5, 0.65, 0.8]
|
|
41
|
-
const POINT_POSITIONS = ['top', 'top-right', 'right', 'bottom-right', 'bottom', 'bottom-left', 'left', 'top-left', 'center']
|
|
42
|
-
/** 事件日志:最新一条在最上面,只留最近 12 条 */
|
|
36
|
+
const logSeq = ref(0)
|
|
37
|
+
const form = reactive({ content: '', position: 'bottom-right', color: '#ffe08a', hidden: false })
|
|
38
|
+
const menu = reactive({ open: false, x: 0, y: 0 })
|
|
39
|
+
|
|
40
|
+
const CHANNEL_POSITIONS = [0.2, 0.35, 0.5, 0.65, 0.8]
|
|
41
|
+
const POINT_POSITIONS = ['top', 'top-right', 'right', 'bottom-right', 'bottom', 'bottom-left', 'left', 'top-left', 'center']
|
|
42
|
+
/** 事件日志:最新一条在最上面,只留最近 12 条 */
|
|
43
43
|
const pushLog = (name, text) => {
|
|
44
44
|
logSeq.value += 1
|
|
45
45
|
logLines.value = [{ id: logSeq.value, name, text }, ...logLines.value].slice(0, 12)
|
|
@@ -75,32 +75,32 @@ const applyOverlapMode = (mode) => {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/** 把一条标签事件载荷压成一行读数:哪一条、挂在谁身上、内容是什么 */
|
|
78
|
-
const describeLabel = (payload) => {
|
|
79
|
-
if (!payload) {
|
|
80
|
-
return '—'
|
|
81
|
-
}
|
|
82
|
-
const parent = payload.parent?.id ?? payload.id ?? payload.index ?? '—'
|
|
83
|
-
const content = payload.label?.content ?? payload.state?.content ?? '—'
|
|
84
|
-
return `${parent} 上的「${payload.labelId ?? 'default'}」:${content}`
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* 面板上的编辑表单:按当前选中的那一条(或最近右键的那一条)填好。
|
|
89
|
-
* batchLabelId 不跟着点选走 —— 它指的是「批量改 / 批量删」动哪一批,
|
|
90
|
-
* 由三个「加一条」按钮记下自己新建出来的那一批(见 rememberBatch)。
|
|
91
|
-
*/
|
|
92
|
-
const syncFormFromHit = () => {
|
|
93
|
-
const payload = hit.value
|
|
94
|
-
if (!payload) {
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
form.content = String(payload.label?.content ?? '')
|
|
98
|
-
form.position = payload.label?.position ?? (payload.kind === 'line' ? 0.5 : 'bottom-right')
|
|
99
|
-
form.color = payload.state?.style?.color ?? payload.label?.style?.color ?? '#ffe08a'
|
|
100
|
-
form.hidden = payload.state?.hidden === true
|
|
101
|
-
nextTick(() => contentInput.value?.focus())
|
|
102
|
-
}
|
|
103
|
-
|
|
78
|
+
const describeLabel = (payload) => {
|
|
79
|
+
if (!payload) {
|
|
80
|
+
return '—'
|
|
81
|
+
}
|
|
82
|
+
const parent = payload.parent?.id ?? payload.id ?? payload.index ?? '—'
|
|
83
|
+
const content = payload.label?.content ?? payload.state?.content ?? '—'
|
|
84
|
+
return `${parent} 上的「${payload.labelId ?? 'default'}」:${content}`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 面板上的编辑表单:按当前选中的那一条(或最近右键的那一条)填好。
|
|
89
|
+
* batchLabelId 不跟着点选走 —— 它指的是「批量改 / 批量删」动哪一批,
|
|
90
|
+
* 由三个「加一条」按钮记下自己新建出来的那一批(见 rememberBatch)。
|
|
91
|
+
*/
|
|
92
|
+
const syncFormFromHit = () => {
|
|
93
|
+
const payload = hit.value
|
|
94
|
+
if (!payload) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
form.content = String(payload.label?.content ?? '')
|
|
98
|
+
form.position = payload.label?.position ?? (payload.kind === 'line' ? 0.5 : 'bottom-right')
|
|
99
|
+
form.color = payload.state?.style?.color ?? payload.label?.style?.color ?? '#ffe08a'
|
|
100
|
+
form.hidden = payload.state?.hidden === true
|
|
101
|
+
nextTick(() => contentInput.value?.focus())
|
|
102
|
+
}
|
|
103
|
+
|
|
104
104
|
const buildOptions = () => ({
|
|
105
105
|
points: dataset.points.map((point, index) => index === 0
|
|
106
106
|
? {
|
|
@@ -130,12 +130,12 @@ const buildOptions = () => ({
|
|
|
130
130
|
]
|
|
131
131
|
}
|
|
132
132
|
: point),
|
|
133
|
-
lines: dataset.lines,
|
|
134
|
-
channels: dataset.channels,
|
|
135
|
-
channelWidth: 2.4,
|
|
136
|
-
channelInset: 1.8,
|
|
137
|
-
pointStyle: { size: 7, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.5 },
|
|
138
|
-
lineStyle: { width: 1.5, color: 'rgba(120, 168, 226, 0.6)' },
|
|
133
|
+
lines: dataset.lines,
|
|
134
|
+
channels: dataset.channels,
|
|
135
|
+
channelWidth: 2.4,
|
|
136
|
+
channelInset: 1.8,
|
|
137
|
+
pointStyle: { size: 7, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.5 },
|
|
138
|
+
lineStyle: { width: 1.5, color: 'rgba(120, 168, 226, 0.6)' },
|
|
139
139
|
showGrid: true,
|
|
140
140
|
gridColor: 'rgba(126, 168, 214, 0.22)',
|
|
141
141
|
fitPadding: 60,
|
|
@@ -144,23 +144,23 @@ const buildOptions = () => ({
|
|
|
144
144
|
// 标签按优化档显示:maxCount(限数)与避让生效,面板上的「标签清单」才是有意义的那一份
|
|
145
145
|
labelOptimize: true,
|
|
146
146
|
label: {
|
|
147
|
-
content: '{name}',
|
|
148
|
-
position: 'top',
|
|
149
|
-
gap: 8,
|
|
150
|
-
maxCount: 40,
|
|
151
|
-
// 标签自己接指针:能点(labelClick)、能拖(labelDrag)、右键有菜单
|
|
152
|
-
draggable: true,
|
|
153
|
-
editable: true,
|
|
154
|
-
style: { color: '#dbe7f5', fontSize: '12px' }
|
|
155
|
-
},
|
|
156
|
-
lineLabel: {
|
|
157
|
-
content: '{name}',
|
|
158
|
-
position: 0.5,
|
|
159
|
-
maxCount: 10,
|
|
160
|
-
draggable: true,
|
|
161
|
-
editable: true,
|
|
162
|
-
style: { color: '#ffe08a', fontSize: '11px' }
|
|
163
|
-
},
|
|
147
|
+
content: '{name}',
|
|
148
|
+
position: 'top',
|
|
149
|
+
gap: 8,
|
|
150
|
+
maxCount: 40,
|
|
151
|
+
// 标签自己接指针:能点(labelClick)、能拖(labelDrag)、右键有菜单
|
|
152
|
+
draggable: true,
|
|
153
|
+
editable: true,
|
|
154
|
+
style: { color: '#dbe7f5', fontSize: '12px' }
|
|
155
|
+
},
|
|
156
|
+
lineLabel: {
|
|
157
|
+
content: '{name}',
|
|
158
|
+
position: 0.5,
|
|
159
|
+
maxCount: 10,
|
|
160
|
+
draggable: true,
|
|
161
|
+
editable: true,
|
|
162
|
+
style: { color: '#ffe08a', fontSize: '11px' }
|
|
163
|
+
},
|
|
164
164
|
onReady: () => {
|
|
165
165
|
emit('notice', `${dataset.points.length} 个节点 / ${dataset.channels.length} 条通道,标签可点可拖可右键`)
|
|
166
166
|
pushLog('ready', '视图就绪:点一下标签试试(右键还有菜单)')
|
|
@@ -171,40 +171,40 @@ const buildOptions = () => ({
|
|
|
171
171
|
perItemText.value = `第一个节点有 ${perItem.length} 条逐条配置:${perItem.map(label => `${label.id}(${String(label.content)})`).join(' · ')};A 可拖可点,B 只读`
|
|
172
172
|
}
|
|
173
173
|
},
|
|
174
|
-
onSelect: () => syncSelection()
|
|
175
|
-
})
|
|
176
|
-
/* ---- 三条添加路径:统一 API 的三种 refs 写法 ---- */
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* 选中集的唯一出口:画布上点(onSelect)与接口选中(selectChange,比如面板上的「选中第一个节点」)
|
|
180
|
-
* 都汇到这里,面板只认一份读数。签名没变就只刷新读数,不重复触发「点选即加标签」。
|
|
181
|
-
*/
|
|
182
|
-
let lastSelectionKey = ''
|
|
183
|
-
const syncSelection = () => {
|
|
184
|
-
const selections = view?.getSelections() ?? []
|
|
185
|
-
const key = selections.map(entry => `${entry.type}:${entry.index}`).join(',')
|
|
186
|
-
selectedIds.value = selections.map(entry => entry.item?.id ?? String(entry.index))
|
|
187
|
-
selectionText.value = selections.length
|
|
188
|
-
? `选中 ${selections.length} 条:${selectedIds.value.join('、')}`
|
|
189
|
-
: '未选中(先在画布上点一个节点 / 线路 / 通道)'
|
|
190
|
-
if (key === lastSelectionKey) {
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
lastSelectionKey = key
|
|
194
|
-
if (autoAddOnSelect.value && selections.length) {
|
|
195
|
-
addToSelection()
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/** 面板上的「选中一个」:不用在画布上瞄,点一下就把第一条节点 / 线路交给选中集 */
|
|
174
|
+
onSelect: () => syncSelection()
|
|
175
|
+
})
|
|
176
|
+
/* ---- 三条添加路径:统一 API 的三种 refs 写法 ---- */
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 选中集的唯一出口:画布上点(onSelect)与接口选中(selectChange,比如面板上的「选中第一个节点」)
|
|
180
|
+
* 都汇到这里,面板只认一份读数。签名没变就只刷新读数,不重复触发「点选即加标签」。
|
|
181
|
+
*/
|
|
182
|
+
let lastSelectionKey = ''
|
|
183
|
+
const syncSelection = () => {
|
|
184
|
+
const selections = view?.getSelections() ?? []
|
|
185
|
+
const key = selections.map(entry => `${entry.type}:${entry.index}`).join(',')
|
|
186
|
+
selectedIds.value = selections.map(entry => entry.item?.id ?? String(entry.index))
|
|
187
|
+
selectionText.value = selections.length
|
|
188
|
+
? `选中 ${selections.length} 条:${selectedIds.value.join('、')}`
|
|
189
|
+
: '未选中(先在画布上点一个节点 / 线路 / 通道)'
|
|
190
|
+
if (key === lastSelectionKey) {
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
lastSelectionKey = key
|
|
194
|
+
if (autoAddOnSelect.value && selections.length) {
|
|
195
|
+
addToSelection()
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** 面板上的「选中一个」:不用在画布上瞄,点一下就把第一条节点 / 线路交给选中集 */
|
|
200
200
|
const selectFirst = (kind) => {
|
|
201
|
-
const item = kind === 'line' ? view?.getData().lines?.[0] : view?.getData().points?.[0]
|
|
202
|
-
if (!item) {
|
|
203
|
-
return
|
|
204
|
-
}
|
|
205
|
-
if (kind === 'line') {
|
|
206
|
-
labelKind.value = 'line'
|
|
207
|
-
}
|
|
201
|
+
const item = kind === 'line' ? view?.getData().lines?.[0] : view?.getData().points?.[0]
|
|
202
|
+
if (!item) {
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
if (kind === 'line') {
|
|
206
|
+
labelKind.value = 'line'
|
|
207
|
+
}
|
|
208
208
|
view.selectFeatures([item.id])
|
|
209
209
|
pushLog('select', `用接口选中 ${item.id}`)
|
|
210
210
|
}
|
|
@@ -221,287 +221,287 @@ const focusPerItemLabels = () => {
|
|
|
221
221
|
: '逐条交互示例没有读出来'
|
|
222
222
|
pushLog('逐条标签', perItemText.value)
|
|
223
223
|
}
|
|
224
|
-
|
|
225
|
-
/** 记住这一批加出来的 labelId:批量改 / 批量删就动这一批(addOnce 每次写 备注1 / 备注2…) */
|
|
226
|
-
const rememberBatch = (result) => {
|
|
227
|
-
const id = result?.added?.[0]?.labelId
|
|
228
|
-
if (id) {
|
|
229
|
-
batchLabelId.value = id
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/** 每一次「加一条」的序号:内容与 offset 都跟着它走,连着点几次就能看出是几条 */
|
|
234
|
-
const addSerial = ref(0)
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* 三个「加一条」按钮共用的那一步:序号进一位 → 按 refs 调 addLabels。
|
|
238
|
-
* 加出来的标签默认和别的标签共用一张避让网格:位置压在一起就整条不画 ——
|
|
239
|
-
* 面板上看着像「只有第一次加上了」,所以这里给每条错开位置(offset 按序号往下叠),
|
|
240
|
-
* 并把 avoidOverlap 关掉:使用方亲手加的标签一定画出来。
|
|
241
|
-
*/
|
|
242
|
-
const addOnce = (kind, refs, positionOf, color, suffix = '') => {
|
|
243
|
-
const v = view
|
|
244
|
-
if (!v) {
|
|
245
|
-
return null
|
|
246
|
-
}
|
|
247
|
-
addSerial.value += 1
|
|
248
|
-
const serial = addSerial.value
|
|
249
|
-
const result = v.addLabels(kind, refs, (item, index) => ({
|
|
250
|
-
id: `备注${serial}`,
|
|
251
|
-
content: `${item.name ?? item.id} · 备注${suffix}#${serial}`,
|
|
252
|
-
position: typeof positionOf === 'function' ? positionOf(item, index) : positionOf,
|
|
253
|
-
offset: [0, 18 * ((serial - 1) % 5)],
|
|
254
|
-
style: { color },
|
|
255
|
-
avoidOverlap: false
|
|
256
|
-
}))
|
|
257
|
-
rememberBatch(result)
|
|
258
|
-
return result
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/** 给当前选中集加一条(点节点 / 线路 / 通道选中后再点这个按钮,或勾上「点选即加标签」) */
|
|
262
|
-
const addToSelection = () => {
|
|
263
|
-
const result = addOnce(labelKind.value, 'selection', () => (labelKind.value === 'line' ? 0.35 : 'bottom-right'), '#ffd166')
|
|
264
|
-
if (result) {
|
|
265
|
-
pushLog('addLabels', `选中集:新增 ${result.added.length} 条,跳过 ${result.skipped.length} 条 · labelId ${batchLabelId.value}`)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/** 给全部节点加(同一份定义,内容按要素各写各的) */
|
|
270
|
-
const addToAll = () => {
|
|
271
|
-
const result = addOnce(labelKind.value, 'all', () => (labelKind.value === 'line' ? 0.5 : 'top-left'), '#9fe8c8')
|
|
272
|
-
if (result) {
|
|
273
|
-
pushLog('addLabels', `全部:新增 ${result.added.length} 条,跳过 ${result.skipped.length} 条 · labelId ${batchLabelId.value}`)
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/** 给整条通道的回线一起加(refs 写 { channel: id },与「整条通道」在界面上的说法一致) */
|
|
278
|
-
const addToChannel = (channelId) => {
|
|
279
|
-
const result = addOnce('line', { channel: channelId }, 0.6, '#ff9ecb', ' · 通道')
|
|
280
|
-
if (result) {
|
|
281
|
-
pushLog('addLabels', `通道 ${channelId}:新增 ${result.added.length} 条,跳过 ${result.skipped.length} 条 · labelId ${batchLabelId.value}`)
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/** 批量改:所有带 batchLabelId 的要素一起换内容与样式 */
|
|
286
|
-
const updateBatch = () => {
|
|
287
|
-
const result = view?.updateLabels(labelKind.value, 'all', {
|
|
288
|
-
labelId: batchLabelId.value,
|
|
289
|
-
content: form.content,
|
|
290
|
-
style: { color: form.color }
|
|
291
|
-
})
|
|
292
|
-
if (result) {
|
|
293
|
-
pushLog('updateLabels', `改到 ${result.updated.length} 条,跳过 ${result.skipped.length} 条`)
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/** 批量删:运行时加的删掉那一份,声明里的改成隐藏(removeLabels 的返回里分开报) */
|
|
298
|
-
const removeBatch = () => {
|
|
299
|
-
const result = view?.removeLabels(labelKind.value, 'all', batchLabelId.value)
|
|
300
|
-
if (result) {
|
|
301
|
-
pushLog('removeLabels', `删掉 ${result.removed} 条 / 隐藏 ${result.hidden} 条`)
|
|
302
|
-
hit.value = null
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/** 选中集里到底有几条要素:给面板一个「这次会加到几条」的预览读数 */
|
|
307
|
-
const selectionPreview = computed(() => {
|
|
308
|
-
// 读一下 selectedIds:选中集变了这个 computed 才会重算(view 本身不是响应式的)
|
|
309
|
-
const ids = selectedIds.value
|
|
310
|
-
if (!ids.length) {
|
|
311
|
-
return '当前选中集是空的:先在画布上点一个要素'
|
|
312
|
-
}
|
|
313
|
-
const targets = view?.resolveLabelTargets(labelKind.value, 'selection') ?? []
|
|
314
|
-
return targets.length
|
|
315
|
-
? `本次会加到 ${targets.length} 条:${targets.map(item => item.id).join('、')}`
|
|
316
|
-
: '当前选中集里没有这一类要素(换另一类标签试试)'
|
|
317
|
-
})
|
|
318
|
-
/* ---- 标签自己的事件:点选 / 拖动 / 右键 / 双击 ---- */
|
|
319
|
-
|
|
320
|
-
const onLabelClick = (payload) => {
|
|
321
|
-
// 点一下就是把这一条交给面板:内容、来源、父级都在载荷里,不用自己去猜
|
|
322
|
-
hit.value = payload
|
|
323
|
-
syncFormFromHit()
|
|
324
|
-
pushLog('labelClick', describeLabel(payload))
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const onLabelDrag = (payload) => {
|
|
328
|
-
// 拖动过程每次 move 都会发:日志只记 start / end 两条,免得刷屏
|
|
329
|
-
if (payload.phase === 'move') {
|
|
330
|
-
return
|
|
331
|
-
}
|
|
332
|
-
pushLog('labelDrag', `${payload.phase}:${describeLabel(payload)} 偏移 [${payload.offset.map(value => value.toFixed(1)).join(', ')}]`)
|
|
333
|
-
if (payload.phase === 'end' && payload.moved) {
|
|
334
|
-
syncLabelStates()
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const onLabelContextMenu = (payload) => {
|
|
339
|
-
hit.value = payload
|
|
340
|
-
syncFormFromHit()
|
|
224
|
+
|
|
225
|
+
/** 记住这一批加出来的 labelId:批量改 / 批量删就动这一批(addOnce 每次写 备注1 / 备注2…) */
|
|
226
|
+
const rememberBatch = (result) => {
|
|
227
|
+
const id = result?.added?.[0]?.labelId
|
|
228
|
+
if (id) {
|
|
229
|
+
batchLabelId.value = id
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** 每一次「加一条」的序号:内容与 offset 都跟着它走,连着点几次就能看出是几条 */
|
|
234
|
+
const addSerial = ref(0)
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 三个「加一条」按钮共用的那一步:序号进一位 → 按 refs 调 addLabels。
|
|
238
|
+
* 加出来的标签默认和别的标签共用一张避让网格:位置压在一起就整条不画 ——
|
|
239
|
+
* 面板上看着像「只有第一次加上了」,所以这里给每条错开位置(offset 按序号往下叠),
|
|
240
|
+
* 并把 avoidOverlap 关掉:使用方亲手加的标签一定画出来。
|
|
241
|
+
*/
|
|
242
|
+
const addOnce = (kind, refs, positionOf, color, suffix = '') => {
|
|
243
|
+
const v = view
|
|
244
|
+
if (!v) {
|
|
245
|
+
return null
|
|
246
|
+
}
|
|
247
|
+
addSerial.value += 1
|
|
248
|
+
const serial = addSerial.value
|
|
249
|
+
const result = v.addLabels(kind, refs, (item, index) => ({
|
|
250
|
+
id: `备注${serial}`,
|
|
251
|
+
content: `${item.name ?? item.id} · 备注${suffix}#${serial}`,
|
|
252
|
+
position: typeof positionOf === 'function' ? positionOf(item, index) : positionOf,
|
|
253
|
+
offset: [0, 18 * ((serial - 1) % 5)],
|
|
254
|
+
style: { color },
|
|
255
|
+
avoidOverlap: false
|
|
256
|
+
}))
|
|
257
|
+
rememberBatch(result)
|
|
258
|
+
return result
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** 给当前选中集加一条(点节点 / 线路 / 通道选中后再点这个按钮,或勾上「点选即加标签」) */
|
|
262
|
+
const addToSelection = () => {
|
|
263
|
+
const result = addOnce(labelKind.value, 'selection', () => (labelKind.value === 'line' ? 0.35 : 'bottom-right'), '#ffd166')
|
|
264
|
+
if (result) {
|
|
265
|
+
pushLog('addLabels', `选中集:新增 ${result.added.length} 条,跳过 ${result.skipped.length} 条 · labelId ${batchLabelId.value}`)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** 给全部节点加(同一份定义,内容按要素各写各的) */
|
|
270
|
+
const addToAll = () => {
|
|
271
|
+
const result = addOnce(labelKind.value, 'all', () => (labelKind.value === 'line' ? 0.5 : 'top-left'), '#9fe8c8')
|
|
272
|
+
if (result) {
|
|
273
|
+
pushLog('addLabels', `全部:新增 ${result.added.length} 条,跳过 ${result.skipped.length} 条 · labelId ${batchLabelId.value}`)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** 给整条通道的回线一起加(refs 写 { channel: id },与「整条通道」在界面上的说法一致) */
|
|
278
|
+
const addToChannel = (channelId) => {
|
|
279
|
+
const result = addOnce('line', { channel: channelId }, 0.6, '#ff9ecb', ' · 通道')
|
|
280
|
+
if (result) {
|
|
281
|
+
pushLog('addLabels', `通道 ${channelId}:新增 ${result.added.length} 条,跳过 ${result.skipped.length} 条 · labelId ${batchLabelId.value}`)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** 批量改:所有带 batchLabelId 的要素一起换内容与样式 */
|
|
286
|
+
const updateBatch = () => {
|
|
287
|
+
const result = view?.updateLabels(labelKind.value, 'all', {
|
|
288
|
+
labelId: batchLabelId.value,
|
|
289
|
+
content: form.content,
|
|
290
|
+
style: { color: form.color }
|
|
291
|
+
})
|
|
292
|
+
if (result) {
|
|
293
|
+
pushLog('updateLabels', `改到 ${result.updated.length} 条,跳过 ${result.skipped.length} 条`)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** 批量删:运行时加的删掉那一份,声明里的改成隐藏(removeLabels 的返回里分开报) */
|
|
298
|
+
const removeBatch = () => {
|
|
299
|
+
const result = view?.removeLabels(labelKind.value, 'all', batchLabelId.value)
|
|
300
|
+
if (result) {
|
|
301
|
+
pushLog('removeLabels', `删掉 ${result.removed} 条 / 隐藏 ${result.hidden} 条`)
|
|
302
|
+
hit.value = null
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** 选中集里到底有几条要素:给面板一个「这次会加到几条」的预览读数 */
|
|
307
|
+
const selectionPreview = computed(() => {
|
|
308
|
+
// 读一下 selectedIds:选中集变了这个 computed 才会重算(view 本身不是响应式的)
|
|
309
|
+
const ids = selectedIds.value
|
|
310
|
+
if (!ids.length) {
|
|
311
|
+
return '当前选中集是空的:先在画布上点一个要素'
|
|
312
|
+
}
|
|
313
|
+
const targets = view?.resolveLabelTargets(labelKind.value, 'selection') ?? []
|
|
314
|
+
return targets.length
|
|
315
|
+
? `本次会加到 ${targets.length} 条:${targets.map(item => item.id).join('、')}`
|
|
316
|
+
: '当前选中集里没有这一类要素(换另一类标签试试)'
|
|
317
|
+
})
|
|
318
|
+
/* ---- 标签自己的事件:点选 / 拖动 / 右键 / 双击 ---- */
|
|
319
|
+
|
|
320
|
+
const onLabelClick = (payload) => {
|
|
321
|
+
// 点一下就是把这一条交给面板:内容、来源、父级都在载荷里,不用自己去猜
|
|
322
|
+
hit.value = payload
|
|
323
|
+
syncFormFromHit()
|
|
324
|
+
pushLog('labelClick', describeLabel(payload))
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const onLabelDrag = (payload) => {
|
|
328
|
+
// 拖动过程每次 move 都会发:日志只记 start / end 两条,免得刷屏
|
|
329
|
+
if (payload.phase === 'move') {
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
pushLog('labelDrag', `${payload.phase}:${describeLabel(payload)} 偏移 [${payload.offset.map(value => value.toFixed(1)).join(', ')}]`)
|
|
333
|
+
if (payload.phase === 'end' && payload.moved) {
|
|
334
|
+
syncLabelStates()
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const onLabelContextMenu = (payload) => {
|
|
339
|
+
hit.value = payload
|
|
340
|
+
syncFormFromHit()
|
|
341
341
|
// 用原始指针事件的 clientX / clientY 把自绘菜单贴在指针位置(系统菜单已被 preventContextMenu 收掉)
|
|
342
342
|
menu.open = true
|
|
343
343
|
menu.x = payload.originalEvent?.clientX ?? 0
|
|
344
344
|
menu.y = payload.originalEvent?.clientY ?? 0
|
|
345
345
|
pushLog('labelContextMenu', `右键 ${describeLabel(payload)} @ ${menu.x}, ${menu.y}`)
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const onLabelDblClick = (payload) => {
|
|
349
|
-
// 双击接成「重置偏移」:改过的内容与样式留着,只把拖出来的那一段清掉
|
|
350
|
-
const result = view?.resetLabel(payload.kind, payload.id ?? payload.index, payload.labelId)
|
|
351
|
-
pushLog('labelDblClick', `重置偏移:${describeLabel(payload)} → ${result ? '已还原' : '本来就没拖过'}`)
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/** 编辑面板「应用」:内容 / 位置 / 颜色 / 隐藏一起写进 updateLabel */
|
|
355
|
-
const applyEdit = () => {
|
|
356
|
-
const payload = hit.value
|
|
357
|
-
if (!payload) {
|
|
358
|
-
return
|
|
359
|
-
}
|
|
360
|
-
const patch = {
|
|
361
|
-
labelId: payload.labelId,
|
|
362
|
-
content: form.content,
|
|
363
|
-
position: payload.kind === 'line' ? Number(form.position) : form.position,
|
|
364
|
-
style: { color: form.color },
|
|
365
|
-
hidden: form.hidden
|
|
366
|
-
}
|
|
367
|
-
const state = view?.updateLabel(payload.kind, payload.id ?? payload.index, patch)
|
|
368
|
-
pushLog('updateLabel', state ? `${describeLabel(payload)} → 内容「${form.content}」` : '这一条已经不在了')
|
|
369
|
-
hit.value = state ? { ...payload, label: { ...payload.label, content: form.content, position: patch.position }, state } : null
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/** 右键菜单:切位置(点标签 8 个锚点轮换,线路标签沿线比例轮换) */
|
|
373
|
-
const cyclePosition = () => {
|
|
374
|
-
const payload = hit.value
|
|
375
|
-
if (!payload) {
|
|
376
|
-
return
|
|
377
|
-
}
|
|
378
|
-
const list = payload.kind === 'line' ? CHANNEL_POSITIONS : POINT_POSITIONS
|
|
379
|
-
const current = payload.label?.position ?? (payload.kind === 'line' ? 0.5 : 'top')
|
|
380
|
-
const next = list[(Math.max(0, list.indexOf(current)) + 1) % list.length]
|
|
381
|
-
const state = view?.updateLabel(payload.kind, payload.id ?? payload.index, { labelId: payload.labelId, position: next })
|
|
382
|
-
form.position = next
|
|
383
|
-
// 读数跟着换:面板上的「位置」与事件日志都读 hit.value,不回写就会停在改之前的那一档
|
|
384
|
-
hit.value = { ...payload, label: { ...payload.label, position: next }, state: state ?? payload.state }
|
|
385
|
-
pushLog('updateLabel', `${describeLabel(payload)} 位置 → ${next}`)
|
|
386
|
-
menu.open = false
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const toggleHidden = () => {
|
|
390
|
-
const payload = hit.value
|
|
391
|
-
if (!payload) {
|
|
392
|
-
return
|
|
393
|
-
}
|
|
394
|
-
form.hidden = !form.hidden
|
|
395
|
-
const state = view?.updateLabel(payload.kind, payload.id ?? payload.index, { labelId: payload.labelId, hidden: form.hidden })
|
|
396
|
-
hit.value = { ...payload, state: state ?? payload.state }
|
|
397
|
-
pushLog('updateLabel', `${describeLabel(payload)} ${form.hidden ? '已隐藏' : '已显示'}`)
|
|
398
|
-
menu.open = false
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const resetOffset = () => {
|
|
402
|
-
const payload = hit.value
|
|
403
|
-
if (!payload) {
|
|
404
|
-
return
|
|
405
|
-
}
|
|
406
|
-
view?.resetLabel(payload.kind, payload.id ?? payload.index, payload.labelId)
|
|
407
|
-
pushLog('resetLabel', `重置偏移:${describeLabel(payload)}`)
|
|
408
|
-
menu.open = false
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/** 删一条:运行时加的直接删,声明里的转成隐藏 */
|
|
412
|
-
const removeOne = () => {
|
|
413
|
-
const payload = hit.value
|
|
414
|
-
if (!payload) {
|
|
415
|
-
return
|
|
416
|
-
}
|
|
417
|
-
const result = view?.removeLabel(payload.kind, payload.id ?? payload.index, payload.labelId)
|
|
418
|
-
pushLog('removeLabel', result ? `${describeLabel(payload)} → ${result.removed ? '已删除' : '改成隐藏'}` : '没找到这一条')
|
|
419
|
-
hit.value = null
|
|
420
|
-
menu.open = false
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const closeMenu = () => {
|
|
424
|
-
menu.open = false
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
const onKeyDown = (event) => {
|
|
428
|
-
if (event.key === 'Escape') {
|
|
429
|
-
closeMenu()
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
/** 菜单的收起:点菜单以外的地方、按 Esc、执行完动作都收 */
|
|
433
|
-
const onGlobalPointerDown = (event) => {
|
|
434
|
-
if (event?.target?.closest?.('.dv-menu')) {
|
|
435
|
-
return
|
|
436
|
-
}
|
|
437
|
-
menu.open = false
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
const describeChange = (payload) => {
|
|
441
|
-
if (!payload) {
|
|
442
|
-
return '—'
|
|
443
|
-
}
|
|
444
|
-
if (payload.count !== undefined && payload.count !== null) {
|
|
445
|
-
return `${payload.action}:${payload.kind ?? '—'} · ${payload.count} 条`
|
|
446
|
-
}
|
|
447
|
-
return `${payload.action}:${payload.kind ?? '—'} · ${payload.id ?? payload.index ?? '—'} · ${payload.labelId ?? 'default'}`
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* 标签覆盖读数(getLabelStates):一次列出「哪些标签被改过 / 拖过 / 隐藏过、哪些是运行时加的」,
|
|
452
|
-
* 不用自己拿 getLabels 逐条比对。labelChange 一响就刷新一次(批量加减改都在这一条路上)。
|
|
453
|
-
*/
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const onLabelDblClick = (payload) => {
|
|
349
|
+
// 双击接成「重置偏移」:改过的内容与样式留着,只把拖出来的那一段清掉
|
|
350
|
+
const result = view?.resetLabel(payload.kind, payload.id ?? payload.index, payload.labelId)
|
|
351
|
+
pushLog('labelDblClick', `重置偏移:${describeLabel(payload)} → ${result ? '已还原' : '本来就没拖过'}`)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** 编辑面板「应用」:内容 / 位置 / 颜色 / 隐藏一起写进 updateLabel */
|
|
355
|
+
const applyEdit = () => {
|
|
356
|
+
const payload = hit.value
|
|
357
|
+
if (!payload) {
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
const patch = {
|
|
361
|
+
labelId: payload.labelId,
|
|
362
|
+
content: form.content,
|
|
363
|
+
position: payload.kind === 'line' ? Number(form.position) : form.position,
|
|
364
|
+
style: { color: form.color },
|
|
365
|
+
hidden: form.hidden
|
|
366
|
+
}
|
|
367
|
+
const state = view?.updateLabel(payload.kind, payload.id ?? payload.index, patch)
|
|
368
|
+
pushLog('updateLabel', state ? `${describeLabel(payload)} → 内容「${form.content}」` : '这一条已经不在了')
|
|
369
|
+
hit.value = state ? { ...payload, label: { ...payload.label, content: form.content, position: patch.position }, state } : null
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** 右键菜单:切位置(点标签 8 个锚点轮换,线路标签沿线比例轮换) */
|
|
373
|
+
const cyclePosition = () => {
|
|
374
|
+
const payload = hit.value
|
|
375
|
+
if (!payload) {
|
|
376
|
+
return
|
|
377
|
+
}
|
|
378
|
+
const list = payload.kind === 'line' ? CHANNEL_POSITIONS : POINT_POSITIONS
|
|
379
|
+
const current = payload.label?.position ?? (payload.kind === 'line' ? 0.5 : 'top')
|
|
380
|
+
const next = list[(Math.max(0, list.indexOf(current)) + 1) % list.length]
|
|
381
|
+
const state = view?.updateLabel(payload.kind, payload.id ?? payload.index, { labelId: payload.labelId, position: next })
|
|
382
|
+
form.position = next
|
|
383
|
+
// 读数跟着换:面板上的「位置」与事件日志都读 hit.value,不回写就会停在改之前的那一档
|
|
384
|
+
hit.value = { ...payload, label: { ...payload.label, position: next }, state: state ?? payload.state }
|
|
385
|
+
pushLog('updateLabel', `${describeLabel(payload)} 位置 → ${next}`)
|
|
386
|
+
menu.open = false
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const toggleHidden = () => {
|
|
390
|
+
const payload = hit.value
|
|
391
|
+
if (!payload) {
|
|
392
|
+
return
|
|
393
|
+
}
|
|
394
|
+
form.hidden = !form.hidden
|
|
395
|
+
const state = view?.updateLabel(payload.kind, payload.id ?? payload.index, { labelId: payload.labelId, hidden: form.hidden })
|
|
396
|
+
hit.value = { ...payload, state: state ?? payload.state }
|
|
397
|
+
pushLog('updateLabel', `${describeLabel(payload)} ${form.hidden ? '已隐藏' : '已显示'}`)
|
|
398
|
+
menu.open = false
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const resetOffset = () => {
|
|
402
|
+
const payload = hit.value
|
|
403
|
+
if (!payload) {
|
|
404
|
+
return
|
|
405
|
+
}
|
|
406
|
+
view?.resetLabel(payload.kind, payload.id ?? payload.index, payload.labelId)
|
|
407
|
+
pushLog('resetLabel', `重置偏移:${describeLabel(payload)}`)
|
|
408
|
+
menu.open = false
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** 删一条:运行时加的直接删,声明里的转成隐藏 */
|
|
412
|
+
const removeOne = () => {
|
|
413
|
+
const payload = hit.value
|
|
414
|
+
if (!payload) {
|
|
415
|
+
return
|
|
416
|
+
}
|
|
417
|
+
const result = view?.removeLabel(payload.kind, payload.id ?? payload.index, payload.labelId)
|
|
418
|
+
pushLog('removeLabel', result ? `${describeLabel(payload)} → ${result.removed ? '已删除' : '改成隐藏'}` : '没找到这一条')
|
|
419
|
+
hit.value = null
|
|
420
|
+
menu.open = false
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const closeMenu = () => {
|
|
424
|
+
menu.open = false
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const onKeyDown = (event) => {
|
|
428
|
+
if (event.key === 'Escape') {
|
|
429
|
+
closeMenu()
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/** 菜单的收起:点菜单以外的地方、按 Esc、执行完动作都收 */
|
|
433
|
+
const onGlobalPointerDown = (event) => {
|
|
434
|
+
if (event?.target?.closest?.('.dv-menu')) {
|
|
435
|
+
return
|
|
436
|
+
}
|
|
437
|
+
menu.open = false
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const describeChange = (payload) => {
|
|
441
|
+
if (!payload) {
|
|
442
|
+
return '—'
|
|
443
|
+
}
|
|
444
|
+
if (payload.count !== undefined && payload.count !== null) {
|
|
445
|
+
return `${payload.action}:${payload.kind ?? '—'} · ${payload.count} 条`
|
|
446
|
+
}
|
|
447
|
+
return `${payload.action}:${payload.kind ?? '—'} · ${payload.id ?? payload.index ?? '—'} · ${payload.labelId ?? 'default'}`
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* 标签覆盖读数(getLabelStates):一次列出「哪些标签被改过 / 拖过 / 隐藏过、哪些是运行时加的」,
|
|
452
|
+
* 不用自己拿 getLabels 逐条比对。labelChange 一响就刷新一次(批量加减改都在这一条路上)。
|
|
453
|
+
*/
|
|
454
454
|
const labelStatesText = ref('—')
|
|
455
455
|
const perItemText = ref('第一个节点有两条标签:A 可拖 / 可编辑,B 只读(不可拖 / 不可点)')
|
|
456
|
-
const syncLabelStates = () => {
|
|
457
|
-
const states = view?.getLabelStates() ?? []
|
|
458
|
-
if (!states.length) {
|
|
459
|
-
labelStatesText.value = '还没有改过的标签:改个内容 / 拖一段 / 右键隐藏,这里会列出它的覆盖读数'
|
|
460
|
-
return
|
|
461
|
-
}
|
|
462
|
-
const added = states.filter(state => state.added).length
|
|
463
|
-
const moved = states.filter(state => (state.offset?.[0] ?? 0) !== 0 || (state.offset?.[1] ?? 0) !== 0).length
|
|
464
|
-
const hidden = states.filter(state => state.hidden === true).length
|
|
465
|
-
const head = states.slice(0, 3).map(state => `${state.id ?? state.index}·${state.labelId}${state.hidden ? '(隐藏)' : ''}`).join(',')
|
|
466
|
-
labelStatesText.value = `改过 ${states.length} 条(运行时加 ${added} / 拖过 ${moved} / 隐藏 ${hidden}):${head}${states.length > 3 ? ' …' : ''}`
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
onMounted(() => {
|
|
470
|
-
view = createGisView(container.value, buildOptions())
|
|
471
|
-
offEvents = [
|
|
472
|
-
['labelClick', onLabelClick],
|
|
473
|
-
['labelDrag', onLabelDrag],
|
|
474
|
-
['labelContextMenu', onLabelContextMenu],
|
|
475
|
-
['labelDblClick', onLabelDblClick],
|
|
476
|
-
['labelChange', (payload) => {
|
|
477
|
-
pushLog('labelChange', describeChange(payload))
|
|
478
|
-
syncLabelStates()
|
|
479
|
-
}],
|
|
456
|
+
const syncLabelStates = () => {
|
|
457
|
+
const states = view?.getLabelStates() ?? []
|
|
458
|
+
if (!states.length) {
|
|
459
|
+
labelStatesText.value = '还没有改过的标签:改个内容 / 拖一段 / 右键隐藏,这里会列出它的覆盖读数'
|
|
460
|
+
return
|
|
461
|
+
}
|
|
462
|
+
const added = states.filter(state => state.added).length
|
|
463
|
+
const moved = states.filter(state => (state.offset?.[0] ?? 0) !== 0 || (state.offset?.[1] ?? 0) !== 0).length
|
|
464
|
+
const hidden = states.filter(state => state.hidden === true).length
|
|
465
|
+
const head = states.slice(0, 3).map(state => `${state.id ?? state.index}·${state.labelId}${state.hidden ? '(隐藏)' : ''}`).join(',')
|
|
466
|
+
labelStatesText.value = `改过 ${states.length} 条(运行时加 ${added} / 拖过 ${moved} / 隐藏 ${hidden}):${head}${states.length > 3 ? ' …' : ''}`
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
onMounted(() => {
|
|
470
|
+
view = createGisView(container.value, buildOptions())
|
|
471
|
+
offEvents = [
|
|
472
|
+
['labelClick', onLabelClick],
|
|
473
|
+
['labelDrag', onLabelDrag],
|
|
474
|
+
['labelContextMenu', onLabelContextMenu],
|
|
475
|
+
['labelDblClick', onLabelDblClick],
|
|
476
|
+
['labelChange', (payload) => {
|
|
477
|
+
pushLog('labelChange', describeChange(payload))
|
|
478
|
+
syncLabelStates()
|
|
479
|
+
}],
|
|
480
480
|
// 选中镜像统一走 selectChange:覆盖点击与接口来源,且重复设同一份选中集不会重复通知
|
|
481
481
|
['selectChange', () => syncSelection()]
|
|
482
|
-
].map(([name, handler]) => view.on(name, handler))
|
|
483
|
-
window.addEventListener('keydown', onKeyDown)
|
|
484
|
-
window.addEventListener('pointerdown', onGlobalPointerDown)
|
|
485
|
-
})
|
|
486
|
-
|
|
487
|
-
onBeforeUnmount(() => {
|
|
488
|
-
window.removeEventListener('keydown', onKeyDown)
|
|
489
|
-
window.removeEventListener('pointerdown', onGlobalPointerDown)
|
|
490
|
-
offEvents.forEach(off => off())
|
|
491
|
-
offEvents = []
|
|
492
|
-
view?.destroy()
|
|
493
|
-
view = null
|
|
494
|
-
})
|
|
495
|
-
</script>
|
|
496
|
-
<template>
|
|
497
|
-
<section class="dv-demo">
|
|
498
|
-
<div class="dv-stage">
|
|
499
|
-
<div ref="container" class="dv-canvas"></div>
|
|
500
|
-
<div class="dv-toolbar">
|
|
501
|
-
<button type="button" class="dv-button" @click="view?.fitView(60)">适应全部</button>
|
|
482
|
+
].map(([name, handler]) => view.on(name, handler))
|
|
483
|
+
window.addEventListener('keydown', onKeyDown)
|
|
484
|
+
window.addEventListener('pointerdown', onGlobalPointerDown)
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
onBeforeUnmount(() => {
|
|
488
|
+
window.removeEventListener('keydown', onKeyDown)
|
|
489
|
+
window.removeEventListener('pointerdown', onGlobalPointerDown)
|
|
490
|
+
offEvents.forEach(off => off())
|
|
491
|
+
offEvents = []
|
|
492
|
+
view?.destroy()
|
|
493
|
+
view = null
|
|
494
|
+
})
|
|
495
|
+
</script>
|
|
496
|
+
<template>
|
|
497
|
+
<section class="dv-demo">
|
|
498
|
+
<div class="dv-stage">
|
|
499
|
+
<div ref="container" class="dv-canvas"></div>
|
|
500
|
+
<div class="dv-toolbar">
|
|
501
|
+
<button type="button" class="dv-button" @click="view?.fitView(60)">适应全部</button>
|
|
502
502
|
<button type="button" class="dv-button" @click="view?.selectFeatures(null)">清空选中</button>
|
|
503
|
-
</div>
|
|
504
|
-
</div>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
505
|
<aside class="dv-panel">
|
|
506
506
|
<div class="dv-group">
|
|
507
507
|
<h3>一键换标签内容<span class="dv-chip">setLabelContent</span></h3>
|
|
@@ -538,100 +538,100 @@ onBeforeUnmount(() => {
|
|
|
538
538
|
<div class="dv-group">
|
|
539
539
|
<h3>
|
|
540
540
|
加标签(统一 API)
|
|
541
|
-
<span class="dv-chips">
|
|
542
|
-
<button
|
|
543
|
-
v-for="item in ['point', 'line']"
|
|
544
|
-
:key="item"
|
|
545
|
-
type="button"
|
|
546
|
-
class="dv-chip"
|
|
547
|
-
:class="{ 'dv-chip--active': labelKind === item }"
|
|
548
|
-
@click="labelKind = item"
|
|
549
|
-
>{{ item === 'point' ? '节点标签' : '线路标签' }}</button>
|
|
550
|
-
</span>
|
|
551
|
-
</h3>
|
|
552
|
-
<div class="dv-row">
|
|
553
|
-
<button type="button" class="dv-button dv-button--primary" :disabled="!selectedIds.length" @click="addToSelection">给选中加一条</button>
|
|
554
|
-
<button type="button" class="dv-button" @click="addToAll">给全部加一条</button>
|
|
555
|
-
</div>
|
|
556
|
-
<div class="dv-row">
|
|
557
|
-
<button type="button" class="dv-button" @click="selectFirst('point')">选中第一个节点</button>
|
|
558
|
-
<button type="button" class="dv-button" @click="selectFirst('line')">选中第一条线路</button>
|
|
559
|
-
</div>
|
|
560
|
-
<div class="dv-row">
|
|
561
|
-
<label><input v-model="autoAddOnSelect" type="checkbox" /> 点选即加标签(点节点 / 线路 / 通道)</label>
|
|
562
|
-
</div>
|
|
563
|
-
<div class="dv-row">
|
|
564
|
-
<button
|
|
565
|
-
v-for="channel in dataset.channels"
|
|
566
|
-
:key="channel.id"
|
|
567
|
-
type="button"
|
|
568
|
-
class="dv-button"
|
|
569
|
-
@click="addToChannel(channel.id)"
|
|
570
|
-
>整条通道加:{{ channel.name ?? channel.id }}</button>
|
|
571
|
-
</div>
|
|
572
|
-
<p class="dv-readout">{{ selectionText }}</p>
|
|
573
|
-
<p class="dv-hint">{{ selectionPreview }}</p>
|
|
574
|
-
</div>
|
|
575
|
-
|
|
576
|
-
<div class="dv-group">
|
|
577
|
-
<h3>编辑这一条<span class="dv-chip">{{ hit ? (hit.labelId ?? 'default') : '未选中' }}</span></h3>
|
|
578
|
-
<p class="dv-readout">{{ hit ? describeLabel(hit) : '点一下标签选中它,或者右键标签弹出菜单' }}</p>
|
|
579
|
-
<div class="dv-row">
|
|
580
|
-
<label>内容<input ref="contentInput" v-model="form.content" class="dv-input" /></label>
|
|
581
|
-
</div>
|
|
582
|
-
<div class="dv-row">
|
|
583
|
-
<label>位置
|
|
584
|
-
<select v-model="form.position">
|
|
585
|
-
<option v-for="item in POINT_POSITIONS" :key="item" :value="item">{{ item }}</option>
|
|
586
|
-
</select>
|
|
587
|
-
</label>
|
|
588
|
-
<label>颜色<input v-model="form.color" type="color" /></label>
|
|
589
|
-
<label><input v-model="form.hidden" type="checkbox" /> 隐藏</label>
|
|
590
|
-
</div>
|
|
591
|
-
<div class="dv-row">
|
|
592
|
-
<button type="button" class="dv-button dv-button--primary" :disabled="!hit" @click="applyEdit">应用(updateLabel)</button>
|
|
593
|
-
<button type="button" class="dv-button" :disabled="!hit" @click="resetOffset">重置偏移</button>
|
|
594
|
-
<button type="button" class="dv-button dv-button--danger" :disabled="!hit" @click="removeOne">删除</button>
|
|
595
|
-
</div>
|
|
596
|
-
<div class="dv-row">
|
|
597
|
-
<button type="button" class="dv-button" @click="updateBatch">批量改这一类全部</button>
|
|
598
|
-
<button type="button" class="dv-button dv-button--danger" @click="removeBatch">批量删这一类全部</button>
|
|
599
|
-
</div>
|
|
600
|
-
<p class="dv-readout">{{ labelStatesText }}</p>
|
|
601
|
-
<p class="dv-hint">
|
|
602
|
-
批量动的是 labelId「{{ batchLabelId }}」:三个「加一条」按钮每点一次都是一批新标签
|
|
603
|
-
(id 按序号写 备注1 / 备注2…),内容与 offset 也跟着序号走 —— 加出来的标签默认要和已经放下的
|
|
604
|
-
标签共用避让网格,位置压在一起就整条不画(看上去像「只加上了一次」),这里给每条错开位置并关掉避让。
|
|
605
|
-
</p>
|
|
606
|
-
</div>
|
|
607
|
-
|
|
608
|
-
<div class="dv-group">
|
|
609
|
-
<h3>事件日志</h3>
|
|
610
|
-
<ul class="dv-log">
|
|
611
|
-
<li v-for="line in logLines" :key="line.id"><strong>{{ line.name }}</strong> · {{ line.text }}</li>
|
|
612
|
-
<li v-if="!logLines.length">还没有事件:点一下标签 / 拖一段 / 右键试试</li>
|
|
613
|
-
</ul>
|
|
614
|
-
</div>
|
|
615
|
-
</aside>
|
|
616
|
-
|
|
617
|
-
<div v-if="menu.open" class="dv-menu" :style="{ left: `${menu.x}px`, top: `${menu.y}px` }">
|
|
618
|
-
<p class="dv-menu__title">{{ hit ? describeLabel(hit) : '' }}</p>
|
|
619
|
-
<button type="button" @click="applyEdit">改内容</button>
|
|
620
|
-
<button type="button" @click="cyclePosition">换位置</button>
|
|
621
|
-
<button type="button" @click="resetOffset">重置偏移</button>
|
|
622
|
-
<button type="button" @click="toggleHidden">{{ form.hidden ? '显示这一条' : '隐藏这一条' }}</button>
|
|
623
|
-
<button type="button" @click="removeOne">删除这一条</button>
|
|
624
|
-
</div>
|
|
625
|
-
</section>
|
|
626
|
-
</template>
|
|
627
|
-
|
|
628
|
-
<style scoped>
|
|
629
|
-
.dv-input {
|
|
630
|
-
width: 132px;
|
|
631
|
-
padding: 4px 6px;
|
|
632
|
-
border: 1px solid #d6e0ec;
|
|
633
|
-
border-radius: 6px;
|
|
634
|
-
color: #1f2a44;
|
|
635
|
-
font-size: 12px;
|
|
636
|
-
}
|
|
637
|
-
</style>
|
|
541
|
+
<span class="dv-chips">
|
|
542
|
+
<button
|
|
543
|
+
v-for="item in ['point', 'line']"
|
|
544
|
+
:key="item"
|
|
545
|
+
type="button"
|
|
546
|
+
class="dv-chip"
|
|
547
|
+
:class="{ 'dv-chip--active': labelKind === item }"
|
|
548
|
+
@click="labelKind = item"
|
|
549
|
+
>{{ item === 'point' ? '节点标签' : '线路标签' }}</button>
|
|
550
|
+
</span>
|
|
551
|
+
</h3>
|
|
552
|
+
<div class="dv-row">
|
|
553
|
+
<button type="button" class="dv-button dv-button--primary" :disabled="!selectedIds.length" @click="addToSelection">给选中加一条</button>
|
|
554
|
+
<button type="button" class="dv-button" @click="addToAll">给全部加一条</button>
|
|
555
|
+
</div>
|
|
556
|
+
<div class="dv-row">
|
|
557
|
+
<button type="button" class="dv-button" @click="selectFirst('point')">选中第一个节点</button>
|
|
558
|
+
<button type="button" class="dv-button" @click="selectFirst('line')">选中第一条线路</button>
|
|
559
|
+
</div>
|
|
560
|
+
<div class="dv-row">
|
|
561
|
+
<label><input v-model="autoAddOnSelect" type="checkbox" /> 点选即加标签(点节点 / 线路 / 通道)</label>
|
|
562
|
+
</div>
|
|
563
|
+
<div class="dv-row">
|
|
564
|
+
<button
|
|
565
|
+
v-for="channel in dataset.channels"
|
|
566
|
+
:key="channel.id"
|
|
567
|
+
type="button"
|
|
568
|
+
class="dv-button"
|
|
569
|
+
@click="addToChannel(channel.id)"
|
|
570
|
+
>整条通道加:{{ channel.name ?? channel.id }}</button>
|
|
571
|
+
</div>
|
|
572
|
+
<p class="dv-readout">{{ selectionText }}</p>
|
|
573
|
+
<p class="dv-hint">{{ selectionPreview }}</p>
|
|
574
|
+
</div>
|
|
575
|
+
|
|
576
|
+
<div class="dv-group">
|
|
577
|
+
<h3>编辑这一条<span class="dv-chip">{{ hit ? (hit.labelId ?? 'default') : '未选中' }}</span></h3>
|
|
578
|
+
<p class="dv-readout">{{ hit ? describeLabel(hit) : '点一下标签选中它,或者右键标签弹出菜单' }}</p>
|
|
579
|
+
<div class="dv-row">
|
|
580
|
+
<label>内容<input ref="contentInput" v-model="form.content" class="dv-input" /></label>
|
|
581
|
+
</div>
|
|
582
|
+
<div class="dv-row">
|
|
583
|
+
<label>位置
|
|
584
|
+
<select v-model="form.position">
|
|
585
|
+
<option v-for="item in POINT_POSITIONS" :key="item" :value="item">{{ item }}</option>
|
|
586
|
+
</select>
|
|
587
|
+
</label>
|
|
588
|
+
<label>颜色<input v-model="form.color" type="color" /></label>
|
|
589
|
+
<label><input v-model="form.hidden" type="checkbox" /> 隐藏</label>
|
|
590
|
+
</div>
|
|
591
|
+
<div class="dv-row">
|
|
592
|
+
<button type="button" class="dv-button dv-button--primary" :disabled="!hit" @click="applyEdit">应用(updateLabel)</button>
|
|
593
|
+
<button type="button" class="dv-button" :disabled="!hit" @click="resetOffset">重置偏移</button>
|
|
594
|
+
<button type="button" class="dv-button dv-button--danger" :disabled="!hit" @click="removeOne">删除</button>
|
|
595
|
+
</div>
|
|
596
|
+
<div class="dv-row">
|
|
597
|
+
<button type="button" class="dv-button" @click="updateBatch">批量改这一类全部</button>
|
|
598
|
+
<button type="button" class="dv-button dv-button--danger" @click="removeBatch">批量删这一类全部</button>
|
|
599
|
+
</div>
|
|
600
|
+
<p class="dv-readout">{{ labelStatesText }}</p>
|
|
601
|
+
<p class="dv-hint">
|
|
602
|
+
批量动的是 labelId「{{ batchLabelId }}」:三个「加一条」按钮每点一次都是一批新标签
|
|
603
|
+
(id 按序号写 备注1 / 备注2…),内容与 offset 也跟着序号走 —— 加出来的标签默认要和已经放下的
|
|
604
|
+
标签共用避让网格,位置压在一起就整条不画(看上去像「只加上了一次」),这里给每条错开位置并关掉避让。
|
|
605
|
+
</p>
|
|
606
|
+
</div>
|
|
607
|
+
|
|
608
|
+
<div class="dv-group">
|
|
609
|
+
<h3>事件日志</h3>
|
|
610
|
+
<ul class="dv-log">
|
|
611
|
+
<li v-for="line in logLines" :key="line.id"><strong>{{ line.name }}</strong> · {{ line.text }}</li>
|
|
612
|
+
<li v-if="!logLines.length">还没有事件:点一下标签 / 拖一段 / 右键试试</li>
|
|
613
|
+
</ul>
|
|
614
|
+
</div>
|
|
615
|
+
</aside>
|
|
616
|
+
|
|
617
|
+
<div v-if="menu.open" class="dv-menu" :style="{ left: `${menu.x}px`, top: `${menu.y}px` }">
|
|
618
|
+
<p class="dv-menu__title">{{ hit ? describeLabel(hit) : '' }}</p>
|
|
619
|
+
<button type="button" @click="applyEdit">改内容</button>
|
|
620
|
+
<button type="button" @click="cyclePosition">换位置</button>
|
|
621
|
+
<button type="button" @click="resetOffset">重置偏移</button>
|
|
622
|
+
<button type="button" @click="toggleHidden">{{ form.hidden ? '显示这一条' : '隐藏这一条' }}</button>
|
|
623
|
+
<button type="button" @click="removeOne">删除这一条</button>
|
|
624
|
+
</div>
|
|
625
|
+
</section>
|
|
626
|
+
</template>
|
|
627
|
+
|
|
628
|
+
<style scoped>
|
|
629
|
+
.dv-input {
|
|
630
|
+
width: 132px;
|
|
631
|
+
padding: 4px 6px;
|
|
632
|
+
border: 1px solid #d6e0ec;
|
|
633
|
+
border-radius: 6px;
|
|
634
|
+
color: #1f2a44;
|
|
635
|
+
font-size: 12px;
|
|
636
|
+
}
|
|
637
|
+
</style>
|