gis-view-webgl2 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -8
- package/dist/gis-view.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1036 -249
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +194 -0
- package/docs/GisView.md +9 -8
- package/docs/GisView/345/256/236/347/216/260/345/216/237/347/220/206.md +2 -2
- package/docs/ai/06-/344/274/232/350/257/235/347/264/242/345/274/225/344/270/216/345/244/215/346/237/245/346/226/271/346/263/225.md +172 -0
- package/docs/api/index.html +545 -59
- package/docs/demos/BackendSyncDemo.vue +10 -8
- package/docs/demos/BasicDemo.vue +101 -101
- package/docs/demos/BatchAddDemo.vue +5 -5
- package/docs/demos/ChannelDemo.vue +617 -10
- package/docs/demos/ConnectDemo.vue +392 -0
- package/docs/demos/CopyMergeDemo.vue +980 -929
- package/docs/demos/DecorationDraftDemo.vue +1300 -0
- package/docs/demos/DemoShell.vue +569 -240
- package/docs/demos/DragDropDemo.vue +11 -11
- package/docs/demos/EffectsDemo.vue +6 -6
- package/docs/demos/EventDemo.vue +327 -327
- package/docs/demos/ExtensionDemo.vue +458 -0
- package/docs/demos/GisViewDemo.vue +61 -48
- package/docs/demos/GraphicDemo.vue +338 -0
- package/docs/demos/LabelDemo.vue +553 -497
- package/docs/demos/MinimapDemo.vue +358 -0
- package/docs/demos/PerfDemo.vue +29 -0
- package/docs/demos/SearchFilterDemo.vue +412 -0
- package/docs/demos/StyleDemo.vue +121 -121
- package/docs/demos/api-index.js +98 -77
- package/docs/demos/demos.js +159 -135
- package/docs/demos/shared/demoData.js +107 -107
- package/docs/demos/shared/demoStyle.css +249 -249
- package/docs/demos/shared/fakeBackend.js +93 -6
- 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 +28 -8
- package/docs/format/04-/351/200/232/351/201/223/344/270/216/346/240/207/347/255/276.md +5 -4
- package/docs/format/06-/345/257/274/345/205/245/345/257/274/345/207/272/344/270/216/346/240/207/345/207/206/346/226/207/346/241/243.md +8 -6
- package/docs/format/07-/344/272/213/344/273/266/350/275/275/350/215/267/344/270/216/347/244/272/344/276/213/346/225/260/346/215/256.md +6 -6
- package/docs/guide/sample-document.html +4 -4
- package/docs/index.html +180 -9
- package/docs/internals/11-/350/246/206/347/233/226/345/261/202/346/240/207/347/255/276/344/270/216/346/217/220/347/244/272.md +1 -1
- package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +3 -2
- package/docs/internals/15-03-/351/200/220/350/275/256/350/256/260/345/275/225-/346/200/247/350/203/275/344/270/216/344/277/256/345/244/215-/344/270/213.md +16 -0
- package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +11 -0
- package/docs/internals/17-/350/260/203/350/257/225/344/270/216/346/211/251/345/261/225/345/273/272/350/256/256.md +1 -1
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +18 -4
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +110 -25
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +13 -8
- package/docs/manual/06-/346/225/260/346/215/256/344/270/216/345/234/272/346/231/257/346/233/264/346/226/260.md +111 -10
- package/docs/manual/07-/346/214/207/351/222/210/344/270/216/350/246/201/347/264/240/347/274/226/350/276/221.md +47 -9
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +17 -15
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +61 -11
- 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 +74 -7
- package/docs/manual/11-/346/240/267/345/274/217/344/270/216/350/247/204/345/210/231.md +3 -2
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +260 -3
- package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +6 -5
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +27 -14
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +142 -49
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +31 -29
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +55 -39
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +189 -64
- package/docs/manual/19-/350/257/273/346/225/260/344/270/216/346/213/226/346/213/275/351/242/204/350/247/210.md +4 -4
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +42 -2
- 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 +22 -6
- package/docs/playground/index.html +5 -5
- package/docs/steps/README.md +14 -10
- package/docs/steps/check-doc-truth.mjs +1 -1
- package/docs/steps/selftest-actions.html +10 -10
- package/docs/steps/selftest-data.html +96 -14
- package/docs/steps/selftest-decoration.html +577 -0
- package/docs/steps/selftest-demo.html +6 -5
- package/docs/steps/selftest-demos.html +2201 -1612
- package/docs/steps/selftest-docs.html +89 -16
- package/docs/steps/selftest-effects.html +5 -5
- package/docs/steps/selftest-features.html +757 -86
- package/docs/steps/selftest-full.html +583 -583
- package/docs/steps/selftest-labels.html +1034 -652
- package/docs/steps/selftest-perf.html +1899 -1844
- package/docs/steps/selftest-scale.html +117 -7
- package/docs/steps/selftest-select.html +24 -17
- package/docs/steps/step-10-full.js +1 -1
- package/docs//346/216/245/345/217/243/345/215/217/350/256/256.md +167 -17
- package/docs//346/225/260/346/215/256/346/240/274/345/274/217.md +1 -1
- package/package.json +43 -43
- package/docs/demos/HistoryDemo.vue +0 -230
- package/docs/demos/InterfaceDemo.vue +0 -309
- package/docs/demos/SampleDemo.vue +0 -283
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
* 确认之后才动数据(组件不弹界面,框是这一页自己按载荷画的)。
|
|
15
15
|
* - 导出:`exportData({ format: 'input' })` 拿回与输入同构的一份数据(含通道与拐点),
|
|
16
16
|
* 点「按导出重建」就把它喂回 `updateData` + `updateOptions` —— 导出再导入即回显;
|
|
17
|
-
* 旁边一并给一份 `format: 'view'` 的业务口径(line_data / parallel_groups
|
|
17
|
+
* 旁边一并给一份 `format: 'view'` 的业务口径(line_data / parallel_groups 那套列名);
|
|
18
|
+
* 「后端 DTO」按钮演示 `defineExportFormat` 带作用域注册的项目自定义格式。
|
|
18
19
|
*/
|
|
19
20
|
import { computed, onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
|
|
20
|
-
import { createGisView } from '../../src/index.js'
|
|
21
|
+
import { createGisView, defineExportFormat, listExportFormats } from '../../src/index.js'
|
|
21
22
|
import { buildDemoData } from './shared/demoData.js'
|
|
22
23
|
import { createFakeBackend, cloneFakeData, FAKE_BACKEND_MODES } from './shared/fakeBackend.js'
|
|
23
24
|
import './shared/demoStyle.css'
|
|
@@ -32,11 +33,16 @@ const channelInset = ref(0.3)
|
|
|
32
33
|
const offsetMode = ref('tangent')
|
|
33
34
|
/** 通道选中模式:'line' 只选点中的回线 / 'group' 连所在分组 / 'channel' 连整条通道 */
|
|
34
35
|
const channelSelectMode = ref('line')
|
|
36
|
+
/** 分组拐点拖动模式:'parallel' 共用骨架 / 'vertex' 只移同组同序号拐点 */
|
|
37
|
+
const channelWaypointDragMode = ref('parallel')
|
|
38
|
+
const waypointDragText = ref('分组拐点拖动:先点「准备两分组拐点」,再拖中间拐点比较两种模式')
|
|
35
39
|
/** 待确认的删除请求('remove' 事件的 pending 载荷):确认框照着它渲染 */
|
|
36
40
|
const removeRequest = ref(null)
|
|
37
41
|
const removeText = ref('删除:按 Delete 或点上面的按钮,先出载荷、确认之后才动数据')
|
|
38
42
|
const channelsText = ref('—')
|
|
39
43
|
const groupsText = ref('—')
|
|
44
|
+
const visibilityText = ref('隐藏 / 显示:—')
|
|
45
|
+
const changedText = ref('最近变化:—')
|
|
40
46
|
const exportText = ref('')
|
|
41
47
|
const logLines = ref([])
|
|
42
48
|
const backendMode = ref('success')
|
|
@@ -49,6 +55,35 @@ const backendLastSuccess = shallowRef(null)
|
|
|
49
55
|
const backendLogLines = ref([])
|
|
50
56
|
const waypointSyncText = ref('拐点 / 分组同步:—')
|
|
51
57
|
const fakeBackend = createFakeBackend({ initialVersion: 8, latency: 420, prefix: 'channel' })
|
|
58
|
+
const DTO_FORMAT = 'channel-backend-dto'
|
|
59
|
+
let disposeDtoFormat = null
|
|
60
|
+
/** 自定义导出格式:只把标准文档映射成这一页后端要的列名,不碰组件内部数组 */
|
|
61
|
+
const backendDtoAdapter = ({ document: source, counts }) => ({
|
|
62
|
+
schema: 'gis-view.channel-demo.v1',
|
|
63
|
+
counts,
|
|
64
|
+
camera: source.view?.camera ?? null,
|
|
65
|
+
stations: source.points.map(point => ({
|
|
66
|
+
station_id: point.id,
|
|
67
|
+
station_name: point.name ?? '',
|
|
68
|
+
x: point.x,
|
|
69
|
+
y: point.y
|
|
70
|
+
})),
|
|
71
|
+
routes: source.lines.map(line => ({
|
|
72
|
+
route_id: line.id,
|
|
73
|
+
from: line.from ?? null,
|
|
74
|
+
to: line.to ?? null,
|
|
75
|
+
coords: line.path ?? []
|
|
76
|
+
})),
|
|
77
|
+
channels: source.channels.map(channel => ({
|
|
78
|
+
channel_id: channel.id,
|
|
79
|
+
channel_name: channel.name ?? '',
|
|
80
|
+
groups: (channel.groups ?? []).map(group => ({
|
|
81
|
+
group_id: group.id,
|
|
82
|
+
line_ids: group.lineIds,
|
|
83
|
+
waypoints: group.path
|
|
84
|
+
}))
|
|
85
|
+
}))
|
|
86
|
+
})
|
|
52
87
|
let logSeq = 0
|
|
53
88
|
let backendLogSeq = 0
|
|
54
89
|
let backendJobSeq = 0
|
|
@@ -193,6 +228,7 @@ const handleWaypointBackend = (payload) => {
|
|
|
193
228
|
const groupId = payload.groupId ?? payload.group_id
|
|
194
229
|
const group = view.getChannelGroupWaypoints(payload.channelId, groupId)
|
|
195
230
|
const path = pathJson(group?.path)
|
|
231
|
+
const waypointOffsets = { ...(group?.waypointOffsets ?? {}) }
|
|
196
232
|
if (!group) {
|
|
197
233
|
waypointSyncText.value = `${phaseLabel}:分组 ${String(groupId ?? '—')} 的整份骨架读不出来,请求没有发出`
|
|
198
234
|
return
|
|
@@ -206,6 +242,7 @@ const handleWaypointBackend = (payload) => {
|
|
|
206
242
|
channelId: payload.channelId,
|
|
207
243
|
groupId,
|
|
208
244
|
path,
|
|
245
|
+
waypointOffsets,
|
|
209
246
|
phase: payload.phase,
|
|
210
247
|
scope: group.scope ?? 'group'
|
|
211
248
|
}
|
|
@@ -269,6 +306,8 @@ const buildOptions = () => ({
|
|
|
269
306
|
features: { pan: true, zoom: true, hover: true, select: true, contextMenu: true, keyboard: true, drag: true, channelMenu: true, remove: true },
|
|
270
307
|
// 通道选中模式:点一条回线选它自己 / 连所在分组 / 连整条通道
|
|
271
308
|
channelSelectMode: channelSelectMode.value,
|
|
309
|
+
// 分组拐点拖动:共用骨架 / 只移同组同序号拐点
|
|
310
|
+
channelWaypointDragMode: channelWaypointDragMode.value,
|
|
272
311
|
// 删除配置:confirm 缺省(null)表示组件不弹界面、停在待确认 —— 这一页订阅 'remove' 事件自己画确认框
|
|
273
312
|
remove: { scope: 'lines' },
|
|
274
313
|
// 交互框里的「加拐点 / 删拐点」动的是分组骨架,要有这一项才不是灰的(Ctrl + 点击加点、双击删点也一起开了)
|
|
@@ -288,8 +327,12 @@ const buildOptions = () => ({
|
|
|
288
327
|
const channel = view?.getLineChannel(entry.index)
|
|
289
328
|
groupsText.value = channel
|
|
290
329
|
? `线路 ${entry.item?.id ?? entry.index} → ${channel.channelId} / 分组 ${channel.groupId}(共 ${channel.group?.lineIds?.length ?? 0} 回)`
|
|
291
|
-
|
|
330
|
+
: `线路 ${entry.item?.id ?? entry.index} 不在通道里`
|
|
292
331
|
}
|
|
332
|
+
},
|
|
333
|
+
onDataChange: () => {
|
|
334
|
+
syncReadouts()
|
|
335
|
+
syncChangedChannels()
|
|
293
336
|
}
|
|
294
337
|
})
|
|
295
338
|
|
|
@@ -316,6 +359,37 @@ const syncReadouts = () => {
|
|
|
316
359
|
groupsText.value = firstChannelId === undefined ? '—' : describeGroups(firstChannelId)
|
|
317
360
|
// 面板上改过分组之后,开着的右键框也要跟着对上(对不上就收掉)
|
|
318
361
|
syncMenu()
|
|
362
|
+
syncChangedChannels()
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** 最近变化通道:分组 / 成员 / 拐点 / 端点 / 可见性 / 其余声明字段 */
|
|
366
|
+
const syncChangedChannels = () => {
|
|
367
|
+
const labels = { groups: '分组', members: '成员', waypoints: '拐点', endpoints: '端点', visibility: '可见性', fields: '字段' }
|
|
368
|
+
const changed = view?.getChangedChannels() ?? []
|
|
369
|
+
changedText.value = changed.length
|
|
370
|
+
? `最近变化 ${changed.length} 条:${changed.slice(0, 4).map(item => `${String(item.id)}(${item.reasons.map(reason => labels[reason] ?? reason).join(' / ')})`).join(' · ')}`
|
|
371
|
+
: '最近变化:没有(改分组 / 拖拐点 / 隐藏后再看)'
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const setFirstFeatureVisible = (kind, visible) => {
|
|
375
|
+
const options = view?.getOptions()
|
|
376
|
+
const item = kind === 'point' ? options?.points?.[0] : kind === 'line' ? options?.lines?.[0] : options?.channels?.[0]
|
|
377
|
+
const ref = item?.id ?? (item ? 0 : null)
|
|
378
|
+
const kindLabel = kind === 'point' ? '节点' : kind === 'line' ? '线路' : '通道'
|
|
379
|
+
if (!view || ref === null) return
|
|
380
|
+
const outcome = visible ? view.showFeatures(kind, ref) : view.hideFeatures(kind, ref)
|
|
381
|
+
visibilityText.value = outcome.ok
|
|
382
|
+
? `${visible ? '显示' : '隐藏'}${kindLabel} ${String(ref)}:${outcome.indexes.length} 条${outcome.skipped.length ? ` · 跳过 ${outcome.skipped.length}` : ''}`
|
|
383
|
+
: `${visible ? '显示' : '隐藏'}${kindLabel}失败:${outcome.reason}`
|
|
384
|
+
pushLog(visible ? 'showFeatures' : 'hideFeatures', visibilityText.value)
|
|
385
|
+
syncReadouts()
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const clearChannelChanges = () => {
|
|
389
|
+
view?.clearChangedChannels()
|
|
390
|
+
syncChangedChannels()
|
|
391
|
+
visibilityText.value = '通道对账:已把当前声明记成新基准'
|
|
392
|
+
pushLog('clearChangedChannels', visibilityText.value)
|
|
319
393
|
}
|
|
320
394
|
|
|
321
395
|
const describeGroups = (channelId) => {
|
|
@@ -430,6 +504,127 @@ const splitPickedIntoGroup = () => {
|
|
|
430
504
|
syncReadouts()
|
|
431
505
|
}
|
|
432
506
|
|
|
507
|
+
/**
|
|
508
|
+
* 线路分组弹框:参考业务页的「分组卡片 + 行内移入目标」。
|
|
509
|
+
* 行内「确认移入」只改本地草稿,底部「确定分组」才调用一次 splitChannelGroups:
|
|
510
|
+
* 一次把整份 groups 声明换掉,避免每挪一条回线都发一次请求 / 记一步历史。
|
|
511
|
+
*/
|
|
512
|
+
const groupDialog = ref(null)
|
|
513
|
+
const groupDialogTargets = ref({})
|
|
514
|
+
const groupDialogText = ref('线路分组:改完所有移动后,点「确定分组」一次提交')
|
|
515
|
+
const sameGroupId = (left, right) => String(left) === String(right)
|
|
516
|
+
|
|
517
|
+
const dialogLineRowOf = (lineId) => {
|
|
518
|
+
const line = view?.getLine(lineId)
|
|
519
|
+
const from = line?.from === undefined || line?.from === null ? null : view?.getPoint(line.from)
|
|
520
|
+
const to = line?.to === undefined || line?.to === null ? null : view?.getPoint(line.to)
|
|
521
|
+
const endpointName = from?.name && to?.name ? `${from.name}_${to.name}` : String(lineId)
|
|
522
|
+
return {
|
|
523
|
+
id: lineId,
|
|
524
|
+
label: line?.name || endpointName,
|
|
525
|
+
meta: line ? [line.from, line.to].filter(id => id !== undefined && id !== null).map(String).join(' → ') : String(lineId)
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** 弹框副标题沿用截图里的「场站对标标识」:优先用通道两端,读不到再退回第一条回线两端 */
|
|
530
|
+
const dialogStationKeyOf = (channelId) => {
|
|
531
|
+
const declared = view?.getOptions().channels?.find(channel => sameGroupId(channel.id, channelId))
|
|
532
|
+
const ends = [declared?.from, declared?.to].filter(id => id !== undefined && id !== null)
|
|
533
|
+
if (ends.length) {
|
|
534
|
+
return ends.map(String).join('|')
|
|
535
|
+
}
|
|
536
|
+
const firstLineId = view?.getChannelGroups(channelId)?.flatMap(group => group.lineIds ?? [])[0]
|
|
537
|
+
const line = firstLineId === undefined ? null : view?.getLine(firstLineId)
|
|
538
|
+
const lineEnds = [line?.from, line?.to].filter(id => id !== undefined && id !== null)
|
|
539
|
+
return lineEnds.length ? lineEnds.map(String).join('|') : String(channelId)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const groupDialogGroups = computed(() => (groupDialog.value?.groups ?? []).map((group, index) => ({
|
|
543
|
+
...group,
|
|
544
|
+
index,
|
|
545
|
+
rows: group.lineIds.map(dialogLineRowOf)
|
|
546
|
+
})))
|
|
547
|
+
|
|
548
|
+
const groupDialogTargetsFor = sourceIndex => groupDialogGroups.value.filter(group => group.index !== sourceIndex)
|
|
549
|
+
|
|
550
|
+
const openGroupDialog = (channelId, hitLineId) => {
|
|
551
|
+
const wanted = channelId ?? channelMenu.value?.channelId ?? firstChannelGroups()?.channelId
|
|
552
|
+
if (!view || wanted === undefined || wanted === null) {
|
|
553
|
+
pushLog('线路分组:当前没有可编辑的通道')
|
|
554
|
+
return
|
|
555
|
+
}
|
|
556
|
+
const groups = view.getChannelGroups(wanted)
|
|
557
|
+
if (!groups.length) {
|
|
558
|
+
pushLog('线路分组:通道 ' + wanted + ' 暂时读不到分组')
|
|
559
|
+
return
|
|
560
|
+
}
|
|
561
|
+
groupDialog.value = {
|
|
562
|
+
channelId: wanted,
|
|
563
|
+
stationKey: dialogStationKeyOf(wanted),
|
|
564
|
+
hitLineId: hitLineId ?? null,
|
|
565
|
+
groups: groups.map(group => ({ id: group.id, lineIds: [...(group.lineIds ?? [])] })),
|
|
566
|
+
moves: 0
|
|
567
|
+
}
|
|
568
|
+
groupDialogTargets.value = {}
|
|
569
|
+
groupDialogText.value = String(wanted) + ':' + groups.length + ' 组 / ' + groups.reduce((sum, group) => sum + group.lineIds.length, 0) + ' 回线。可以连续移动,确定时一次提交。'
|
|
570
|
+
if (channelMenu.value) {
|
|
571
|
+
closeChannelMenu()
|
|
572
|
+
}
|
|
573
|
+
pushLog('线路分组:打开 ' + wanted + ' 的整组编辑弹框')
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const moveGroupDialogLine = (lineId) => {
|
|
577
|
+
const draft = groupDialog.value
|
|
578
|
+
if (!draft) {
|
|
579
|
+
return
|
|
580
|
+
}
|
|
581
|
+
const rawTarget = groupDialogTargets.value[String(lineId)]
|
|
582
|
+
const sourceIndex = draft.groups.findIndex(group => group.lineIds.some(id => sameGroupId(id, lineId)))
|
|
583
|
+
const targetIndex = draft.groups.findIndex(group => sameGroupId(group.id, rawTarget))
|
|
584
|
+
if (sourceIndex < 0) {
|
|
585
|
+
groupDialogText.value = '这条回线已经不在草稿里,重新打开弹框再试'
|
|
586
|
+
return
|
|
587
|
+
}
|
|
588
|
+
if (targetIndex < 0) {
|
|
589
|
+
groupDialogText.value = '先给这条回线选一个目标分组'
|
|
590
|
+
return
|
|
591
|
+
}
|
|
592
|
+
const sourceLabel = '分组 ' + (sourceIndex + 1)
|
|
593
|
+
const targetLabel = '分组 ' + (targetIndex + 1)
|
|
594
|
+
const nextGroups = draft.groups.map(group => ({ id: group.id, lineIds: [...group.lineIds] }))
|
|
595
|
+
nextGroups[sourceIndex].lineIds = nextGroups[sourceIndex].lineIds.filter(id => !sameGroupId(id, lineId))
|
|
596
|
+
nextGroups[targetIndex].lineIds.push(lineId)
|
|
597
|
+
draft.groups = nextGroups.filter(group => group.lineIds.length)
|
|
598
|
+
draft.moves += 1
|
|
599
|
+
delete groupDialogTargets.value[String(lineId)]
|
|
600
|
+
groupDialogText.value = '草稿第 ' + draft.moves + ' 处:' + dialogLineRowOf(lineId).label + ' ' + sourceLabel + ' → ' + targetLabel + '。数据还没动,点「确定分组」一次提交。'
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const closeGroupDialog = () => {
|
|
604
|
+
groupDialog.value = null
|
|
605
|
+
groupDialogTargets.value = {}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const confirmGroupDialog = () => {
|
|
609
|
+
const draft = groupDialog.value
|
|
610
|
+
if (!view || !draft) {
|
|
611
|
+
return
|
|
612
|
+
}
|
|
613
|
+
if (!draft.moves) {
|
|
614
|
+
groupDialogText.value = '还没有移动任何回线;可以继续编辑,或点「取消」退出'
|
|
615
|
+
return
|
|
616
|
+
}
|
|
617
|
+
const groups = draft.groups
|
|
618
|
+
.filter(group => group.lineIds.length)
|
|
619
|
+
.map(group => ({ id: group.id, lineIds: [...group.lineIds] }))
|
|
620
|
+
view.splitChannelGroups(draft.channelId, groups)
|
|
621
|
+
const lineCount = groups.reduce((sum, group) => sum + group.lineIds.length, 0)
|
|
622
|
+
pushLog('splitChannelGroups:' + draft.channelId + ' 一次提交 ' + draft.moves + ' 处移动(' + groups.length + ' 组 / ' + lineCount + ' 回线)')
|
|
623
|
+
groupDialogText.value = '已一次提交:' + groups.length + ' 组 / ' + lineCount + ' 回线'
|
|
624
|
+
closeGroupDialog()
|
|
625
|
+
syncReadouts()
|
|
626
|
+
}
|
|
627
|
+
|
|
433
628
|
/**
|
|
434
629
|
* 右键交互框:右键一条通道回线时 onChannelContextMenu(payload) 把「这条通道几组 / 当前是哪一组 /
|
|
435
630
|
* 这一组能做哪些交互」一次交出来,界面照着 groups 与 actions 渲染就行 —— 核心是四件事:
|
|
@@ -563,6 +758,78 @@ const applyChannelSelectMode = () => {
|
|
|
563
758
|
pushLog('通道选中模式 → ' + channelSelectMode.value + '(' + labels[channelSelectMode.value] + ')')
|
|
564
759
|
}
|
|
565
760
|
|
|
761
|
+
/** 分组拐点拖动模式:只影响下一次拖动,updateOptions 不重排场景 */
|
|
762
|
+
const applyChannelWaypointDragMode = () => {
|
|
763
|
+
view?.updateOptions({ channelWaypointDragMode: channelWaypointDragMode.value })
|
|
764
|
+
const labels = {
|
|
765
|
+
parallel: '共用骨架 + 平行偏移(和现有逻辑一样)',
|
|
766
|
+
vertex: '只移动同组同序号拐点,其他拐点不动'
|
|
767
|
+
}
|
|
768
|
+
waypointDragText.value = '分组拐点拖动:' + labels[channelWaypointDragMode.value]
|
|
769
|
+
pushLog('分组拐点拖动 → ' + channelWaypointDragMode.value + ':' + labels[channelWaypointDragMode.value])
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* 把第一条通道拆成两组,并给每组加两个错开的拐点:
|
|
774
|
+
* 拖动中间拐点时,parallel 模式会重算整条平行骨架;vertex 模式只平移四根线上同一个序号的顶点。
|
|
775
|
+
*/
|
|
776
|
+
const prepareWaypointDragDemo = () => {
|
|
777
|
+
if (!view) {
|
|
778
|
+
return
|
|
779
|
+
}
|
|
780
|
+
const channel = view.getChannels()[0]
|
|
781
|
+
if (!channel) {
|
|
782
|
+
return
|
|
783
|
+
}
|
|
784
|
+
const channelId = channel.channelId
|
|
785
|
+
let groups = view.getChannelGroups(channelId)
|
|
786
|
+
const lineIds = groups.flatMap(group => group.lineIds)
|
|
787
|
+
if (lineIds.length < 2) {
|
|
788
|
+
waypointDragText.value = '这条通道只有一回线,拆不出两组可比较的分组拐点'
|
|
789
|
+
return
|
|
790
|
+
}
|
|
791
|
+
const splitAt = Math.ceil(lineIds.length / 2)
|
|
792
|
+
view.splitChannelGroups(channelId, [lineIds.slice(0, splitAt), lineIds.slice(splitAt)])
|
|
793
|
+
groups = view.getChannelGroups(channelId)
|
|
794
|
+
// 先把旧拐点清干净,重复点按钮也能得到同一份演示数据
|
|
795
|
+
for (const group of groups) {
|
|
796
|
+
let live = view.getChannelGroups(channelId).find(item => String(item.id) === String(group.id))
|
|
797
|
+
let guard = 0
|
|
798
|
+
while (live && live.skeleton.length > 2 && guard < 64) {
|
|
799
|
+
view.removeChannelGroupWaypoint(channelId, group.id, live.skeleton.length - 2)
|
|
800
|
+
live = view.getChannelGroups(channelId).find(item => String(item.id) === String(group.id))
|
|
801
|
+
guard += 1
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
groups = view.getChannelGroups(channelId)
|
|
805
|
+
groups.forEach((group, groupIndex) => {
|
|
806
|
+
const start = group.skeleton[0]
|
|
807
|
+
const end = group.skeleton[group.skeleton.length - 1]
|
|
808
|
+
if (!start || !end) {
|
|
809
|
+
return
|
|
810
|
+
}
|
|
811
|
+
const dx = end[0] - start[0]
|
|
812
|
+
const dy = end[1] - start[1]
|
|
813
|
+
const length = Math.hypot(dx, dy)
|
|
814
|
+
if (length < 1e-9) {
|
|
815
|
+
return
|
|
816
|
+
}
|
|
817
|
+
const normal = [-dy / length, dx / length]
|
|
818
|
+
const side = groupIndex === 0 ? 1 : -1
|
|
819
|
+
const offset = Math.min(Math.max(length * 0.06, 0.05), 0.16) * side
|
|
820
|
+
for (const ratio of [0.33, 0.66]) {
|
|
821
|
+
view.addChannelGroupWaypoint(channelId, group.id, [
|
|
822
|
+
start[0] + dx * ratio + normal[0] * offset,
|
|
823
|
+
start[1] + dy * ratio + normal[1] * offset
|
|
824
|
+
])
|
|
825
|
+
}
|
|
826
|
+
})
|
|
827
|
+
view.updateOptions({ channelSelectMode: channelSelectMode.value, channelWaypointDragMode: channelWaypointDragMode.value })
|
|
828
|
+
syncReadouts()
|
|
829
|
+
waypointDragText.value = `已准备 ${groups.length} 组 / 每组 2 个拐点:拖动中间拐点比较「共用骨架」和「只移同序号拐点」`
|
|
830
|
+
pushLog('分组拐点演示就绪:' + groups.map(group => group.id + '(' + group.lineIds.length + ' 回)').join(' / '))
|
|
831
|
+
}
|
|
832
|
+
|
|
566
833
|
/** 删除载荷的中文读数:要删什么、连带什么 */
|
|
567
834
|
const describeRemove = (plan) => {
|
|
568
835
|
const counts = plan.counts ?? {}
|
|
@@ -625,7 +892,14 @@ const cancelRemove = () => {
|
|
|
625
892
|
|
|
626
893
|
/** Esc 收框:框是演示自己画的,组件只负责把载荷交出来 */
|
|
627
894
|
const onWindowKeyDown = (event) => {
|
|
628
|
-
if (event.key
|
|
895
|
+
if (event.key !== 'Escape') {
|
|
896
|
+
return
|
|
897
|
+
}
|
|
898
|
+
if (groupDialog.value) {
|
|
899
|
+
closeGroupDialog()
|
|
900
|
+
return
|
|
901
|
+
}
|
|
902
|
+
if (channelMenu.value) {
|
|
629
903
|
closeChannelMenu()
|
|
630
904
|
}
|
|
631
905
|
}
|
|
@@ -661,6 +935,18 @@ const exportData = () => {
|
|
|
661
935
|
return exported
|
|
662
936
|
}
|
|
663
937
|
|
|
938
|
+
/** 自定义格式按钮:同一份标准文档,不必再手写深拷贝 / 坐标归一 / JSON 安全 */
|
|
939
|
+
const exportBackendDto = () => {
|
|
940
|
+
const dto = view?.exportData({ format: DTO_FORMAT })
|
|
941
|
+
if (!dto) {
|
|
942
|
+
return
|
|
943
|
+
}
|
|
944
|
+
exportText.value = `自定义格式 ${DTO_FORMAT}:${dto.counts.points} 站 / ${dto.counts.lines} 线路 / ${dto.counts.channels} 通道 · 通道分组 ${dto.counts.groups}`
|
|
945
|
+
+ ` | 当前可注册表:${listExportFormats().join(' / ')}`
|
|
946
|
+
pushLog(`自定义导出:${dto.schema},${dto.stations.length} 站 / ${dto.routes.length} 线 / ${dto.channels.length} 通道`)
|
|
947
|
+
return dto
|
|
948
|
+
}
|
|
949
|
+
|
|
664
950
|
/** 回灌:把 input 口径的那份直接喂回视图(点 / 线走 updateData,通道走 updateOptions) */
|
|
665
951
|
const rebuildFromExport = () => {
|
|
666
952
|
const exported = exportData()
|
|
@@ -675,6 +961,7 @@ const rebuildFromExport = () => {
|
|
|
675
961
|
}
|
|
676
962
|
|
|
677
963
|
onMounted(() => {
|
|
964
|
+
disposeDtoFormat = defineExportFormat(DTO_FORMAT, backendDtoAdapter)
|
|
678
965
|
view = createGisView(container.value, buildOptions())
|
|
679
966
|
// 删除请求走 'remove' 事件(与 onRemove 同一份载荷):pending / remove / cancel 三个阶段都在这一条上
|
|
680
967
|
view.on('remove', handleRemoveRequest)
|
|
@@ -687,6 +974,8 @@ onMounted(() => {
|
|
|
687
974
|
|
|
688
975
|
onBeforeUnmount(() => {
|
|
689
976
|
disposed = true
|
|
977
|
+
disposeDtoFormat?.()
|
|
978
|
+
disposeDtoFormat = null
|
|
690
979
|
window.removeEventListener('keydown', onWindowKeyDown)
|
|
691
980
|
view?.destroy()
|
|
692
981
|
view = null
|
|
@@ -715,6 +1004,9 @@ onBeforeUnmount(() => {
|
|
|
715
1004
|
<span>{{ group.lineCount }} 回{{ group.waypointCount ? ' / ' + group.waypointCount + ' 拐点' : '' }}</span>
|
|
716
1005
|
</li>
|
|
717
1006
|
</ul>
|
|
1007
|
+
<button type="button" class="dv-channel-menu__open-dialog" data-demo="channel-group-dialog-open-menu" @click="openGroupDialog(channelMenu.channelId, channelMenu.lineId)">
|
|
1008
|
+
线路分组…
|
|
1009
|
+
</button>
|
|
718
1010
|
<div v-if="menuLineRows.length" class="dv-channel-menu__lines">
|
|
719
1011
|
<p class="dv-channel-menu__subtitle">线路分配 · 每条回线都能拆成新分组或并进别的分组</p>
|
|
720
1012
|
<ul>
|
|
@@ -743,6 +1035,65 @@ onBeforeUnmount(() => {
|
|
|
743
1035
|
命中回线 {{ channelMenu.lineId }} · 这些都在 onChannelContextMenu(payload) 里;点空白处 / Esc / × 收框
|
|
744
1036
|
</p>
|
|
745
1037
|
</div>
|
|
1038
|
+
<div v-if="groupDialog" class="dv-group-dialog" data-demo="channel-group-dialog" @click.self="closeGroupDialog">
|
|
1039
|
+
<section class="dv-group-dialog__panel" role="dialog" aria-modal="true" aria-labelledby="dv-channel-group-title">
|
|
1040
|
+
<header class="dv-group-dialog__head">
|
|
1041
|
+
<div>
|
|
1042
|
+
<h3 id="dv-channel-group-title">线路分组</h3>
|
|
1043
|
+
<p>场站对标标识:{{ groupDialog.stationKey }}</p>
|
|
1044
|
+
</div>
|
|
1045
|
+
<button type="button" title="取消并关闭(Esc)" @click="closeGroupDialog">×</button>
|
|
1046
|
+
</header>
|
|
1047
|
+
<div class="dv-group-dialog__body">
|
|
1048
|
+
<section
|
|
1049
|
+
v-for="group in groupDialogGroups"
|
|
1050
|
+
:key="String(group.id)"
|
|
1051
|
+
class="dv-group-dialog__card"
|
|
1052
|
+
data-demo="channel-group-dialog-card"
|
|
1053
|
+
:data-group-id="String(group.id)"
|
|
1054
|
+
>
|
|
1055
|
+
<header>
|
|
1056
|
+
<strong>分组 {{ group.index + 1 }}</strong>
|
|
1057
|
+
<span>{{ group.lineIds.length }} 条线路</span>
|
|
1058
|
+
<code>{{ group.id }}</code>
|
|
1059
|
+
</header>
|
|
1060
|
+
<ul>
|
|
1061
|
+
<li
|
|
1062
|
+
v-for="row in group.rows"
|
|
1063
|
+
:key="String(row.id)"
|
|
1064
|
+
class="dv-group-dialog__line"
|
|
1065
|
+
data-demo="channel-group-dialog-line"
|
|
1066
|
+
:data-line-id="String(row.id)"
|
|
1067
|
+
:data-group-id="String(group.id)"
|
|
1068
|
+
>
|
|
1069
|
+
<div class="dv-group-dialog__line-text">
|
|
1070
|
+
<strong>{{ row.label }}</strong>
|
|
1071
|
+
<span>{{ row.meta }}</span>
|
|
1072
|
+
</div>
|
|
1073
|
+
<select v-model="groupDialogTargets[String(row.id)]" data-demo="channel-group-dialog-target">
|
|
1074
|
+
<option value="">移入其他组</option>
|
|
1075
|
+
<option v-for="target in groupDialogTargetsFor(group.index)" :key="String(target.id)" :value="String(target.id)">
|
|
1076
|
+
分组 {{ target.index + 1 }}({{ target.lineIds.length }} 条)
|
|
1077
|
+
</option>
|
|
1078
|
+
</select>
|
|
1079
|
+
<button type="button" :disabled="!groupDialogTargets[String(row.id)]" data-demo="channel-group-dialog-move" @click="moveGroupDialogLine(row.id)">
|
|
1080
|
+
确认移入
|
|
1081
|
+
</button>
|
|
1082
|
+
</li>
|
|
1083
|
+
</ul>
|
|
1084
|
+
</section>
|
|
1085
|
+
</div>
|
|
1086
|
+
<footer class="dv-group-dialog__footer">
|
|
1087
|
+
<p data-demo="channel-group-dialog-text">{{ groupDialogText }}</p>
|
|
1088
|
+
<div>
|
|
1089
|
+
<button type="button" data-demo="channel-group-dialog-cancel" @click="closeGroupDialog">取消</button>
|
|
1090
|
+
<button type="button" class="dv-button--primary" data-demo="channel-group-dialog-confirm" :disabled="!groupDialog.moves" @click="confirmGroupDialog">
|
|
1091
|
+
确定分组
|
|
1092
|
+
</button>
|
|
1093
|
+
</div>
|
|
1094
|
+
</footer>
|
|
1095
|
+
</section>
|
|
1096
|
+
</div>
|
|
746
1097
|
<div v-if="removeRequest" class="dv-remove-box">
|
|
747
1098
|
<div class="dv-channel-menu__head">
|
|
748
1099
|
<strong>确认删除</strong>
|
|
@@ -803,6 +1154,7 @@ onBeforeUnmount(() => {
|
|
|
803
1154
|
<div class="dv-row">
|
|
804
1155
|
<button type="button" class="dv-button" @click="splitFirstChannel">第一条通道:每回线拆一组</button>
|
|
805
1156
|
<button type="button" class="dv-button" @click="mergeFirstChannel">并回一组</button>
|
|
1157
|
+
<button type="button" class="dv-button" data-demo="channel-group-dialog-open" @click="openGroupDialog()">线路分组…</button>
|
|
806
1158
|
</div>
|
|
807
1159
|
<p class="dv-readout">{{ channelsText }}</p>
|
|
808
1160
|
<div class="dv-row dv-split" data-demo="add-channel-group-picks">
|
|
@@ -827,14 +1179,14 @@ onBeforeUnmount(() => {
|
|
|
827
1179
|
<p class="dv-hint">勾几条回线点「勾上的拆成一组」= addChannelGroup(channelId, lineIds, { id?, path? }):一次把这几条挪进单独一组(数据里没写 groups 的通道也是这么拆,被挪空的那一组整组消失);「新这一组的拐点」两项演示 options.path(照抄来源组当前的骨架 / 写 [] 直连),组 id 留空按 通道 id::N 排。一条一条挪走 moveChannelLine,整份重排走 splitChannelGroups。</p>
|
|
828
1180
|
<p class="dv-readout">{{ groupsText }}</p>
|
|
829
1181
|
<p class="dv-hint">选中一条通道回线也会刷新读数:onSelect 载荷里的 items 带上 index,再用 getLineChannel(index) 问出它属于哪条通道、哪一组。</p>
|
|
830
|
-
<p class="dv-hint">右键一条通道回线出交互框:点分组就是 setCurrentChannelGroup(顺带选中它的回线),每条回线可以拆成新分组 / 并进别的分组,下面一排按钮交给 runChannelAction
|
|
1182
|
+
<p class="dv-hint">右键一条通道回线出交互框:点分组就是 setCurrentChannelGroup(顺带选中它的回线),每条回线可以拆成新分组 / 并进别的分组,下面一排按钮交给 runChannelAction。「线路分组…」弹框把多次移入攒成一份草稿,确定时只调用一次 splitChannelGroups 换整份分组声明。</p>
|
|
831
1183
|
</div>
|
|
832
1184
|
|
|
833
1185
|
<div class="dv-group">
|
|
834
1186
|
<h3>通道选中模式<span class="dv-chip">{{ channelSelectMode }}</span></h3>
|
|
835
1187
|
<div class="dv-row">
|
|
836
1188
|
<label>点一条回线
|
|
837
|
-
<select v-model="channelSelectMode" @change="applyChannelSelectMode">
|
|
1189
|
+
<select v-model="channelSelectMode" data-demo="channel-select-mode" @change="applyChannelSelectMode">
|
|
838
1190
|
<option value="line">line(只选它自己)</option>
|
|
839
1191
|
<option value="group">group(连所在分组)</option>
|
|
840
1192
|
<option value="channel">channel(连整条通道)</option>
|
|
@@ -843,7 +1195,49 @@ onBeforeUnmount(() => {
|
|
|
843
1195
|
</div>
|
|
844
1196
|
<p class="dv-hint">
|
|
845
1197
|
channelSelectMode 只决定下一次点选怎么摊开:line 单条、group 连这一组、channel 整条通道;
|
|
846
|
-
在 group / channel 下 Ctrl +
|
|
1198
|
+
在 group / channel 下 Ctrl + 点击是整批取反,Ctrl + 点击线路加点也按这一档选中并高亮。
|
|
1199
|
+
切换走 updateOptions,场景不用重排。
|
|
1200
|
+
</p>
|
|
1201
|
+
</div>
|
|
1202
|
+
|
|
1203
|
+
<div class="dv-group">
|
|
1204
|
+
<h3>分组拐点拖动<span class="dv-chip">{{ channelWaypointDragMode }}</span></h3>
|
|
1205
|
+
<div class="dv-row">
|
|
1206
|
+
<label>拖动模式
|
|
1207
|
+
<select v-model="channelWaypointDragMode" data-demo="channel-waypoint-drag-mode" @change="applyChannelWaypointDragMode">
|
|
1208
|
+
<option value="parallel">parallel(共用骨架,和现有逻辑一样)</option>
|
|
1209
|
+
<option value="vertex">vertex(只移同组同序号拐点)</option>
|
|
1210
|
+
</select>
|
|
1211
|
+
</label>
|
|
1212
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="channel-waypoint-drag-prepare" @click="prepareWaypointDragDemo">准备两分组拐点</button>
|
|
1213
|
+
</div>
|
|
1214
|
+
<p class="dv-readout" data-demo="channel-waypoint-drag-text">{{ waypointDragText }}</p>
|
|
1215
|
+
<p class="dv-hint">
|
|
1216
|
+
channelWaypointDragMode 只影响下一次拖动:parallel 改共用骨架,组内回线按平行偏移整体重算;vertex 只把同组同一
|
|
1217
|
+
pathIndex 的拐点按相同世界增量平移,相邻拐点与其它分组不动。vertex 的结果写进 groups[].waypointOffsets,
|
|
1218
|
+
键是骨架顶点序号(只认中间拐点),格式 { "1": [dx, dy] };不写或空对象就是普通平行骨架。
|
|
1219
|
+
</p>
|
|
1220
|
+
</div>
|
|
1221
|
+
|
|
1222
|
+
<div class="dv-group">
|
|
1223
|
+
<h3>隐藏 / 显示与最近变化</h3>
|
|
1224
|
+
<div class="dv-row">
|
|
1225
|
+
<button type="button" class="dv-button" data-demo="point-hide" @click="setFirstFeatureVisible('point', false)">隐藏第一个节点</button>
|
|
1226
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="point-show" @click="setFirstFeatureVisible('point', true)">显示第一个节点</button>
|
|
1227
|
+
<button type="button" class="dv-button" data-demo="line-hide" @click="setFirstFeatureVisible('line', false)">隐藏第一条线路</button>
|
|
1228
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="line-show" @click="setFirstFeatureVisible('line', true)">显示第一条线路</button>
|
|
1229
|
+
</div>
|
|
1230
|
+
<div class="dv-row">
|
|
1231
|
+
<button type="button" class="dv-button" data-demo="channel-hide" @click="setFirstFeatureVisible('channel', false)">隐藏第一条通道</button>
|
|
1232
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="channel-show" @click="setFirstFeatureVisible('channel', true)">显示第一条通道</button>
|
|
1233
|
+
<button type="button" class="dv-button" data-demo="channel-clear-changed" @click="clearChannelChanges">通道对账</button>
|
|
1234
|
+
</div>
|
|
1235
|
+
<p class="dv-readout" data-demo="channel-visibility">{{ visibilityText }}</p>
|
|
1236
|
+
<p class="dv-readout" data-demo="channel-changed">{{ changedText }}</p>
|
|
1237
|
+
<p class="dv-hint">
|
|
1238
|
+
节点 / 线路 / 通道都能用 hideFeatures / showFeatures 收起再恢复,只改 visible,数据本身不删;通道收起时成员回线、分组、拐点与编辑状态都保留;
|
|
1239
|
+
getChangedChannels 会把分组、成员、拐点、端点、可见性与其余字段的变化逐条列出来,
|
|
1240
|
+
clearChangedChannels 把当前声明记成新的对账基准。
|
|
847
1241
|
</p>
|
|
848
1242
|
</div>
|
|
849
1243
|
|
|
@@ -883,7 +1277,7 @@ onBeforeUnmount(() => {
|
|
|
883
1277
|
<p class="dv-readout" data-demo="channel-waypoint-status">{{ waypointSyncText }}</p>
|
|
884
1278
|
<p class="dv-readout" data-demo="channel-backend-readout">{{ backendText }}</p>
|
|
885
1279
|
<p class="dv-hint">
|
|
886
|
-
{{ backendModeHint }}。请求头带 `Idempotency-Key` / `If-Match`,body 带 baseVersion 与 opId;拐点只接 add / remove / end,start / move 的中间态一条都不发;拖动结束读整份 path 覆盖。
|
|
1280
|
+
{{ backendModeHint }}。请求头带 `Idempotency-Key` / `If-Match`,body 带 baseVersion 与 opId;拐点只接 add / remove / end,start / move 的中间态一条都不发;拖动结束读整份 path 与 waypointOffsets 覆盖。
|
|
887
1281
|
通道里的拐点走 `PATCH /gis/channels/{id}/groups/waypoints`,独立线路走 `PATCH /gis/lines/{id}/path`;
|
|
888
1282
|
分组变化走 `PATCH /gis/channels/{id}/groups`,body 是整份 groups 声明,不按 delta 猜被挪空的组。
|
|
889
1283
|
成功响应统一为 `{ code, message, requestId, traceId, data: { operation, version, idempotent, transactionId, result } }`,
|
|
@@ -901,12 +1295,14 @@ onBeforeUnmount(() => {
|
|
|
901
1295
|
<h3>导出 / 回灌</h3>
|
|
902
1296
|
<div class="dv-row">
|
|
903
1297
|
<button type="button" class="dv-button" @click="exportData">导出(exportData)</button>
|
|
1298
|
+
<button type="button" class="dv-button" data-demo="channel-export-dto" @click="exportBackendDto">后端 DTO(自定义格式)</button>
|
|
904
1299
|
<button type="button" class="dv-button dv-button--primary" @click="rebuildFromExport">按导出重建</button>
|
|
905
1300
|
</div>
|
|
906
|
-
<p class="dv-readout">{{ exportText || '还没导出:点一下看导出的份量' }}</p>
|
|
1301
|
+
<p class="dv-readout" data-demo="channel-export-readout">{{ exportText || '还没导出:点一下看导出的份量' }}</p>
|
|
907
1302
|
<p class="dv-hint">
|
|
908
1303
|
回灌用的是 input 口径(与 createGisView 入参同构);view 口径是给后端 / 别的系统的业务记录
|
|
909
|
-
(station_data / line_data / parallel_groups
|
|
1304
|
+
(station_data / line_data / parallel_groups 那套列名),不拿来回灌。后端 DTO 口径由
|
|
1305
|
+
`defineExportFormat` 注册,卸载时只清掉自己这一个适配器;演示把标准文档映射成项目自己的表。
|
|
910
1306
|
</p>
|
|
911
1307
|
</div>
|
|
912
1308
|
|
|
@@ -1056,6 +1452,217 @@ onBeforeUnmount(() => {
|
|
|
1056
1452
|
line-height: 1.6;
|
|
1057
1453
|
}
|
|
1058
1454
|
|
|
1455
|
+
.dv-channel-menu__open-dialog {
|
|
1456
|
+
width: 100%;
|
|
1457
|
+
margin-top: 6px;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
/* 线路分组弹框:草稿式整组编辑,底部确定才调用一次 splitChannelGroups */
|
|
1461
|
+
.dv-group-dialog {
|
|
1462
|
+
position: absolute;
|
|
1463
|
+
z-index: 12;
|
|
1464
|
+
inset: 0;
|
|
1465
|
+
display: flex;
|
|
1466
|
+
align-items: center;
|
|
1467
|
+
justify-content: center;
|
|
1468
|
+
padding: 20px;
|
|
1469
|
+
background: rgba(18, 34, 56, 0.28);
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
.dv-group-dialog__panel {
|
|
1473
|
+
display: flex;
|
|
1474
|
+
flex-direction: column;
|
|
1475
|
+
width: min(760px, 100%);
|
|
1476
|
+
max-height: 100%;
|
|
1477
|
+
overflow: hidden;
|
|
1478
|
+
border: 1px solid #dce5f0;
|
|
1479
|
+
border-radius: 14px;
|
|
1480
|
+
background: #fff;
|
|
1481
|
+
box-shadow: 0 24px 64px rgba(16, 39, 70, 0.28);
|
|
1482
|
+
color: #314964;
|
|
1483
|
+
font-size: 12px;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
.dv-group-dialog__head {
|
|
1487
|
+
display: flex;
|
|
1488
|
+
align-items: flex-start;
|
|
1489
|
+
justify-content: space-between;
|
|
1490
|
+
gap: 12px;
|
|
1491
|
+
padding: 17px 20px 13px;
|
|
1492
|
+
border-bottom: 1px solid #edf1f6;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
.dv-group-dialog__head h3 {
|
|
1496
|
+
margin: 0;
|
|
1497
|
+
color: #17233d;
|
|
1498
|
+
font-size: 17px;
|
|
1499
|
+
font-weight: 650;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
.dv-group-dialog__head p {
|
|
1503
|
+
margin: 5px 0 0;
|
|
1504
|
+
color: #7f8fa5;
|
|
1505
|
+
font-size: 11px;
|
|
1506
|
+
word-break: break-all;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
.dv-group-dialog__head > button {
|
|
1510
|
+
padding: 0 4px;
|
|
1511
|
+
border: 0;
|
|
1512
|
+
background: transparent;
|
|
1513
|
+
color: #7d8ca1;
|
|
1514
|
+
font-size: 22px;
|
|
1515
|
+
line-height: 1;
|
|
1516
|
+
cursor: pointer;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
.dv-group-dialog__body {
|
|
1520
|
+
display: flex;
|
|
1521
|
+
flex-direction: column;
|
|
1522
|
+
gap: 11px;
|
|
1523
|
+
padding: 15px 20px;
|
|
1524
|
+
overflow: auto;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
.dv-group-dialog__card {
|
|
1528
|
+
overflow: hidden;
|
|
1529
|
+
border: 1px solid #e3e9f1;
|
|
1530
|
+
border-radius: 9px;
|
|
1531
|
+
background: #f7f9fc;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
.dv-group-dialog__card > header {
|
|
1535
|
+
display: flex;
|
|
1536
|
+
align-items: baseline;
|
|
1537
|
+
gap: 8px;
|
|
1538
|
+
padding: 10px 14px;
|
|
1539
|
+
border-bottom: 1px solid #e9eef5;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
.dv-group-dialog__card > header strong {
|
|
1543
|
+
color: #253c58;
|
|
1544
|
+
font-size: 13px;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
.dv-group-dialog__card > header span {
|
|
1548
|
+
color: #8a99ad;
|
|
1549
|
+
font-size: 11px;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
.dv-group-dialog__card > header code {
|
|
1553
|
+
margin-left: auto;
|
|
1554
|
+
color: #9aa8b9;
|
|
1555
|
+
font-size: 10px;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
.dv-group-dialog__card ul {
|
|
1559
|
+
margin: 0;
|
|
1560
|
+
padding: 2px 12px 4px;
|
|
1561
|
+
list-style: none;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
.dv-group-dialog__line {
|
|
1565
|
+
display: flex;
|
|
1566
|
+
align-items: center;
|
|
1567
|
+
gap: 8px;
|
|
1568
|
+
padding: 9px 2px;
|
|
1569
|
+
border-bottom: 1px solid #ebeff5;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
.dv-group-dialog__line:last-child {
|
|
1573
|
+
border-bottom: 0;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
.dv-group-dialog__line-text {
|
|
1577
|
+
display: flex;
|
|
1578
|
+
flex: 1;
|
|
1579
|
+
flex-direction: column;
|
|
1580
|
+
min-width: 0;
|
|
1581
|
+
gap: 2px;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
.dv-group-dialog__line-text strong {
|
|
1585
|
+
overflow: hidden;
|
|
1586
|
+
color: #314964;
|
|
1587
|
+
font-size: 12px;
|
|
1588
|
+
text-overflow: ellipsis;
|
|
1589
|
+
white-space: nowrap;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
.dv-group-dialog__line-text span {
|
|
1593
|
+
overflow: hidden;
|
|
1594
|
+
color: #93a0b2;
|
|
1595
|
+
font-size: 10px;
|
|
1596
|
+
text-overflow: ellipsis;
|
|
1597
|
+
white-space: nowrap;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
.dv-group-dialog select {
|
|
1601
|
+
min-width: 138px;
|
|
1602
|
+
padding: 5px 7px;
|
|
1603
|
+
border: 1px solid #d6e0ec;
|
|
1604
|
+
border-radius: 6px;
|
|
1605
|
+
background: #fff;
|
|
1606
|
+
color: #3c5878;
|
|
1607
|
+
font-size: 11px;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
.dv-group-dialog button {
|
|
1611
|
+
padding: 5px 10px;
|
|
1612
|
+
border: 1px solid #d1deed;
|
|
1613
|
+
border-radius: 6px;
|
|
1614
|
+
background: #f7faff;
|
|
1615
|
+
color: #2f5e94;
|
|
1616
|
+
font-size: 11px;
|
|
1617
|
+
cursor: pointer;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
.dv-group-dialog button:disabled {
|
|
1621
|
+
opacity: 0.45;
|
|
1622
|
+
cursor: not-allowed;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
.dv-group-dialog__line button {
|
|
1626
|
+
white-space: nowrap;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
.dv-group-dialog__footer {
|
|
1630
|
+
display: flex;
|
|
1631
|
+
align-items: center;
|
|
1632
|
+
justify-content: space-between;
|
|
1633
|
+
gap: 14px;
|
|
1634
|
+
padding: 13px 20px 15px;
|
|
1635
|
+
border-top: 1px solid #edf1f6;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
.dv-group-dialog__footer p {
|
|
1639
|
+
flex: 1;
|
|
1640
|
+
margin: 0;
|
|
1641
|
+
color: #7f8fa5;
|
|
1642
|
+
font-size: 11px;
|
|
1643
|
+
line-height: 1.55;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
.dv-group-dialog__footer > div {
|
|
1647
|
+
display: flex;
|
|
1648
|
+
gap: 8px;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
.dv-group-dialog__footer button {
|
|
1652
|
+
padding: 7px 15px;
|
|
1653
|
+
font-size: 12px;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
.dv-group-dialog__footer .dv-button--primary {
|
|
1657
|
+
border-color: #2f78d0;
|
|
1658
|
+
background: #347fd8;
|
|
1659
|
+
color: #fff;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
.dv-group-dialog__footer .dv-button--primary:disabled {
|
|
1663
|
+
background: #a8c4e6;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1059
1666
|
/* 删除确认框:组件只把载荷交出来(要删什么 / 连带什么 / 能按哪些范围删),框是这一页自己按载荷画的 */
|
|
1060
1667
|
.dv-remove-box {
|
|
1061
1668
|
position: absolute;
|