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
|
@@ -983,7 +983,7 @@
|
|
|
983
983
|
|
|
984
984
|
/* 快捷键:Ctrl+C / Ctrl+V 与默认框选键 */
|
|
985
985
|
cmEvents.length = 0
|
|
986
|
-
cmView.
|
|
986
|
+
cmView.selectFeatures('a')
|
|
987
987
|
cmView.el.dispatchEvent(new KeyboardEvent('keydown', { key: 'c', ctrlKey: true, bubbles: true }))
|
|
988
988
|
check('Ctrl+C 把选中的节点收进剪贴板(默认快捷键)', cmView.getPointClipboard()?.item.id === 'a', cmView.getPointClipboard()?.item.id)
|
|
989
989
|
check('Ctrl+C 命中 onPointCopy 的 clipboard 载荷(copy + keyboard + shortcuts 三件套)', cmShortcutClipboard?.phase === 'clipboard' && cmShortcutClipboard?.via === 'shortcut' && cmShortcutClipboard?.count === 1 && cmShortcutClipboard?.item?.id === 'a', JSON.stringify({ phase: cmShortcutClipboard?.phase, via: cmShortcutClipboard?.via, count: cmShortcutClipboard?.count, item: cmShortcutClipboard?.item?.id }))
|
|
@@ -998,13 +998,13 @@
|
|
|
998
998
|
cmView.fitView()
|
|
999
999
|
await settle(30)
|
|
1000
1000
|
const cmAnchor = cmView.project(0, 0)
|
|
1001
|
-
cmView.
|
|
1001
|
+
cmView.selectFeatures(null)
|
|
1002
1002
|
await cmDrag(cmView, [cmAnchor[0] - 26, cmAnchor[1] - 26], [cmAnchor[0] + 26, cmAnchor[1] + 26], { ctrlKey: true })
|
|
1003
1003
|
check('默认框选键是 Shift 时 Ctrl 拖动不再框选', cmView.getSelections().length === 0, `选中 ${cmView.getSelections().length}`)
|
|
1004
1004
|
cmView.fitView()
|
|
1005
1005
|
await settle(30)
|
|
1006
1006
|
const cmAnchor2 = cmView.project(0, 0)
|
|
1007
|
-
cmView.
|
|
1007
|
+
cmView.selectFeatures(null)
|
|
1008
1008
|
await cmDrag(cmView, [cmAnchor2[0] - 26, cmAnchor2[1] - 26], [cmAnchor2[0] + 26, cmAnchor2[1] + 26], { shiftKey: true })
|
|
1009
1009
|
check('按住新的框选键(Shift)拖动出选框并选中节点', cmView.getSelections().length >= 1 && cmView.getSelections().some(item => item.item.id === 'a'), `选中 ${cmView.getSelections().map(item => item.item.id).join(',')}`)
|
|
1010
1010
|
cmView.updateOptions({ shortcuts: { boxSelect: 'ctrl' } })
|
|
@@ -1249,6 +1249,47 @@
|
|
|
1249
1249
|
check('mergePoints 找不到节点:ok: false + 一条失败事件', cmFail.ok === false && cmMergeEvents5[cmMergeEvents5.length - 1].phase === 'merge' && cmMergeEvents5[cmMergeEvents5.length - 1].ok === false && cmView5.getPointMerge() === null, JSON.stringify(cmFail).slice(0, 160))
|
|
1250
1250
|
cmView5.destroy()
|
|
1251
1251
|
cmHost5.remove()
|
|
1252
|
+
|
|
1253
|
+
/* 合并触发时机:碰撞(两个半径相碰)/ 中心点进入目标节点区域 */
|
|
1254
|
+
const triggerHost = document.createElement('div')
|
|
1255
|
+
triggerHost.style.cssText = 'width:520px;height:300px'
|
|
1256
|
+
document.body.appendChild(triggerHost)
|
|
1257
|
+
const triggerEvents = []
|
|
1258
|
+
const triggerView = createGisView(triggerHost, {
|
|
1259
|
+
points: [{ id: 'source', x: 0, y: 0, size: 12 }, { id: 'target', x: 140, y: 0, size: 12 }],
|
|
1260
|
+
fitPadding: 70,
|
|
1261
|
+
showGrid: false,
|
|
1262
|
+
merge: { trigger: 'center', confirm: false },
|
|
1263
|
+
draggable: true,
|
|
1264
|
+
onPointMerge: payload => triggerEvents.push(payload)
|
|
1265
|
+
})
|
|
1266
|
+
await settle(120)
|
|
1267
|
+
const triggerTarget = triggerView.project(140, 0)
|
|
1268
|
+
await cmDrag(triggerView, triggerView.project(0, 0), [triggerTarget[0] - 10, triggerTarget[1]])
|
|
1269
|
+
check('merge.trigger=center:中心还差 10px 没进入目标区域时不触发(同样距离下碰撞档会触发)', triggerEvents.length === 0 && triggerView.getData().points.length === 2, JSON.stringify({ events: triggerEvents.map(item => item.phase), points: triggerView.getData().points.map(item => item.id) }))
|
|
1270
|
+
const triggerSource = triggerView.getData().points.find(point => point.id === 'source')
|
|
1271
|
+
await cmDrag(triggerView, triggerView.project(triggerSource.x, triggerSource.y), triggerTarget)
|
|
1272
|
+
check('merge.trigger=center:中心进入目标区域后触发,载荷带 trigger=center', triggerView.getData().points.length === 1 && triggerView.getData().points[0].id === 'target' && triggerEvents[0]?.trigger === 'center', JSON.stringify({ points: triggerView.getData().points.map(item => item.id), trigger: triggerEvents[0]?.trigger }))
|
|
1273
|
+
triggerView.destroy()
|
|
1274
|
+
triggerHost.remove()
|
|
1275
|
+
|
|
1276
|
+
const collisionHost = document.createElement('div')
|
|
1277
|
+
collisionHost.style.cssText = 'width:520px;height:300px'
|
|
1278
|
+
document.body.appendChild(collisionHost)
|
|
1279
|
+
const collisionView = createGisView(collisionHost, {
|
|
1280
|
+
points: [{ id: 'source', x: 0, y: 0, size: 12 }, { id: 'target', x: 140, y: 0, size: 12 }],
|
|
1281
|
+
fitPadding: 70,
|
|
1282
|
+
showGrid: false,
|
|
1283
|
+
merge: { trigger: 'collision', confirm: false },
|
|
1284
|
+
draggable: true
|
|
1285
|
+
})
|
|
1286
|
+
await settle(120)
|
|
1287
|
+
const collisionTarget = collisionView.project(140, 0)
|
|
1288
|
+
await cmDrag(collisionView, collisionView.project(0, 0), [collisionTarget[0] - 10, collisionTarget[1]])
|
|
1289
|
+
check('merge.trigger=collision:中心还差 10px 时两个节点半径已经相碰,照旧触发', collisionView.getData().points.length === 1 && collisionView.getData().points[0].id === 'target', JSON.stringify(collisionView.getData().points.map(item => item.id)))
|
|
1290
|
+
collisionView.destroy()
|
|
1291
|
+
collisionHost.remove()
|
|
1292
|
+
|
|
1252
1293
|
/* 合并 ④:框选 / 多选之后按接口一次并完(mergeSelectedPoints) */
|
|
1253
1294
|
const msHost = document.createElement('div')
|
|
1254
1295
|
msHost.style.cssText = 'width:560px;height:360px'
|
|
@@ -1288,7 +1329,7 @@
|
|
|
1288
1329
|
})
|
|
1289
1330
|
await settle(140)
|
|
1290
1331
|
const msIds = () => msView.getData().points.map(point => point.id ?? '(无id)').join(',')
|
|
1291
|
-
msView.
|
|
1332
|
+
msView.selectFeatures(['h', 'a', 'b', 'c'])
|
|
1292
1333
|
await settle(30)
|
|
1293
1334
|
const msBulk = msView.mergeSelectedPoints()
|
|
1294
1335
|
check('框选 / 多选之后一步并完:自动挑「绑定线路最多」的节点当目标', msBulk.ok === true && msBulk.phase === 'mergeSelected' && msBulk.target.item.id === 'h' && msBulk.sourcesMerged === 3 && msBulk.skipped.length === 0, JSON.stringify({ target: msBulk.target?.item?.id, merged: msBulk.sourcesMerged, skipped: msBulk.skipped }))
|
|
@@ -1304,7 +1345,7 @@
|
|
|
1304
1345
|
/* 相机移动不该收掉待确认的合并(小图拖动 / moveCenter 走的是同一条路) */
|
|
1305
1346
|
msView.fitView()
|
|
1306
1347
|
await settle(60)
|
|
1307
|
-
msView.
|
|
1348
|
+
msView.selectFeatures(null)
|
|
1308
1349
|
await cmDrag(msView, msView.project(100, 0), msView.project(0, 0))
|
|
1309
1350
|
const msPend = msView.getPointMerge()
|
|
1310
1351
|
check('把节点拖到目标上:进入待确认(读得到 pending 载荷)', msPend !== null && msPend.phase === 'state' && msPend.confirmed === false, JSON.stringify(msPend?.phase))
|
|
@@ -1319,16 +1360,16 @@
|
|
|
1319
1360
|
const msWrapped = msView.batch(() => msView.mergeSelectedPoints({ target: 'h', sources: ['b', 'c'] }), { label: '批量合并' })
|
|
1320
1361
|
const msWrappedUndo = msView.undo()
|
|
1321
1362
|
check('batch 里调 mergeSelectedPoints:整批只占一步历史(撤销一次全回去)', msWrapped?.ok === true && msWrapped.sourcesMerged === 2 && msWrappedUndo?.label === '批量合并' && msIds() === 'h,a,b,c,lock,(无id)', JSON.stringify({ merged: msWrapped?.sourcesMerged, label: msWrappedUndo?.label }))
|
|
1322
|
-
msView.
|
|
1363
|
+
msView.selectFeatures(null)
|
|
1323
1364
|
const msEmpty = msView.mergeSelectedPoints()
|
|
1324
1365
|
check('没选中节点:ok: false 并带回原因', msEmpty.ok === false && msEmpty.phase === 'mergeSelected' && msEmpty.reason === '没有选中的节点' && msEmpty.target === null, msEmpty.reason)
|
|
1325
|
-
msView.
|
|
1366
|
+
msView.selectFeatures('h')
|
|
1326
1367
|
const msOne = msView.mergeSelectedPoints()
|
|
1327
1368
|
check('只选中一个节点:自动挑目标时要两个以上', msOne.ok === false && msOne.reason === '选中的节点不足两个', msOne.reason)
|
|
1328
|
-
msView.
|
|
1369
|
+
msView.selectFeatures(['h', 'lock'])
|
|
1329
1370
|
const msAllLocked = msView.mergeSelectedPoints()
|
|
1330
1371
|
check('选中里只剩 mergeable: false 的节点:一个都并不了', msAllLocked.ok === false && msAllLocked.reason === '可以合并的节点不足两个', msAllLocked.reason)
|
|
1331
|
-
msView.
|
|
1372
|
+
msView.selectFeatures(['h', 'a', 'lock'])
|
|
1332
1373
|
const msSelective = msView.mergeSelectedPoints()
|
|
1333
1374
|
check('多选里混着 mergeable: false:跳过它、其余照并(skipped 说明原因)', msSelective.ok === true && msSelective.sourcesMerged === 1 && msSelective.merged[0].source.item.id === 'a' && msSelective.skipped.length === 1 && msSelective.skipped[0].id === 'lock' && msSelective.skipped[0].reason === '标记了 mergeable: false', JSON.stringify({ merged: msSelective.sourcesMerged, skipped: msSelective.skipped }))
|
|
1334
1375
|
check('只有一个来源时走普通的一次合并(数据变更不是 batch)', msChanges[msChanges.length - 1].type === 'mergePoints', msChanges[msChanges.length - 1].type)
|
|
@@ -1401,14 +1442,14 @@
|
|
|
1401
1442
|
const cmCenter = cmLayerView.project(0, 0)
|
|
1402
1443
|
const cmOverPixel = await cmPixelAt(cmLayerView, cmCenter)
|
|
1403
1444
|
check('没选中时上层节点压在下面那个之上(按数据顺序画)', cmIsBlue(cmOverPixel), JSON.stringify(cmOverPixel))
|
|
1404
|
-
cmLayerView.
|
|
1445
|
+
cmLayerView.selectFeatures('under')
|
|
1405
1446
|
await settle(60)
|
|
1406
1447
|
const cmRaisedPixel = await cmPixelAt(cmLayerView, cmCenter)
|
|
1407
1448
|
check('选中的节点抬到最上层(与 zIndex / 数据顺序无关)', cmIsRed(cmRaisedPixel), JSON.stringify(cmRaisedPixel))
|
|
1408
|
-
cmLayerView.
|
|
1449
|
+
cmLayerView.selectFeatures(null)
|
|
1409
1450
|
cmLayerView.updateOptions({ selectionOnTop: false })
|
|
1410
1451
|
await settle(60)
|
|
1411
|
-
cmLayerView.
|
|
1452
|
+
cmLayerView.selectFeatures('under')
|
|
1412
1453
|
await settle(60)
|
|
1413
1454
|
const cmFlatPixel = await cmPixelAt(cmLayerView, cmCenter)
|
|
1414
1455
|
check('selectionOnTop: false 时不抬层(仍然按数据顺序画)', cmIsBlue(cmFlatPixel), JSON.stringify(cmFlatPixel))
|
|
@@ -1464,11 +1505,11 @@
|
|
|
1464
1505
|
const cmLineSpot = cmRaiseLineView.project(0, 0.6)
|
|
1465
1506
|
const cmLineStillPixel = await cmPixelAt(cmRaiseLineView, cmLineSpot)
|
|
1466
1507
|
check('两条线重叠处:后画的那条压在上面', cmIsBlue(cmLineStillPixel), JSON.stringify(cmLineStillPixel))
|
|
1467
|
-
cmRaiseLineView.
|
|
1508
|
+
cmRaiseLineView.selectFeatures('line-low')
|
|
1468
1509
|
await settle(90)
|
|
1469
1510
|
const cmLineRaisePixel = await cmPixelAt(cmRaiseLineView, cmLineSpot)
|
|
1470
1511
|
check('选中线路:抬到最上层(与数据顺序无关)', cmIsRed(cmLineRaisePixel), JSON.stringify(cmLineRaisePixel))
|
|
1471
|
-
cmRaiseLineView.
|
|
1512
|
+
cmRaiseLineView.selectFeatures(null)
|
|
1472
1513
|
await settle(90)
|
|
1473
1514
|
const cmLineBackPixel = await cmPixelAt(cmRaiseLineView, cmLineSpot)
|
|
1474
1515
|
check('取消选中:线路恢复原来的次序', cmIsBlue(cmLineBackPixel), JSON.stringify(cmLineBackPixel))
|
|
@@ -1504,7 +1545,7 @@
|
|
|
1504
1545
|
cmChannelStillPixels.push(await cmPixelAt(cmRaiseChannelView, spot))
|
|
1505
1546
|
}
|
|
1506
1547
|
check('通道回线与另一条线交叉处:后画的普通线压在上面', cmChannelStillPixels.every(cmIsBlue), JSON.stringify(cmChannelStillPixels))
|
|
1507
|
-
cmRaiseChannelView.
|
|
1548
|
+
cmRaiseChannelView.selectFeatures('ch-c1')
|
|
1508
1549
|
await settle(120)
|
|
1509
1550
|
const cmChannelRaisePixels = []
|
|
1510
1551
|
for (const spot of cmChannelSpots) {
|
|
@@ -1747,7 +1788,7 @@
|
|
|
1747
1788
|
// ② 整份换成新对象(等价于撤销 / 重新拉一份数据):收掉并补一条 cancel,载荷带 invalidated
|
|
1748
1789
|
const pendSourceBefore = pendView.getData().points.find(point => point.id === 'a')
|
|
1749
1790
|
const pendSourceX = pendSourceBefore.x
|
|
1750
|
-
pendView.
|
|
1791
|
+
pendView.updateData({ points: pendView.getData().points.map(point => ({ ...point })) })
|
|
1751
1792
|
await settle(80)
|
|
1752
1793
|
const pendCancel = pendMergeEvents[pendMergeEvents.length - 1]
|
|
1753
1794
|
const pendSourceAfter = pendView.getData().points.find(point => point.id === 'a')
|
|
@@ -1859,11 +1900,11 @@
|
|
|
1859
1900
|
check('updateOptions 发 onDataChange(changed 标出改的是哪一类)', evLog.data.length === 1 && evLog.data[0].type === 'updateOptions' && evLog.data[0].changed.points === false && evLog.data[0].changed.channels === true, evTypes())
|
|
1860
1901
|
|
|
1861
1902
|
evLog.select.length = 0
|
|
1862
|
-
evView.
|
|
1903
|
+
evView.selectFeatures('a')
|
|
1863
1904
|
check('接口选中发 onSelectChange(cause 为 api)', evLog.select.length === 1 && evLog.select[0].cause === 'api' && evLog.select[0].count === 1 && evLog.select[0].ids[0] === 'a' && evLog.select[0].points === 1, JSON.stringify(evLog.select.map(entry => `${entry.cause}:${entry.count}`)))
|
|
1864
|
-
evView.
|
|
1905
|
+
evView.selectFeatures('a')
|
|
1865
1906
|
check('选中集没变就不再发 onSelectChange', evLog.select.length === 1, `次数 ${evLog.select.length}`)
|
|
1866
|
-
evView.
|
|
1907
|
+
evView.selectFeatures(null)
|
|
1867
1908
|
check('清空选中也发一条(count: 0 / cause: api)', evLog.select.length === 2 && evLog.select[1].count === 0 && evLog.select[1].cause === 'api', JSON.stringify(evLog.select.map(entry => entry.count)))
|
|
1868
1909
|
|
|
1869
1910
|
evLog.shortcut.length = 0
|
|
@@ -1937,7 +1978,7 @@
|
|
|
1937
1978
|
check('onChannelGroupChange 回调和事件总线收到同一份载荷', cgOption.length === 1 && cgOption[0] === cgBus[0])
|
|
1938
1979
|
|
|
1939
1980
|
// 选中一条回线再合并:选中镜像里的 groupId / groupIndex 要跟着真值走(本轮修复的回归守卫)
|
|
1940
|
-
cgView.
|
|
1981
|
+
cgView.selectFeatures('c2')
|
|
1941
1982
|
await settle(60)
|
|
1942
1983
|
const cgTruth = () => {
|
|
1943
1984
|
const groups = cgView.getChannelGroups('ch-1')
|
|
@@ -2005,8 +2046,8 @@
|
|
|
2005
2046
|
ulHost.remove()
|
|
2006
2047
|
/* 这一轮新增:通道改名 / 通道下增减回线 / 删除分组(问题.txt 第 2 条那几个「方法有吗」) */
|
|
2007
2048
|
const cgNameBefore = cgData.length
|
|
2008
|
-
cgView.
|
|
2009
|
-
check('
|
|
2049
|
+
cgView.updateChannel('ch-1', { name: '甲通道' })
|
|
2050
|
+
check('updateChannel({ name }) 改通道名称:数据副本落地 + 一条 updateChannel 变更', cgView.getOptions().channels[0].name === '甲通道' && cgData.length === cgNameBefore + 1 && cgData[cgData.length - 1].type === 'updateChannel', JSON.stringify({ name: cgView.getOptions().channels[0].name, type: cgData[cgData.length - 1]?.type }))
|
|
2010
2051
|
|
|
2011
2052
|
cgView.addLine({ id: 'c3', from: 'ca', to: 'cb', color: '#8a5cf6' })
|
|
2012
2053
|
const cgMemberCount = () => cgView.getChannels().reduce((total, state) => total + state.lineIndexes.length, 0)
|
|
@@ -2143,7 +2184,7 @@
|
|
|
2143
2184
|
ucView.destroy()
|
|
2144
2185
|
ucHost.remove()
|
|
2145
2186
|
|
|
2146
|
-
/* 这一轮新增:改 lineIds 增删线路(有 groups 也生效)+
|
|
2187
|
+
/* 这一轮新增:改 lineIds 增删线路(有 groups 也生效)+ updateData({ channels }) 写回分组 id + 声明校验 warning */
|
|
2147
2188
|
const chHost = document.createElement('div')
|
|
2148
2189
|
chHost.style.cssText = 'width:420px;height:280px'
|
|
2149
2190
|
document.body.appendChild(chHost)
|
|
@@ -2164,6 +2205,10 @@
|
|
|
2164
2205
|
await settle(150)
|
|
2165
2206
|
const chGroups = () => chView.getChannelGroups('chA').map(group => ({ id: group.id, lineIds: group.lineIds }))
|
|
2166
2207
|
const chMemberCount = () => chView.getChannels().reduce((total, state) => total + state.lineIndexes.length, 0)
|
|
2208
|
+
const chInView = chView.getChannelsInView()
|
|
2209
|
+
check('getChannelsInView 按视口单遍归并通道,回线与分组 id 都去重',
|
|
2210
|
+
chInView.length === 1 && chInView[0].channelId === 'chA' && chInView[0].lineIndexes.length === 2 && chInView[0].groupIds.length === 1,
|
|
2211
|
+
JSON.stringify(chInView))
|
|
2167
2212
|
|
|
2168
2213
|
chView.updateChannel('chA', { groups: [{ id: 'ga', lineIds: ['k1'] }, { id: 'gb', lineIds: ['k2'] }] })
|
|
2169
2214
|
await settle(60)
|
|
@@ -2191,16 +2236,16 @@
|
|
|
2191
2236
|
check('updateChannel 找不到通道 / 没给补丁各报一句 warning,不静默空操作', chWarn.length === 2, JSON.stringify(chWarn))
|
|
2192
2237
|
|
|
2193
2238
|
chWarn.length = 0
|
|
2194
|
-
chView.
|
|
2239
|
+
chView.updateData({ channels: [{ id: 'chA', name: 'A 通道', groups: [{ lineIds: ['k3'] }, { lineIds: ['nope2'] }] }] })
|
|
2195
2240
|
await settle(60)
|
|
2196
2241
|
const chDeclared = chView.getOptions().channels[0].groups ?? []
|
|
2197
|
-
check('
|
|
2198
|
-
check('
|
|
2242
|
+
check('updateData({ channels }) 把引擎补好的分组 id / 拐点路径写回声明(缺省 id 不再留在数据里)', chDeclared.length === 1 && typeof chDeclared[0].id === 'string' && chDeclared[0].id.length > 0 && Array.isArray(chDeclared[0].path) && JSON.stringify(chDeclared[0].lineIds) === JSON.stringify(['k3']), JSON.stringify(chDeclared))
|
|
2243
|
+
check('updateData({ channels }) 里认不出的成员也报一句 warning', chWarn.length === 1 && String(chWarn[0]).includes('nope2'), JSON.stringify(chWarn))
|
|
2199
2244
|
/* 这一轮新增:整份提交的几个入口也走同一份调和(lineIds 是成员整表),写回时成员表对齐到分组 */
|
|
2200
|
-
chView.
|
|
2245
|
+
chView.updateData({ channels: [{ id: 'chA', lineIds: ['k1', 'k2'], groups: [{ id: 'ga', lineIds: ['k1'] }, { id: 'gb', lineIds: ['k2'] }, { id: 'gc', lineIds: ['k9'] }] }] })
|
|
2201
2246
|
await settle()
|
|
2202
2247
|
const chWhole = chGroups()
|
|
2203
|
-
check('
|
|
2248
|
+
check('updateData({ channels }) 整份提交也调和:声明里 lineIds 与 groups 冲突时按 lineIds 收窄(多出的组丢掉)', JSON.stringify(chWhole.map(group => group.lineIds)) === JSON.stringify([['k1'], ['k2']]) && chMemberCount() === 2, JSON.stringify({ groups: chWhole, members: chMemberCount() }))
|
|
2204
2249
|
|
|
2205
2250
|
chView.updateOptions({ channels: [{ id: 'chA', lineIds: ['k9'] }] })
|
|
2206
2251
|
await settle()
|
|
@@ -2214,18 +2259,18 @@
|
|
|
2214
2259
|
await settle()
|
|
2215
2260
|
check('importData({ channels }) 导入的声明同样过调和(applied.channels 为真)', chImported.ok === true && chImported.applied.channels === true && chMemberCount() === 2, JSON.stringify({ ok: chImported.ok, applied: chImported.applied, members: chMemberCount() }))
|
|
2216
2261
|
|
|
2217
|
-
chView.
|
|
2262
|
+
chView.updateData({ channels: [{ id: 'chA', groups: [{ id: 'ga', lineIds: ['k1'] }, { id: 'gb', lineIds: ['k9'] }] }] })
|
|
2218
2263
|
await settle()
|
|
2219
2264
|
const chOnlyGroups = chView.getOptions().channels[0]
|
|
2220
2265
|
check('只写 groups 的声明原样保留(不补 lineIds,成员由 groups 决定)', chOnlyGroups.lineIds === undefined && chMemberCount() === 2 && chGroups().length === 2, JSON.stringify({ lineIds: chOnlyGroups.lineIds, members: chMemberCount(), groups: chGroups() }))
|
|
2221
2266
|
|
|
2222
|
-
chView.
|
|
2267
|
+
chView.updateData({ channels: [{ id: 'chA', lineIds: ['k1'], groups: [{ id: 'ga', lineIds: ['k1'] }] }] })
|
|
2223
2268
|
await settle()
|
|
2224
2269
|
chView.updateChannel('chA', { groups: [{ id: 'ga', lineIds: ['k1'] }, { id: 'gb', lineIds: ['k9'] }] })
|
|
2225
2270
|
await settle()
|
|
2226
2271
|
check('写回时成员表跟着分组对齐:挪进分组的回线补进 lineIds(下一轮整份提交不会被收窄剔掉)', chMemberCount() === 2 && JSON.stringify(chView.getOptions().channels[0].lineIds) === JSON.stringify(['k1', 'k9']), JSON.stringify(chView.getOptions().channels[0]))
|
|
2227
2272
|
|
|
2228
|
-
chView.
|
|
2273
|
+
chView.updateData({ channels: [JSON.parse(JSON.stringify(chView.getOptions().channels[0]))] })
|
|
2229
2274
|
await settle()
|
|
2230
2275
|
check('拆分 / 挪组之后把声明原样整份重提交:成员与分组一个都不丢', chMemberCount() === 2 && chGroups().length === 2, JSON.stringify({ groups: chGroups(), members: chMemberCount() }))
|
|
2231
2276
|
|
|
@@ -2471,8 +2516,8 @@
|
|
|
2471
2516
|
mvView.undo()
|
|
2472
2517
|
check('位置对账:撤销回到基线之后不再算「位置变过」', mvView.getMovedPoints().length === 0, JSON.stringify(mvView.getMovedPoints().map(item => item.id)))
|
|
2473
2518
|
mvView.updatePoint('mc', { x: 900, y: 0 })
|
|
2474
|
-
mvView.
|
|
2475
|
-
check('位置对账:整份换节点数据(
|
|
2519
|
+
mvView.updateData({ points: mvView.getData().points })
|
|
2520
|
+
check('位置对账:整份换节点数据(updateData({ points }))按新数据对齐', mvView.getMovedPoints().length === 0, JSON.stringify(mvView.getMovedPoints().map(item => item.id)))
|
|
2476
2521
|
mvView.addPoints({ id: 'me', x: 600, y: 0 })
|
|
2477
2522
|
const mvAfterAdd = mvView.getMovedPoints().length
|
|
2478
2523
|
mvView.updatePoint('me', { x: 620, y: 20 })
|
|
@@ -2580,6 +2625,42 @@
|
|
|
2580
2625
|
ftView.destroy()
|
|
2581
2626
|
ftHost.remove()
|
|
2582
2627
|
|
|
2628
|
+
// 拐点只有一道总闸:waypoint / waypointEditable 关闭时新增、移动与删除一起停
|
|
2629
|
+
const waHost = document.createElement('div')
|
|
2630
|
+
waHost.style.cssText = 'width:320px;height:200px'
|
|
2631
|
+
document.body.appendChild(waHost)
|
|
2632
|
+
const waLog = []
|
|
2633
|
+
const waView = createGisView(waHost, {
|
|
2634
|
+
points: [{ id: 'a', x: 0, y: 0 }, { id: 'b', x: 100, y: 0 }],
|
|
2635
|
+
lines: [{ id: 'wa-line', from: 'a', to: 'b' }],
|
|
2636
|
+
showGrid: false,
|
|
2637
|
+
features: { waypoint: false },
|
|
2638
|
+
onWaypointChange: payload => waLog.push(payload)
|
|
2639
|
+
})
|
|
2640
|
+
await settle(120)
|
|
2641
|
+
const waState = waView.getFeatures()
|
|
2642
|
+
waView.addWaypoint('wa-line', [50, 10])
|
|
2643
|
+
await settle(60)
|
|
2644
|
+
check('waypoint:false 总闸关闭新增、且不再有 waypointAdd 细分开关',
|
|
2645
|
+
waState.waypoint === false && !Object.prototype.hasOwnProperty.call(waState, 'waypointAdd') && (waView.getData().lines[0].path ?? null) === null && waLog.length === 0,
|
|
2646
|
+
JSON.stringify({ waypoint: waState.waypoint, keys: Object.keys(waState), path: waView.getData().lines[0].path, events: waLog.length }))
|
|
2647
|
+
waView.setFeatures({ waypoint: true })
|
|
2648
|
+
await settle(60)
|
|
2649
|
+
waView.addWaypoint('wa-line', [50, 10])
|
|
2650
|
+
await settle(60)
|
|
2651
|
+
const waPath = waView.getData().lines[0].path
|
|
2652
|
+
check('打开 waypoint 之后 addWaypoint 正常插入并发 add',
|
|
2653
|
+
waView.getFeatures().waypoint === true && waPath?.length === 3 && waLog.length === 1 && waLog[0].phase === 'add',
|
|
2654
|
+
JSON.stringify({ waypoint: waView.getFeatures().waypoint, path: waPath, events: waLog.map(item => item.phase) }))
|
|
2655
|
+
waView.setFeatures({ waypoint: false })
|
|
2656
|
+
const waRemoved = waView.removeWaypoint('wa-line', 1)
|
|
2657
|
+
await settle(60)
|
|
2658
|
+
check('waypoint:false 同时关闭已有拐点的删除(接口空操作、数据不动)',
|
|
2659
|
+
waRemoved === waView && waView.getData().lines[0].path?.length === 3 && waLog.length === 1,
|
|
2660
|
+
JSON.stringify({ removed: waRemoved, path: waView.getData().lines[0].path, events: waLog.length }))
|
|
2661
|
+
waView.destroy()
|
|
2662
|
+
waHost.remove()
|
|
2663
|
+
|
|
2583
2664
|
// 缺省不记历史:一个开关都不开时与从前完全一样
|
|
2584
2665
|
const nhHost = document.createElement('div')
|
|
2585
2666
|
nhHost.style.cssText = 'width:320px;height:200px'
|
|
@@ -2600,7 +2681,7 @@
|
|
|
2600
2681
|
const stHost = document.createElement('div')
|
|
2601
2682
|
stHost.style.cssText = 'width:320px;height:200px'
|
|
2602
2683
|
document.body.appendChild(stHost)
|
|
2603
|
-
const stView = createGisView(stHost, { points: [{ id: 'a', x: 0, y: 0 }, { id: 'b', x: 100, y: 40 }], features: 'static', showGrid: false })
|
|
2684
|
+
const stView = createGisView(stHost, { points: [{ id: 'a', x: 0, y: 0, name: '甲' }, { id: 'b', x: 100, y: 40 }], features: 'static', showGrid: false, label: { content: '{name}' } })
|
|
2604
2685
|
await settle(150)
|
|
2605
2686
|
const stScale = stView.getCamera().scale
|
|
2606
2687
|
const stBox = stView.canvas.getBoundingClientRect()
|
|
@@ -2608,6 +2689,9 @@
|
|
|
2608
2689
|
await settle(80)
|
|
2609
2690
|
check('features 预设 static:滚轮不缩放', stView.getCamera().scale === stScale, `${stScale} -> ${stView.getCamera().scale}`)
|
|
2610
2691
|
check('features 预设 static:指针事件照旧(getFeatures 里 pan / zoom / hover / select 全是 false)', stView.getFeatures().pan === false && stView.getFeatures().zoom === false && stView.getFeatures().hover === false && stView.getFeatures().select === false, JSON.stringify(stView.getFeatures()))
|
|
2692
|
+
check('features 预设 static:全部开关为 false,配了标签也不产生 DOM',
|
|
2693
|
+
Object.values(stView.getFeatures()).every(value => value === false) && stView.el.querySelectorAll('.gis-view__label-layer--point .gis-view__label').length === 0,
|
|
2694
|
+
JSON.stringify({ state: stView.getFeatures(), labels: stView.el.querySelectorAll('.gis-view__label-layer--point .gis-view__label').length }))
|
|
2611
2695
|
stView.destroy()
|
|
2612
2696
|
stHost.remove()
|
|
2613
2697
|
|
|
@@ -2753,7 +2837,7 @@
|
|
|
2753
2837
|
const exEmpty = await exView.exportSelection({ format: 'data' })
|
|
2754
2838
|
check('没选中节点就导出:ok: false + 原因,不抛异常', exEmpty.ok === false && /没有要导出的节点/.test(String(exEmpty.reason)) && exEmpty.points.length === 0 && exEmpty.image === null, exEmpty.reason)
|
|
2755
2839
|
check('导出失败时点 / 线 / 下标都回空数组', exEmpty.pointIndexes.length === 0 && exEmpty.lineIndexes.length === 0 && exEmpty.box === null, JSON.stringify({ points: exEmpty.pointIndexes, lines: exEmpty.lineIndexes, box: exEmpty.box }))
|
|
2756
|
-
exView.
|
|
2840
|
+
exView.selectFeatures(['ex-a', 'ex-b'])
|
|
2757
2841
|
const exCamera = exView.getCamera()
|
|
2758
2842
|
const exVisible = exView.listPoints({ visibleOnly: true }).length
|
|
2759
2843
|
const exData = await exView.exportSelection({ format: 'data', withLines: true })
|
|
@@ -2922,14 +3006,14 @@
|
|
|
2922
3006
|
rdView.on('selectChange', payload => rdSelects.push(payload))
|
|
2923
3007
|
await rdClick(rdView.project(0, 0), { ctrlKey: true })
|
|
2924
3008
|
check('Ctrl + 点已选中的节点:多选取反走 onSelectChange(count 0),不经过 onClick', rdSelects.length === 1 && rdSelects[0].count === 0 && rdSelects[0].items.length === 0 && rdClicks.length === 2 && rdView.getSelectionData().count === 0, JSON.stringify({ selects: rdSelects.length, clicks: rdClicks.length, count: rdSelects[0]?.count }))
|
|
2925
|
-
rdView.
|
|
3009
|
+
rdView.selectFeatures(['rd-a', 'rd-b'])
|
|
2926
3010
|
await rdRightClick(rdView.project(320, 0))
|
|
2927
3011
|
check('右键落在没选中的节点上:selected / wasSelected 都是 false,selection 照旧给整份选中集', rdMenus[0]?.type === 'point' && rdMenus[0]?.item?.id === 'rd-c' && rdMenus[0]?.selected === false && rdMenus[0]?.wasSelected === false && rdMenus[0]?.selection?.count === 2, JSON.stringify({ id: rdMenus[0]?.item?.id, selected: rdMenus[0]?.selected, count: rdMenus[0]?.selection?.count }))
|
|
2928
3012
|
await rdRightClick(rdView.project(0, 0))
|
|
2929
3013
|
check('右键落在已选中的节点上:wasSelected true(据此决定要不要保住整份多选)', rdMenus[1]?.selected === true && rdMenus[1]?.wasSelected === true && rdMenus[1]?.selection?.count === 2, JSON.stringify({ selected: rdMenus[1]?.selected, was: rdMenus[1]?.wasSelected }))
|
|
2930
3014
|
const rdPointInfo = rdView.getSelectionData()
|
|
2931
3015
|
check('getSelectionData 给读数(ids / count / 各类型条数)', rdPointInfo.count === 2 && rdPointInfo.pointCount === 2 && rdPointInfo.lineCount === 0 && rdPointInfo.channelCount === 0 && rdPointInfo.ids.join(',') === 'rd-a,rd-b', JSON.stringify(rdPointInfo))
|
|
2932
|
-
rdView.
|
|
3016
|
+
rdView.selectFeatures(['rd-1', 'rd-2'])
|
|
2933
3017
|
const rdInfo = rdView.getSelectionData()
|
|
2934
3018
|
check('getSelectionData:选中一条通道的两回线时 lines 两条、channels 去重成一个', rdInfo.count === 2 && rdInfo.lineCount === 2 && rdInfo.channelCount === 1 && rdInfo.lines.length === 2 && rdInfo.channels.length === 1 && rdInfo.channels[0].id === 'rd-ch' && rdInfo.lineIndexes.length === 2 && rdInfo.items.length === 2 && rdInfo.items[0].item === rdInfo.lines[0], JSON.stringify({ channels: rdInfo.channels.map(item => item.id), lineIndexes: rdInfo.lineIndexes }))
|
|
2935
3019
|
|
|
@@ -2960,7 +3044,8 @@
|
|
|
2960
3044
|
channels: csmBuilt.channels,
|
|
2961
3045
|
fitPadding: 60,
|
|
2962
3046
|
showGrid: false,
|
|
2963
|
-
//
|
|
3047
|
+
// 点回线摊开选中集:先关拐点编辑,Ctrl 点击才能测「整批取反」;加点那一段再打开
|
|
3048
|
+
waypointEditable: false,
|
|
2964
3049
|
features: { select: true }
|
|
2965
3050
|
})
|
|
2966
3051
|
await settle(140)
|
|
@@ -3001,9 +3086,99 @@
|
|
|
3001
3086
|
// 通道外的线路没有分组可摊,照旧只选它自己
|
|
3002
3087
|
await csmClick(csmView.project(100, 160))
|
|
3003
3088
|
check('通道外的线路不受选中模式影响(还是只选自己)', csmIds() === 'csm-solo', csmIds())
|
|
3089
|
+
// Ctrl + 点击给通道回线加点:加完后的选中 / 高亮范围与普通单击共用 channelSelectMode
|
|
3090
|
+
csmView.updateOptions({ waypointEditable: true })
|
|
3091
|
+
await settle(60)
|
|
3092
|
+
const csmGroupSkeleton = () => csmView.getChannelGroups('csm-ch').find(group => String(group.id) === 'csm-g2')?.skeleton.length ?? 0
|
|
3093
|
+
const csmAddBefore = csmGroupSkeleton()
|
|
3094
|
+
csmView.updateOptions({ channelSelectMode: 'line' })
|
|
3095
|
+
await csmClick(csmView.project(50, 60), { ctrlKey: true })
|
|
3096
|
+
const csmLineAdd = csmView.getSelectionData()
|
|
3097
|
+
check('Ctrl + 点击加点(line):只高亮被加点的那一条回线', csmLineAdd.lineCount === 1 && csmGroupSkeleton() === csmAddBefore + 1 && csmIds() !== 'csm-2,csm-3' && csmIds() !== 'csm-1,csm-2,csm-3', JSON.stringify({ ids: csmIds(), waypoints: csmGroupSkeleton() }))
|
|
3098
|
+
csmView.updateOptions({ channelSelectMode: 'group' })
|
|
3099
|
+
await csmClick(csmView.project(100, 60), { ctrlKey: true })
|
|
3100
|
+
check('Ctrl + 点击加点(group):加完连所在分组一起高亮', csmIds() === 'csm-2,csm-3' && csmGroupSkeleton() === csmAddBefore + 2, JSON.stringify({ ids: csmIds(), waypoints: csmGroupSkeleton() }))
|
|
3101
|
+
csmView.updateOptions({ channelSelectMode: 'channel' })
|
|
3102
|
+
await csmClick(csmView.project(150, 60), { ctrlKey: true })
|
|
3103
|
+
check('Ctrl + 点击加点(channel):加完整条通道一起高亮', csmIds() === 'csm-1,csm-2,csm-3' && csmView.getSelectionData().channelCount === 1 && csmGroupSkeleton() === csmAddBefore + 3, JSON.stringify({ ids: csmIds(), waypoints: csmGroupSkeleton() }))
|
|
3004
3104
|
csmView.destroy()
|
|
3005
3105
|
csmHost.remove()
|
|
3006
3106
|
|
|
3107
|
+
/* 分组拐点 vertex 拖动:只平移当前分组同一 pathIndex 的顶点,其它拐点与分组不动 */
|
|
3108
|
+
const vwdHost = document.createElement('div')
|
|
3109
|
+
vwdHost.style.cssText = 'width:560px;height:360px'
|
|
3110
|
+
document.body.appendChild(vwdHost)
|
|
3111
|
+
const vwdView = createGisView(vwdHost, {
|
|
3112
|
+
points: [{ id: 'vwd-a', x: 0, y: 0 }, { id: 'vwd-b', x: 360, y: 0 }],
|
|
3113
|
+
lines: [
|
|
3114
|
+
{ id: 'vwd-1', from: 'vwd-a', to: 'vwd-b' },
|
|
3115
|
+
{ id: 'vwd-2', from: 'vwd-a', to: 'vwd-b' },
|
|
3116
|
+
{ id: 'vwd-3', from: 'vwd-a', to: 'vwd-b' },
|
|
3117
|
+
{ id: 'vwd-4', from: 'vwd-a', to: 'vwd-b' }
|
|
3118
|
+
],
|
|
3119
|
+
channels: [{
|
|
3120
|
+
id: 'vwd-ch',
|
|
3121
|
+
from: 'vwd-a',
|
|
3122
|
+
to: 'vwd-b',
|
|
3123
|
+
groups: [
|
|
3124
|
+
{ id: 'vwd-g1', lineIds: ['vwd-1', 'vwd-2'], path: [[100, 80], [260, 80]] },
|
|
3125
|
+
{ id: 'vwd-g2', lineIds: ['vwd-3', 'vwd-4'], path: [[100, -80], [260, -80]], waypointOffsets: { '1': { x: 0, y: 6 } } }
|
|
3126
|
+
]
|
|
3127
|
+
}],
|
|
3128
|
+
waypointEditable: true,
|
|
3129
|
+
waypointVisibility: 'always',
|
|
3130
|
+
channelWaypointDragMode: 'vertex',
|
|
3131
|
+
fitPadding: 70,
|
|
3132
|
+
showGrid: false
|
|
3133
|
+
})
|
|
3134
|
+
await settle(160)
|
|
3135
|
+
const vwdSend = (type, screen, extra = {}) => {
|
|
3136
|
+
const box = vwdView.canvas.getBoundingClientRect()
|
|
3137
|
+
vwdView.canvas.dispatchEvent(new PointerEvent(type, {
|
|
3138
|
+
bubbles: true,
|
|
3139
|
+
cancelable: true,
|
|
3140
|
+
pointerId: 79,
|
|
3141
|
+
pointerType: 'mouse',
|
|
3142
|
+
button: extra.button ?? 0,
|
|
3143
|
+
buttons: extra.buttons ?? (type === 'pointerup' ? 0 : 1),
|
|
3144
|
+
clientX: box.left + screen[0],
|
|
3145
|
+
clientY: box.top + screen[1],
|
|
3146
|
+
...extra
|
|
3147
|
+
}))
|
|
3148
|
+
}
|
|
3149
|
+
const vwdBefore = vwdView.getChannelGroupWaypoints('vwd-ch', 'vwd-g1')
|
|
3150
|
+
const vwdOtherBefore = vwdView.getChannelGroupWaypoints('vwd-ch', 'vwd-g2')
|
|
3151
|
+
const vwdWorldStart = vwdBefore.lines[0].waypoints[0]
|
|
3152
|
+
const vwdWorldTarget = [vwdWorldStart[0] + 42, vwdWorldStart[1] + 30]
|
|
3153
|
+
const vwdStart = vwdView.project(vwdWorldStart[0], vwdWorldStart[1])
|
|
3154
|
+
const vwdTarget = vwdView.project(vwdWorldTarget[0], vwdWorldTarget[1])
|
|
3155
|
+
const vwdObjectOffset = vwdView.getChannelGroups('vwd-ch').find(group => String(group.id) === 'vwd-g2')?.waypointOffsets?.['1']
|
|
3156
|
+
const vwdObjectLane = vwdView.getChannels().find(group => String(group.id) === 'vwd-g2')?.offsets?.[0] ?? NaN
|
|
3157
|
+
check('分组拐点偏移:{ x, y } 声明归一成 [dx, dy] 并参与实际路由', Array.isArray(vwdObjectOffset) && Math.abs(vwdObjectOffset[0]) <= 1e-3 && Math.abs(vwdObjectOffset[1] - 6) <= 1e-3 && Math.abs(vwdOtherBefore.lines[0].waypoints[0][1] - (vwdOtherBefore.skeleton[1][1] + vwdObjectLane + 6)) <= 1e-3, JSON.stringify({ offset: vwdObjectOffset, lane: vwdObjectLane, skeleton: vwdOtherBefore.skeleton, line: vwdOtherBefore.lines[0].waypoints[0] }))
|
|
3158
|
+
vwdSend('pointermove', vwdStart, { buttons: 0 })
|
|
3159
|
+
vwdSend('pointerdown', vwdStart, { buttons: 1 })
|
|
3160
|
+
vwdSend('pointermove', [(vwdStart[0] + vwdTarget[0]) / 2, (vwdStart[1] + vwdTarget[1]) / 2], { buttons: 1 })
|
|
3161
|
+
vwdSend('pointermove', vwdTarget, { buttons: 1 })
|
|
3162
|
+
vwdSend('pointerup', vwdTarget, { buttons: 0 })
|
|
3163
|
+
await settle(180)
|
|
3164
|
+
const vwdAfter = vwdView.getChannelGroupWaypoints('vwd-ch', 'vwd-g1')
|
|
3165
|
+
const vwdOtherAfter = vwdView.getChannelGroupWaypoints('vwd-ch', 'vwd-g2')
|
|
3166
|
+
const vwdClose = (left, right, tolerance = 1e-3) => Math.abs(left - right) <= tolerance
|
|
3167
|
+
const vwdMoved = vwdAfter.lines[0].waypoints[0]
|
|
3168
|
+
const vwdPartnerBefore = vwdBefore.lines[1].waypoints[0]
|
|
3169
|
+
const vwdPartnerAfter = vwdAfter.lines[1].waypoints[0]
|
|
3170
|
+
const vwdAdjacent = vwdBefore.lines[0].waypoints[1]
|
|
3171
|
+
const vwdAdjacentAfter = vwdAfter.lines[0].waypoints[1]
|
|
3172
|
+
const vwdRoute = vwdView.getParallelGroups().find(group => String(group.group_id) === 'vwd-g1')
|
|
3173
|
+
const vwdOffset = vwdAfter.waypointOffsets['1']
|
|
3174
|
+
const vwdDeclared = vwdView.getOptions().channels[0].groups.find(group => String(group.id) === 'vwd-g1')?.waypointOffsets
|
|
3175
|
+
check('分组拐点 vertex:目标分组的同序号拐点按世界增量平移', vwdClose(vwdMoved[0] - vwdWorldStart[0], 42) && vwdClose(vwdMoved[1] - vwdWorldStart[1], 30) && vwdClose(vwdPartnerAfter[0] - vwdPartnerBefore[0], 42) && vwdClose(vwdPartnerAfter[1] - vwdPartnerBefore[1], 30), JSON.stringify({ moved: vwdMoved, partnerDelta: [vwdPartnerAfter[0] - vwdPartnerBefore[0], vwdPartnerAfter[1] - vwdPartnerBefore[1]] }))
|
|
3176
|
+
check('分组拐点 vertex:相邻拐点与其它分组一个都不动', vwdClose(vwdAdjacentAfter[0], vwdAdjacent[0]) && vwdClose(vwdAdjacentAfter[1], vwdAdjacent[1]) && JSON.stringify(vwdOtherAfter.lines.map(line => line.path)) === JSON.stringify(vwdOtherBefore.lines.map(line => line.path)), JSON.stringify({ adjacent: vwdAdjacentAfter, other: vwdOtherAfter.lines[0].waypoints }))
|
|
3177
|
+
check('分组拐点 vertex:只写 waypointOffsets,不改分组公共 path', vwdOffset && Object.keys(vwdAfter.waypointOffsets).join(',') === '1' && JSON.stringify(vwdAfter.path) === JSON.stringify(vwdBefore.path) && vwdDeclared && vwdClose(vwdDeclared['1']?.[0] ?? NaN, vwdOffset[0]) && vwdClose(vwdDeclared['1']?.[1] ?? NaN, vwdOffset[1]), JSON.stringify({ offsets: vwdAfter.waypointOffsets, declared: vwdDeclared, path: vwdAfter.path }))
|
|
3178
|
+
check('分组拐点 vertex:parallel_groups.group_route 带刚性偏移(导出不回到旧骨架)', Array.isArray(vwdOffset) && vwdClose(vwdRoute?.group_route?.[1]?.[0] ?? NaN, vwdBefore.skeleton[1][0] + vwdOffset[0]) && vwdClose(vwdRoute?.group_route?.[1]?.[1] ?? NaN, vwdBefore.skeleton[1][1] + vwdOffset[1]), JSON.stringify(vwdRoute?.group_route ?? null))
|
|
3179
|
+
vwdView.destroy()
|
|
3180
|
+
vwdHost.remove()
|
|
3181
|
+
|
|
3007
3182
|
/* 删除(Delete 键):先出载荷说清「删什么 / 连带什么 / 能按哪些范围删」,确认之后才动数据 */
|
|
3008
3183
|
const rmHost = document.createElement('div')
|
|
3009
3184
|
rmHost.style.cssText = 'width:520px;height:320px'
|
|
@@ -3039,7 +3214,7 @@
|
|
|
3039
3214
|
const rmKey = (key, extra = {}) => {
|
|
3040
3215
|
rmView.el.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true, ...extra }))
|
|
3041
3216
|
}
|
|
3042
|
-
rmView.
|
|
3217
|
+
rmView.selectFeatures(['rm-2'])
|
|
3043
3218
|
rmKey('Delete')
|
|
3044
3219
|
await settle(40)
|
|
3045
3220
|
const rmPlan = rmView.getRemoveRequest()
|
|
@@ -3050,13 +3225,13 @@
|
|
|
3050
3225
|
rmView.cancelRemove()
|
|
3051
3226
|
await settle(40)
|
|
3052
3227
|
check('取消删除:数据不动、事件给 phase: cancel、待确认的载荷收掉', rmView.getData().lines.length === 4 && rmEvents.length === 2 && rmEvents[1].phase === 'cancel' && rmEvents[1].cancelled === true && rmView.getRemoveRequest() === null, JSON.stringify(rmEvents.map(item => item.phase)))
|
|
3053
|
-
rmView.
|
|
3228
|
+
rmView.selectFeatures(['rm-2'])
|
|
3054
3229
|
rmKey('Delete')
|
|
3055
3230
|
await settle(40)
|
|
3056
3231
|
rmView.resolveRemove('channels')
|
|
3057
3232
|
await settle(80)
|
|
3058
3233
|
check('按「整条通道」确认:三条回线一起删、通道声明也摘掉,通道外的线路留着', rmView.getData().lines.length === 1 && rmView.getData().lines[0].id === 'rm-solo' && rmView.getOptions().channels.length === 0 && rmEvents.at(-1).phase === 'remove' && rmEvents.at(-1).scope === 'channels' && rmEvents.at(-1).result.lineIds.length === 3, JSON.stringify({ lines: rmView.getData().lines.map(item => item.id), channels: rmView.getOptions().channels.length }))
|
|
3059
|
-
rmView.
|
|
3234
|
+
rmView.selectFeatures(['rm-a'])
|
|
3060
3235
|
rmKey('Delete')
|
|
3061
3236
|
await settle(40)
|
|
3062
3237
|
const rmPointPlan = rmView.getRemoveRequest()
|
|
@@ -3068,7 +3243,7 @@
|
|
|
3068
3243
|
await settle(80)
|
|
3069
3244
|
check('撤销一步:节点与连带删掉的线路一起回来', rmView.getData().points.length === 3 && rmView.getData().lines.length === 1 && rmView.getData().lines[0].id === 'rm-solo', JSON.stringify({ points: rmView.getData().points.length, lines: rmView.getData().lines.map(item => item.id) }))
|
|
3070
3245
|
rmView.updateOptions({ shortcuts: { remove: 'shift+delete' } })
|
|
3071
|
-
rmView.
|
|
3246
|
+
rmView.selectFeatures(['rm-solo'])
|
|
3072
3247
|
rmKey('Delete')
|
|
3073
3248
|
await settle(40)
|
|
3074
3249
|
check('快捷键单独配:单独的 Delete 不再触发删除', rmView.getRemoveRequest() === null && rmView.getData().lines.length === 1, JSON.stringify(rmView.getRemoveRequest()))
|
|
@@ -3077,7 +3252,7 @@
|
|
|
3077
3252
|
check('配成 Shift+Delete:按住 Shift 再按才出载荷', Boolean(rmView.getRemoveRequest()) && rmView.getRemoveRequest().counts.lines === 1, JSON.stringify(rmView.getRemoveRequest()?.counts))
|
|
3078
3253
|
rmView.cancelRemove()
|
|
3079
3254
|
/* 待确认期间数据换过:确认时整条请求作废(不按旧下标删),事件给 phase: cancel + invalidated */
|
|
3080
|
-
rmView.
|
|
3255
|
+
rmView.selectFeatures(['rm-solo'])
|
|
3081
3256
|
rmKey('Delete', { shiftKey: true })
|
|
3082
3257
|
await settle(40)
|
|
3083
3258
|
rmView.updateData([{ id: 'rm-a', x: 0, y: 0 }, { id: 'rm-b', x: 200, y: 0 }, { id: 'rm-c', x: 100, y: -80 }, { id: 'rm-d', x: 40, y: 40 }], [{ id: 'rm-solo', from: 'rm-a', to: 'rm-c' }])
|
|
@@ -3099,44 +3274,44 @@
|
|
|
3099
3274
|
showGrid: false
|
|
3100
3275
|
})
|
|
3101
3276
|
await settle(120)
|
|
3102
|
-
rmOffView.
|
|
3277
|
+
rmOffView.selectFeatures(['rm-off-line'])
|
|
3103
3278
|
rmOffView.el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete', bubbles: true, cancelable: true }))
|
|
3104
3279
|
await settle(40)
|
|
3105
3280
|
check('开关关着(features.remove 缺省):Delete 不出删除载荷,数据不动', rmOffView.getRemoveRequest() === null && rmOffView.getData().lines.length === 1, JSON.stringify({ lines: rmOffView.getData().lines.length }))
|
|
3106
3281
|
rmOffView.destroy()
|
|
3107
3282
|
rmOffHost.remove()
|
|
3108
3283
|
|
|
3109
|
-
const rdStations = rdView.
|
|
3110
|
-
check('
|
|
3111
|
-
check('
|
|
3112
|
-
const rdLean = rdView.
|
|
3113
|
-
check('
|
|
3284
|
+
const rdStations = rdView.exportData({ format: 'new', parts: ['station'] })
|
|
3285
|
+
check('exportData(format: new, parts: station):new.json 的 station_data 口径(列名 / 母线清单 / 业务列原样透传)', rdStations.ok === true && rdStations.station_data.length === 3 && rdStations.station_data[0].station_id === 'rd-a' && rdStations.station_data[0].station_name === '甲站' && rdStations.station_data[0].vn_kv === 220 && rdStations.station_data[0].bus_id_list.join(',') === 'b-1,b-2' && Array.isArray(rdStations.station_data[0].trafo_id_list) && rdStations.station_data[0].max_loading_rate === 0.8, JSON.stringify(rdStations.station_data[0]).slice(0, 200))
|
|
3286
|
+
check('exportData(format: new, parts: station):四至范围与 show_station_list / show_station_data 一起给', Number.isFinite(rdStations.max_lat) && Number.isFinite(rdStations.min_lon) && rdStations.show_station_list.join(',') === 'b-1,b-2,b-3' && rdStations.show_station_data[0].join(',') === 'b-1,b-2', JSON.stringify({ box: [rdStations.min_lon, rdStations.min_lat, rdStations.max_lon, rdStations.max_lat], list: rdStations.show_station_list }))
|
|
3287
|
+
const rdLean = rdView.exportData({ format: 'new', parts: ['station'], points: ['rd-a'], box: false, showStation: false })
|
|
3288
|
+
check('exportData(format: new, parts: station):box / showStation 关掉就不带那几列,只留选中的这一个站', rdLean.station_data.length === 1 && !('max_lat' in rdLean) && !('show_station_list' in rdLean), JSON.stringify(Object.keys(rdLean)))
|
|
3114
3289
|
const rdCopyRow = rdStations.station_data[0]
|
|
3115
3290
|
rdCopyRow.bus_id_list.push('改一改')
|
|
3116
3291
|
check('导出的是拷贝:改导出结果不动组件里的那一份', rdView.getData().points[0].bus_id_list.length === 2, `组件里 ${rdView.getData().points[0].bus_id_list.length} 个`)
|
|
3117
|
-
const rdPoints = rdView.
|
|
3118
|
-
check('
|
|
3119
|
-
check('
|
|
3120
|
-
const rdLines = rdView.
|
|
3292
|
+
const rdPoints = rdView.exportData({ format: 'test', parts: ['station'] })
|
|
3293
|
+
check('exportData(format: test, parts: station):test.json 的 point_data 口径(同一份数据换列名,带分区列)', rdPoints.ok === true && rdPoints.point_data.length === 3 && rdPoints.point_data[0].station_id === 'rd-a' && rdPoints.point_data[0].station_name === '甲站' && rdPoints.point_data[0].lon === 0 && rdPoints.point_data[0].lat === 0 && rdPoints.counts.points === 3, JSON.stringify({ keys: Object.keys(rdPoints.point_data[0] ?? {}).length, count: rdPoints.counts.points, lon: rdPoints.point_data[0].lon, lat: rdPoints.point_data[0].lat }))
|
|
3294
|
+
check('exportData(format: test, parts: station):zone_id / zone_name 两列在(没给值就是 null)', 'zone_id' in rdPoints.point_data[0] && 'zone_name' in rdPoints.point_data[0] && rdPoints.point_data[0].bus_name_list.length === 2, JSON.stringify({ zone_id: rdPoints.point_data[0].zone_id }))
|
|
3295
|
+
const rdLines = rdView.exportData({ format: 'test', parts: ['line'] })
|
|
3121
3296
|
const rdChannelLine = rdLines.line_data.filter(row => row.channel_id === 'rd-ch')
|
|
3122
|
-
check('
|
|
3123
|
-
check('
|
|
3124
|
-
const rdChannels = rdView.
|
|
3125
|
-
check('
|
|
3126
|
-
const rdChannelLean = rdView.
|
|
3127
|
-
check('
|
|
3128
|
-
const rdGroupsOff = rdView.
|
|
3129
|
-
check('
|
|
3297
|
+
check('exportData(format: test, parts: line):通道回线带上 channel_id / channel_name / parallel_group_id 与第几回', rdLines.ok === true && rdLines.line_data.length === 3 && rdChannelLine.length === 2 && rdChannelLine[0].parallel_group_id !== undefined && rdChannelLine[0].index === 0 && rdChannelLine[1].index === 1 && rdChannelLine.every(row => Array.isArray(row.coords_list) && row.coords_list.length >= 2), JSON.stringify(rdChannelLine.map(row => ({ id: row.line_id, group: row.parallel_group_id, index: row.index }))))
|
|
3298
|
+
check('exportData(format: test, parts: line):不在通道里的线路 channel_id 给 null,折线就是画出来的那一份', rdLines.line_data.some(row => row.line_id === 'rd-solo' && row.channel_id === null && row.coords_list.length === 3), JSON.stringify(rdLines.line_data.map(row => ({ id: row.line_id, channel: row.channel_id, points: row.coords_list.length }))))
|
|
3299
|
+
const rdChannels = rdView.exportData({ format: 'new', parts: ['channel'] })
|
|
3300
|
+
check('exportData(format: new, parts: channel):一条通道一行,回线在 line_data 里、路由在 coords 里', rdChannels.ok === true && rdChannels.channel_data.length === 1 && rdChannels.counts.channels === 1 && rdChannels.counts.lines === 2 && rdChannels.channel_data[0].line_data.length === 2 && Array.isArray(rdChannels.channel_data[0].coords) && rdChannels.channel_data[0].coords.length >= 2, JSON.stringify({ channels: rdChannels.counts.channels, lines: rdChannels.counts.lines, coords: rdChannels.channel_data[0].coords.length }))
|
|
3301
|
+
const rdChannelLean = rdView.exportData({ format: 'new', parts: ['channel'], channels: [0], lines: ['rd-1'] })
|
|
3302
|
+
check('exportData(format: new, parts: channel):options.lines 能把通道里的 line_data 收到这几条回线', rdChannelLean.channel_data[0].line_data.length === 1 && rdChannelLean.channel_data[0].line_data[0].line_id === 'rd-1', JSON.stringify(rdChannelLean.channel_data[0].line_data.map(row => row.line_id)))
|
|
3303
|
+
const rdGroupsOff = rdView.exportData({ format: 'test', parts: ['waypoint'] })
|
|
3304
|
+
check('exportData(format: test, parts: waypoint):没加过拐点时是空的一份(与通道数据各走各的)', rdGroupsOff.ok === true && rdGroupsOff.parallel_groups.length === 0 && rdGroupsOff.counts.parallelGroups === 0, JSON.stringify(rdGroupsOff.counts))
|
|
3130
3305
|
rdView.addWaypoint('rd-1', [80, 40])
|
|
3131
3306
|
await settle(80)
|
|
3132
|
-
const rdGroupsOn = rdView.
|
|
3133
|
-
check('加了拐点之后:parallel_groups 单独给出这一组(group_id / member_line_ids / group_route 对得上)', rdGroupsOn.parallel_groups.length === 1 && rdGroupsOn.counts.parallelGroups === 1 &&
|
|
3134
|
-
const rdGroupsAll = rdView.
|
|
3135
|
-
check('
|
|
3136
|
-
const rdOneGroup = rdView.
|
|
3137
|
-
check('
|
|
3138
|
-
const rdNoGroup = rdView.
|
|
3139
|
-
check('
|
|
3307
|
+
const rdGroupsOn = rdView.exportData({ format: 'test', parts: ['waypoint'] })
|
|
3308
|
+
check('加了拐点之后:parallel_groups 单独给出这一组(group_id / member_line_ids / group_route 对得上)', rdGroupsOn.parallel_groups.length === 1 && rdGroupsOn.counts.parallelGroups === 1 && Array.isArray(rdGroupsOn.parallel_groups[0].group_route) && rdGroupsOn.parallel_groups[0].group_route.length >= 3 && rdGroupsOn.parallel_groups[0].member_line_ids.length >= 1, JSON.stringify(rdGroupsOn.parallel_groups[0]).slice(0, 200))
|
|
3309
|
+
const rdGroupsAll = rdView.exportData({ format: 'test', parts: ['waypoint'], all: true })
|
|
3310
|
+
check('exportData(format: test, parts: waypoint):all 为 true 时把没编辑过的分组 / 带折线的线路也列出来', rdGroupsAll.parallel_groups.some(group => String(group.group_id) === 'rd-solo'), JSON.stringify(rdGroupsAll.parallel_groups.map(group => group.group_id)))
|
|
3311
|
+
const rdOneGroup = rdView.exportData({ format: 'test', parts: ['waypoint'], channels: ['rd-ch'] })
|
|
3312
|
+
check('exportData(format: test, parts: waypoint):channels 只留这条通道那一份', rdOneGroup.parallel_groups.length === 1 && rdOneGroup.parallel_groups[0].group_route.length >= 3, JSON.stringify(rdOneGroup.parallel_groups.map(group => group.group_id)))
|
|
3313
|
+
const rdNoGroup = rdView.exportData({ format: 'test', parts: ['waypoint'], channels: ['没有这条通道'] })
|
|
3314
|
+
check('exportData(format: test, parts: waypoint):通道 id 解不出来时给空表 + 一条 warnings', rdNoGroup.parallel_groups.length === 0 && rdNoGroup.warnings.length === 1, JSON.stringify(rdNoGroup.warnings))
|
|
3140
3315
|
const rdFour = rdView.exportData({ format: 'view' })
|
|
3141
3316
|
check("exportData format: view(缺省):points / lines / channels / parallelGroups 四份分开", rdFour.ok === true && rdFour.points.length === 3 && rdFour.lines.length === 3 && rdFour.channels.length === 1 && Array.isArray(rdFour.parallelGroups) && rdFour.counts.points === 3 && rdFour.counts.lines === 3 && rdFour.counts.channels === 1, JSON.stringify(rdFour.counts))
|
|
3142
3317
|
const rdNew = rdView.exportData({ format: 'new' })
|
|
@@ -3145,6 +3320,32 @@
|
|
|
3145
3320
|
check("exportData format: test:test.json 口径的整份(point_data + line_data + parallel_groups)", rdTest.point_data.length === 3 && rdTest.line_data.length === 3 && Array.isArray(rdTest.switch_data) && rdTest.parallel_groups.length >= 1 && rdTest.counts.points === 3, JSON.stringify(rdTest.counts))
|
|
3146
3321
|
const rdFiltered = rdView.exportData({ format: 'view', points: ['rd-a'], lines: ['rd-solo'], channels: ['rd-ch'] })
|
|
3147
3322
|
check('exportData:三个筛选取一部分时只导这几个(id / 下标 / { id } 都认)', rdFiltered.points.length === 1 && rdFiltered.lines.length === 1 && rdFiltered.channels.length === 1 && rdFiltered.lines[0].line_id === 'rd-solo', JSON.stringify(rdFiltered.counts))
|
|
3323
|
+
const rdStationOnly = rdView.exportData({ format: 'view', parts: ['station'] })
|
|
3324
|
+
check('exportData:parts 只留场站时不生成线路 / 通道 / 拐点', rdStationOnly.points.length === 3 && rdStationOnly.lines === undefined && rdStationOnly.channels === undefined && rdStationOnly.parallelGroups === undefined && rdStationOnly.counts.lines === 0 && rdStationOnly.counts.channels === 0, JSON.stringify(rdStationOnly.counts))
|
|
3325
|
+
const rdViewportUnified = rdView.exportData({ scope: 'viewport', margin: 0 })
|
|
3326
|
+
const rdViewportRepeat = rdView.exportData({ scope: 'viewport' })
|
|
3327
|
+
check('exportData(scope: viewport) 可重复且带 scope / viewRect / margin', JSON.stringify(rdViewportUnified) === JSON.stringify(rdViewportRepeat) && rdViewportUnified.scope === 'viewport' && rdViewportUnified.viewRect && rdViewportUnified.margin === 0, JSON.stringify({ scope: rdViewportUnified.scope, rect: rdViewportUnified.viewRect, margin: rdViewportUnified.margin }))
|
|
3328
|
+
const rdSelectionUnified = rdView.exportData({ scope: 'selection', points: ['rd-a', 'rd-b'], parts: ['station', 'channel'] })
|
|
3329
|
+
const rdSelectionRepeat = rdView.exportData({ scope: 'selection', points: ['rd-a', 'rd-b'], parts: ['station', 'channel'] })
|
|
3330
|
+
check('exportData(scope: selection) 可重复且把下标 / parts 一并回显', JSON.stringify(rdSelectionUnified) === JSON.stringify(rdSelectionRepeat) && rdSelectionUnified.scope === 'selection' && rdSelectionUnified.pointIndexes.length === 2 && Array.isArray(rdSelectionUnified.channelIndexes), JSON.stringify({ scope: rdSelectionUnified.scope, points: rdSelectionUnified.pointIndexes, channels: rdSelectionUnified.channelIndexes }))
|
|
3331
|
+
const rdRemovedAliases = ['exportStationData', 'exportPointData', 'exportLineData', 'exportChannelData', 'exportParallelGroups', 'exportViewportData', 'exportSelectionData']
|
|
3332
|
+
check('七个旧分项导出别名已经从实例上删除,只保留统一 exportData', rdRemovedAliases.every(name => rdView[name] === undefined) && typeof rdView.exportData === 'function', rdRemovedAliases.map(name => `${name}:${typeof rdView[name]}`).join(' / '))
|
|
3333
|
+
const rdRemovedThinWrappers = ['setEffects', 'setPointSize', 'setPointSymbol', 'setPointColor', 'setLineWidth', 'setLineColor', 'setChannelWidth', 'setChannelNodeWidth', 'setChannelInset', 'setChannelTangentInset', 'setChannelName', 'clearFilter', 'clearDropPreview']
|
|
3334
|
+
check('十三个重复的单项 / 清空快捷方法已经从实例上删除,统一走 update* / setFilter(null) / setDropPreview(null)', rdRemovedThinWrappers.every(name => rdView[name] === undefined) && typeof rdView.updatePoint === 'function' && typeof rdView.updateLine === 'function' && typeof rdView.updateChannel === 'function' && typeof rdView.updateOptions === 'function' && typeof rdView.setFilter === 'function' && typeof rdView.setDropPreview === 'function', rdRemovedThinWrappers.map(name => `${name}:${typeof rdView[name]}`).join(' / '))
|
|
3335
|
+
const rdInputViewport = rdView.exportData({ scope: 'viewport', format: 'input' })
|
|
3336
|
+
check('format: input 的视口结果保持回灌形状,不额外夹带 scope / viewRect / margin', Array.isArray(rdInputViewport.points) && !('scope' in rdInputViewport) && !('viewRect' in rdInputViewport) && !('margin' in rdInputViewport), Object.keys(rdInputViewport).join(','))
|
|
3337
|
+
const rdChannelLeanUnified = rdView.exportData({ format: 'new', parts: ['channel'], channels: ['rd-ch'], lines: ['rd-1'] })
|
|
3338
|
+
check('exportData:统一入口下通道的 line_data 仍按 lines 收窄(只留选中的回线)', rdChannelLeanUnified.channel_data.length === 1 && rdChannelLeanUnified.channel_data[0].line_data.length === 1 && rdChannelLeanUnified.channel_data[0].line_data[0].line_id === 'rd-1', JSON.stringify(rdChannelLeanUnified.channel_data[0]?.line_data?.map(row => row.line_id)))
|
|
3339
|
+
const rdFull = rdView.exportData({ format: 'full', scope: 'viewport' })
|
|
3340
|
+
check('exportData:format full 始终返回标准文档本身,不夹带 scope / viewRect 读数', rdFull.format === 'gis-view' && !('scope' in rdFull) && !('viewRect' in rdFull) && !('counts' in rdFull), Object.keys(rdFull).join(','))
|
|
3341
|
+
let rdScopeThrew = ''
|
|
3342
|
+
try {
|
|
3343
|
+
rdView.exportData({ scope: 'window' })
|
|
3344
|
+
}
|
|
3345
|
+
catch (error) {
|
|
3346
|
+
rdScopeThrew = String(error && error.message ? error.message : error)
|
|
3347
|
+
}
|
|
3348
|
+
check('exportData:scope 写错直接抛异常(不静默按全量导)', /viewport/.test(rdScopeThrew) && /selection/.test(rdScopeThrew) && /window/.test(rdScopeThrew), rdScopeThrew)
|
|
3148
3349
|
const rdBad = rdView.exportData({ format: 'test', lines: ['没有这条线'] })
|
|
3149
3350
|
check('解不出的引用记进 warnings,其余照常导出(ok 仍是 true)', rdBad.ok === true && rdBad.line_data.length === 0 && rdBad.warnings.length === 1 && /没找到/.test(String(rdBad.warnings[0])), JSON.stringify(rdBad.warnings))
|
|
3150
3351
|
let rdThrew = ''
|
|
@@ -3229,9 +3430,9 @@
|
|
|
3229
3430
|
check('selectFeatures:拾取载荷(pickAt 的结果)可以直接喂进来', apiIds() === 'api-a', apiIds())
|
|
3230
3431
|
apiView.selectFeatures(apiView.search({ keyword: 'api-l1' }).hits[0])
|
|
3231
3432
|
check('selectFeatures:搜索命中(kind: line)可以直接喂进来', apiIds() === 'api-l1', apiIds())
|
|
3232
|
-
apiView.
|
|
3433
|
+
apiView.selectFeatures(null)
|
|
3233
3434
|
const apiEmpty = apiView.getSelectionData()
|
|
3234
|
-
check('
|
|
3435
|
+
check('selectFeatures(null) 清空选中,getSelectionData 给空的一份(bounds 是 null)', apiEmpty.count === 0 && apiEmpty.ids.length === 0 && apiEmpty.bounds === null, JSON.stringify(apiEmpty.count))
|
|
3235
3436
|
apiSend('pointermove', apiPointScreen)
|
|
3236
3437
|
apiSend('pointerdown', apiPointScreen, { button: 0, buttons: 1 })
|
|
3237
3438
|
apiSend('pointerup', apiPointScreen, { button: 0, buttons: 0 })
|
|
@@ -3282,7 +3483,7 @@
|
|
|
3282
3483
|
check('focusOn(一批, { select: true }):被聚焦的这一批同时选中(整份选中集换成这三个)', apiView.getSelectionData().ids.join(',') === 'api-a,api-b,api-c' && apiBeforeFocusSelect === 'api-a,api-b,api-c' && apiView.getSelectionData().count === 3, `${apiBeforeFocusSelect} -> ${apiView.getSelectionData().ids.join(',')}`)
|
|
3283
3484
|
apiView.focusOn('api-a', { select: true, mode: 'remove' })
|
|
3284
3485
|
check("focusOn(..., { select: true, mode: 'remove' }):只把聚焦的这个从选中集里去掉", apiView.getSelectionData().ids.join(',') === 'api-b,api-c', apiView.getSelectionData().ids.join(','))
|
|
3285
|
-
apiView.
|
|
3486
|
+
apiView.selectFeatures(null)
|
|
3286
3487
|
const apiAreaScale = apiView.getCamera().scale
|
|
3287
3488
|
apiView.focusOn('api-b', { maxZoom: 12 })
|
|
3288
3489
|
const apiZoomCamera = apiView.getCamera()
|
|
@@ -3433,7 +3634,8 @@
|
|
|
3433
3634
|
const pluginLog = []
|
|
3434
3635
|
const pluginWarnings = []
|
|
3435
3636
|
const pluginErrors = []
|
|
3436
|
-
let optionPluginInstance = null
|
|
3637
|
+
let optionPluginInstance = null
|
|
3638
|
+
let optionPluginContext = null
|
|
3437
3639
|
const pluginView = createGisView(pluginHost, {
|
|
3438
3640
|
points: [{ id: 'pg-1', x: 0, y: 0 }],
|
|
3439
3641
|
lines: [],
|
|
@@ -3441,19 +3643,22 @@
|
|
|
3441
3643
|
height: 160,
|
|
3442
3644
|
onWarning: text => pluginWarnings.push(text),
|
|
3443
3645
|
onError: text => pluginErrors.push(text),
|
|
3444
|
-
plugins: [v => {
|
|
3646
|
+
plugins: [(v, context) => {
|
|
3445
3647
|
optionPluginInstance = v
|
|
3648
|
+
optionPluginContext = context
|
|
3446
3649
|
return { destroy: () => pluginLog.push('options') }
|
|
3447
3650
|
}]
|
|
3448
3651
|
})
|
|
3449
|
-
check('options.plugins
|
|
3450
|
-
let usePluginInstance = null
|
|
3652
|
+
check('options.plugins 拿到实例本身与带 apiVersion 的只读上下文', optionPluginInstance === pluginView && optionPluginContext?.apiVersion === 1 && optionPluginContext.view === pluginView && Object.isFrozen(optionPluginContext), JSON.stringify(optionPluginContext ? { apiVersion: optionPluginContext.apiVersion, sameView: optionPluginContext.view === pluginView } : null))
|
|
3653
|
+
let usePluginInstance = null
|
|
3654
|
+
let usePluginContext = null
|
|
3451
3655
|
let useDestroyCount = 0
|
|
3452
|
-
const disposeUse = pluginView.use(v => {
|
|
3656
|
+
const disposeUse = pluginView.use((v, context) => {
|
|
3453
3657
|
usePluginInstance = v
|
|
3658
|
+
usePluginContext = context
|
|
3454
3659
|
return { destroy: () => { useDestroyCount += 1 } }
|
|
3455
3660
|
})
|
|
3456
|
-
check('use()
|
|
3661
|
+
check('use() 立即执行,实例与带版本的上下文都和 options.plugins 一致', usePluginInstance === pluginView && usePluginContext?.apiVersion === 1 && usePluginContext.view === pluginView && Object.isFrozen(usePluginContext))
|
|
3457
3662
|
disposeUse()
|
|
3458
3663
|
disposeUse()
|
|
3459
3664
|
check('use() 返回的卸载函数跑插件的 destroy(重复调用只跑一次)', useDestroyCount === 1, String(useDestroyCount))
|
|
@@ -3664,7 +3869,7 @@
|
|
|
3664
3869
|
await edgeSleep(120)
|
|
3665
3870
|
const shiftDropFrame = await frameOf(shiftView)
|
|
3666
3871
|
check('删掉选中里的那一条:读数只剩 c,b 的位置不留选中色', shiftView.getSelectionData().ids.join(',') === 'sp-c' && !pixelIs(shiftDropFrame, shiftSpotB, 'green') && pixelIs(shiftDropFrame, shiftSpotC, 'green'), JSON.stringify({ ids: shiftView.getSelectionData().ids, b: pixelIs(shiftDropFrame, shiftSpotB, 'green'), c: pixelIs(shiftDropFrame, shiftSpotC, 'green') }))
|
|
3667
|
-
shiftView.
|
|
3872
|
+
shiftView.selectFeatures(null)
|
|
3668
3873
|
await edgeSleep(120)
|
|
3669
3874
|
const shiftClearFrame = await frameOf(shiftView)
|
|
3670
3875
|
check('删点之后取消选中:c 的选中色收掉、回到自己的底色(不会停在别人身上)', !pixelIs(shiftClearFrame, shiftSpotC, 'green') && pixelIsWhite(shiftClearFrame, shiftSpotC), JSON.stringify({ green: pixelIs(shiftClearFrame, shiftSpotC, 'green'), white: pixelIsWhite(shiftClearFrame, shiftSpotC) }))
|
|
@@ -3691,7 +3896,7 @@
|
|
|
3691
3896
|
})
|
|
3692
3897
|
await edgeSleep(120)
|
|
3693
3898
|
const lineSpot = lineShiftView.project(200, 120)
|
|
3694
|
-
lineShiftView.
|
|
3899
|
+
lineShiftView.selectFeatures('ssl2')
|
|
3695
3900
|
lineShiftView.removeLines('ssl1')
|
|
3696
3901
|
await edgeSleep(120)
|
|
3697
3902
|
const lineShiftFrame = await frameOf(lineShiftView)
|
|
@@ -3712,7 +3917,7 @@
|
|
|
3712
3917
|
})
|
|
3713
3918
|
await edgeSleep(120)
|
|
3714
3919
|
const mergeSpotC = mergeShiftView.project(280, 0)
|
|
3715
|
-
mergeShiftView.
|
|
3920
|
+
mergeShiftView.selectFeatures('mp-c')
|
|
3716
3921
|
mergeShiftView.mergePoints('mp-a', 'mp-b')
|
|
3717
3922
|
await edgeSleep(120)
|
|
3718
3923
|
const mergeShiftFrame = await frameOf(mergeShiftView)
|
|
@@ -3734,7 +3939,7 @@
|
|
|
3734
3939
|
await edgeSleep(120)
|
|
3735
3940
|
const fallbackSpotB = fallbackShiftView.project(140, 0)
|
|
3736
3941
|
const fallbackSpotC = fallbackShiftView.project(280, 0)
|
|
3737
|
-
fallbackShiftView.
|
|
3942
|
+
fallbackShiftView.selectFeatures('fp-c')
|
|
3738
3943
|
fallbackShiftView.removePoints('fp-a')
|
|
3739
3944
|
await edgeSleep(120)
|
|
3740
3945
|
const fallbackFrame = await frameOf(fallbackShiftView)
|
|
@@ -3887,11 +4092,37 @@
|
|
|
3887
4092
|
/<image[^>]+href="data:image\/png;base64/.test(symSvg),
|
|
3888
4093
|
(symSvg.match(/<image[^>]*/) ?? ['没有 <image>'])[0].slice(0, 90))
|
|
3889
4094
|
check('自定义图元:listSymbols 报得出注册过的名字', symView.listSymbols().includes('三角'), JSON.stringify(symView.listSymbols()))
|
|
4095
|
+
// 同名覆盖要换一张贴图:画笔从三角形换成整块实心方块,斜角立刻从透明变成实色。
|
|
4096
|
+
// 贴图缓存的来源键没带注册代次时,这里会继续拿旧三角形,画面看起来像「注册没生效」。
|
|
4097
|
+
const symBlockPainter = (context, info) => {
|
|
4098
|
+
context.fillStyle = '#c837d6'
|
|
4099
|
+
context.fillRect(0, 0, info.width, info.height)
|
|
4100
|
+
}
|
|
4101
|
+
symView.registerSymbol('三角', symBlockPainter)
|
|
4102
|
+
await edgeSleep(240)
|
|
4103
|
+
const symReregistered = await symShot(symView)
|
|
4104
|
+
const symReregisteredCorner = symRead(symReregistered, symCorner)
|
|
4105
|
+
check('自定义图元:同名重新注册会重新烘焙贴图(三角形换成整块色块,原来的斜角变实了)',
|
|
4106
|
+
symReregisteredCorner[3] > 200 && symReregisteredCorner[0] > 150 && symReregisteredCorner[1] < 120 && symReregisteredCorner[2] > 150,
|
|
4107
|
+
symReregisteredCorner.join(','))
|
|
3890
4108
|
symView.registerSymbol(' ', trianglePainter)
|
|
3891
4109
|
symView.registerSymbol('空画笔', null)
|
|
3892
4110
|
check('自定义图元:名字空的 / 画笔不是函数只报一条 onWarning,注册表不动',
|
|
3893
4111
|
warnedNames.length === 2 && symView.listSymbols().length === 1 && warnedNames.every(message => message.includes('registerSymbol')),
|
|
3894
4112
|
warnedNames.join(' | '))
|
|
4113
|
+
const scopedSymbolPainter = (context, info) => {
|
|
4114
|
+
context.fillStyle = info.color
|
|
4115
|
+
context.fillRect(0, 0, info.width, info.height)
|
|
4116
|
+
}
|
|
4117
|
+
const scopedSymbolOffA = symView.registerSymbol('作用域图元', scopedSymbolPainter)
|
|
4118
|
+
const scopedSymbolOffB = symView.registerSymbol('作用域图元', scopedSymbolPainter)
|
|
4119
|
+
scopedSymbolOffA()
|
|
4120
|
+
const scopedSymbolSurvived = symView.listSymbols().includes('作用域图元')
|
|
4121
|
+
scopedSymbolOffB()
|
|
4122
|
+
scopedSymbolOffB()
|
|
4123
|
+
check('自定义图元:同名覆盖后旧注销函数不误删新注册(画笔相同也算新注册),新注销函数重复调用安全',
|
|
4124
|
+
scopedSymbolSurvived && !symView.listSymbols().includes('作用域图元'),
|
|
4125
|
+
`旧注销后仍在 ${scopedSymbolSurvived} / 最终 ${JSON.stringify(symView.listSymbols())}`)
|
|
3895
4126
|
symView.unregisterSymbol('三角')
|
|
3896
4127
|
symOff()
|
|
3897
4128
|
await edgeSleep(240)
|
|
@@ -4214,9 +4445,9 @@
|
|
|
4214
4445
|
const auGeometry = id => auView.listLines({}).find(item => item.item?.id === id)?.path ?? null
|
|
4215
4446
|
const auWaypoints = id => auView.getWaypoints(auLineIndex(id)).map(item => item.coordinate)
|
|
4216
4447
|
|
|
4217
|
-
// ① getPointStyle 读的是「样式链定下来的那一份」:
|
|
4218
|
-
auView.
|
|
4219
|
-
check('getPointStyle:
|
|
4448
|
+
// ① getPointStyle 读的是「样式链定下来的那一份」:updatePoint({ size }) 之后必须跟着变
|
|
4449
|
+
auView.updatePoint('a', { size: 34 })
|
|
4450
|
+
check('getPointStyle:updatePoint({ size }) 之后读回来就是新尺寸', auView.getPointStyle(auPointIndex('a'))?.size === 34, JSON.stringify(auView.getPointStyle(auPointIndex('a'))))
|
|
4220
4451
|
auView.updatePoint('a', { size: 26 })
|
|
4221
4452
|
check('getPointStyle:updatePoint({ size }) 之后也跟上(不再停在初次解析的那一份)', auView.getPointStyle(auPointIndex('a'))?.size === 26, JSON.stringify(auView.getPointStyle(auPointIndex('a'))))
|
|
4222
4453
|
|
|
@@ -4229,7 +4460,7 @@
|
|
|
4229
4460
|
auView.cancelPointCopy()
|
|
4230
4461
|
|
|
4231
4462
|
// ③ setCurrentChannelGroup(id, null) = 不选中(不是「选中第一组」)
|
|
4232
|
-
auView.
|
|
4463
|
+
auView.selectFeatures('m1')
|
|
4233
4464
|
const auSelectedBefore = auView.getSelections().filter(entry => entry.type === 'line').length
|
|
4234
4465
|
auView.setCurrentChannelGroup('au-ch', null)
|
|
4235
4466
|
const auSelectedAfter = auView.getSelections().filter(entry => entry.type === 'line').length
|
|
@@ -4344,6 +4575,446 @@
|
|
|
4344
4575
|
JSON.stringify(gwView.getOptions().channels[0]?.groups ?? null))
|
|
4345
4576
|
gwView.destroy()
|
|
4346
4577
|
gwHost.remove()
|
|
4578
|
+
|
|
4579
|
+
/* ⑪ 扩展图形:path / image / canvas 三类,世界坐标跟随相机,DOM 层级在画布与标签之间,导出 PNG / SVG 都包含 */
|
|
4580
|
+
const grHost = document.createElement('div')
|
|
4581
|
+
grHost.style.cssText = 'width:480px;height:300px'
|
|
4582
|
+
document.body.appendChild(grHost)
|
|
4583
|
+
const grImageCanvas = document.createElement('canvas')
|
|
4584
|
+
grImageCanvas.width = 8
|
|
4585
|
+
grImageCanvas.height = 8
|
|
4586
|
+
const grImageContext = grImageCanvas.getContext('2d')
|
|
4587
|
+
grImageContext.fillStyle = '#ffcc00'
|
|
4588
|
+
grImageContext.fillRect(0, 0, 8, 8)
|
|
4589
|
+
const grLiveCanvas = document.createElement('canvas')
|
|
4590
|
+
grLiveCanvas.width = 16
|
|
4591
|
+
grLiveCanvas.height = 16
|
|
4592
|
+
const grLiveContext = grLiveCanvas.getContext('2d')
|
|
4593
|
+
grLiveContext.fillStyle = '#00aa66'
|
|
4594
|
+
grLiveContext.fillRect(0, 0, 16, 16)
|
|
4595
|
+
const grBase = [
|
|
4596
|
+
{ id: 'g-path', type: 'path', x: 0, y: 0, width: 36, height: 36, viewBox: [0, 0, 10, 10], d: 'M0 0H10V10H0Z', fill: '#ff0000', stroke: 'none', anchor: 'center' },
|
|
4597
|
+
{ id: 'g-image', type: 'image', src: grImageCanvas.toDataURL('image/png'), x: 80, y: 0, width: 32, height: 32, anchor: 'center' },
|
|
4598
|
+
{ id: 'g-canvas', type: 'canvas', canvas: grLiveCanvas, x: -80, y: 0, width: 32, height: 32, anchor: 'center', scaleWithView: true }
|
|
4599
|
+
]
|
|
4600
|
+
const grWarns = []
|
|
4601
|
+
const grView = createGisView(grHost, {
|
|
4602
|
+
points: [],
|
|
4603
|
+
lines: [],
|
|
4604
|
+
graphics: grBase,
|
|
4605
|
+
initialView: { scale: 2, center: [0, 0] },
|
|
4606
|
+
showGrid: false,
|
|
4607
|
+
onWarning: message => grWarns.push(message)
|
|
4608
|
+
})
|
|
4609
|
+
await settle(220)
|
|
4610
|
+
const grNode = id => grHost.querySelector(`[data-graphic-id=${id}]`)
|
|
4611
|
+
const grChildren = [...grView.el.children]
|
|
4612
|
+
const grCanvasIndex = grChildren.indexOf(grView.canvas)
|
|
4613
|
+
const grLayerIndex = grChildren.indexOf(grHost.querySelector('.gis-view__graphics'))
|
|
4614
|
+
const grLabelIndex = grChildren.indexOf(grHost.querySelector('.gis-view__labels'))
|
|
4615
|
+
check('扩展图形:初始化同时接受 path / image / canvas 三类,并生成对应 DOM',
|
|
4616
|
+
grNode('g-path')?.dataset.graphicType === 'path' && grNode('g-image')?.querySelector('img') instanceof HTMLImageElement && grNode('g-canvas')?.contains(grLiveCanvas) === true,
|
|
4617
|
+
['g-path', 'g-image', 'g-canvas'].map(id => grNode(id)?.dataset.graphicType ?? 'missing').join(' / '))
|
|
4618
|
+
check('扩展图形:DOM 顺序为 canvas → graphics → labels(图形压在要素上、标签下)',
|
|
4619
|
+
grCanvasIndex >= 0 && grCanvasIndex < grLayerIndex && grLayerIndex < grLabelIndex,
|
|
4620
|
+
JSON.stringify({ canvas: grCanvasIndex, graphics: grLayerIndex, labels: grLabelIndex }))
|
|
4621
|
+
const grPathNode = grNode('g-path')
|
|
4622
|
+
const grCanvasNode = grNode('g-canvas')
|
|
4623
|
+
const grBefore = {
|
|
4624
|
+
left: Number.parseFloat(grPathNode.style.left),
|
|
4625
|
+
width: Number.parseFloat(grPathNode.style.width),
|
|
4626
|
+
canvasWidth: Number.parseFloat(grCanvasNode.style.width)
|
|
4627
|
+
}
|
|
4628
|
+
const grCamera = grView.getCamera()
|
|
4629
|
+
grView.setCamera({ ...grCamera, center: [grCamera.center[0] + 20, grCamera.center[1]], scale: grCamera.scale * 2 })
|
|
4630
|
+
await settle(80)
|
|
4631
|
+
const grAfter = {
|
|
4632
|
+
left: Number.parseFloat(grPathNode.style.left),
|
|
4633
|
+
width: Number.parseFloat(grPathNode.style.width),
|
|
4634
|
+
canvasWidth: Number.parseFloat(grCanvasNode.style.width)
|
|
4635
|
+
}
|
|
4636
|
+
check('扩展图形:相机移动后重投影(屏幕位置跟着变)', grAfter.left !== grBefore.left, `${grBefore.left} → ${grAfter.left}`)
|
|
4637
|
+
check('扩展图形:默认尺寸保持屏幕像素,scaleWithView: true 时宽高随相机缩放',
|
|
4638
|
+
Math.abs(grAfter.width - grBefore.width) < 0.5 && Math.abs(grAfter.canvasWidth - grBefore.canvasWidth * 2) < 0.5,
|
|
4639
|
+
JSON.stringify({ path: [grBefore.width, grAfter.width], canvas: [grBefore.canvasWidth, grAfter.canvasWidth] }))
|
|
4640
|
+
const grScene = grView.snapshotScene({ background: false })
|
|
4641
|
+
check('扩展图形:snapshotScene 带 scene.graphics(屏幕坐标、尺寸、类型齐全)',
|
|
4642
|
+
grScene?.graphics?.length === 3 && grScene.graphics.every(item => typeof item.x === 'number' && typeof item.width === 'number') && grScene.graphics.some(item => item.type === 'path') && grScene.graphics.some(item => item.type === 'image') && grScene.graphics.some(item => item.type === 'canvas'),
|
|
4643
|
+
JSON.stringify(grScene?.graphics?.map(item => ({ id: item.id, type: item.type, width: item.width })) ?? null))
|
|
4644
|
+
const grDecodeSvg = url => {
|
|
4645
|
+
const base64 = String(url).slice('data:image/svg+xml;base64,'.length)
|
|
4646
|
+
return new TextDecoder().decode(Uint8Array.from(atob(base64), char => char.charCodeAt(0)))
|
|
4647
|
+
}
|
|
4648
|
+
const grSvg = grDecodeSvg(await grView.exportImage({ type: 'svg', includeLabels: false, background: 'none' }))
|
|
4649
|
+
check('扩展图形:SVG 导出包含路径与图片 / Canvas 的 <image>',
|
|
4650
|
+
grSvg.includes('M0 0H10V10H0Z') && (grSvg.match(/<image/g) ?? []).length >= 2,
|
|
4651
|
+
`path=${grSvg.includes('M0 0H10V10H0Z')} / image=${(grSvg.match(/<image/g) ?? []).length}`)
|
|
4652
|
+
const grPixelAt = async (url, x, y) => {
|
|
4653
|
+
const image = new Image()
|
|
4654
|
+
await new Promise((resolve, reject) => {
|
|
4655
|
+
image.onload = resolve
|
|
4656
|
+
image.onerror = () => reject(new Error('扩展图形导出图片加载失败'))
|
|
4657
|
+
image.src = url
|
|
4658
|
+
})
|
|
4659
|
+
const buffer = document.createElement('canvas')
|
|
4660
|
+
buffer.width = image.width
|
|
4661
|
+
buffer.height = image.height
|
|
4662
|
+
const context = buffer.getContext('2d')
|
|
4663
|
+
context.drawImage(image, 0, 0)
|
|
4664
|
+
return [...context.getImageData(Math.round(x), Math.round(y), 1, 1).data]
|
|
4665
|
+
}
|
|
4666
|
+
const grPoint = grView.project(0, 0)
|
|
4667
|
+
const grPixel = await grPixelAt(await grView.exportImage({ background: 'none', includeLabels: false, scale: 1 }), grPoint[0], grPoint[1])
|
|
4668
|
+
check('扩展图形:PNG 导出按「画布 → 图形 → 标签」次序合成(路径像素真的进了图)',
|
|
4669
|
+
grPixel[0] > 200 && grPixel[1] < 80 && grPixel[2] < 80 && grPixel[3] > 200,
|
|
4670
|
+
`rgba(${grPixel.join(',')})`)
|
|
4671
|
+
const grAdded = grView.addGraphic({ id: 'g-dyn', type: 'path', x: 0, y: 100, width: 20, height: 20, viewBox: [0, 0, 10, 10], d: 'M0 0H10V10H0Z', fill: '#3355ff', zIndex: 9 })
|
|
4672
|
+
const grBulk = grView.addGraphics([{ id: 'g-low', type: 'path', x: 0, y: -100, width: 20, height: 20, viewBox: [0, 0, 10, 10], d: 'M0 0H10V10H0Z', fill: '#999999', zIndex: -5 }])
|
|
4673
|
+
const grUpdated = grView.updateGraphic('g-dyn', { style: { width: 50, height: 30, fill: '#00aa00' } })
|
|
4674
|
+
const grBadType = grView.addGraphic({ id: 'g-bad', type: 'triangle', x: 0, y: 0, width: 10, height: 10, d: 'M0 0H10V10Z' })
|
|
4675
|
+
const grDuplicate = grView.addGraphics([{ id: 'g-low', type: 'path', x: 0, y: -100, width: 24, height: 20, viewBox: [0, 0, 10, 10], d: 'M0 0H10V10H0Z', fill: '#777777', zIndex: -4 }])
|
|
4676
|
+
const grOrdered = grView.getGraphics()
|
|
4677
|
+
check('扩展图形:addGraphic / addGraphics / updateGraphic / getGraphic 都能用(style.width / height 也能改)',
|
|
4678
|
+
grAdded?.id === 'g-dyn' && grBulk.length === 1 && grUpdated?.width === 50 && grUpdated?.height === 30 && grUpdated?.fill === '#00aa00' && grView.getGraphic('g-image')?.type === 'image' && grView.getGraphic('missing') === null,
|
|
4679
|
+
JSON.stringify({ added: grAdded?.id, bulk: grBulk.length, updated: grUpdated && { width: grUpdated.width, height: grUpdated.height, fill: grUpdated.fill } }))
|
|
4680
|
+
check('扩展图形:显式写错的 type 不静默按 path 处理,跳过并给出 warning', grBadType === null && grView.getGraphic('g-bad') === null && grWarns.some(message => message.includes('triangle') && message.includes('不支持')), grWarns.join(' | '))
|
|
4681
|
+
check('扩展图形:addGraphics 里重复 id 按更新覆盖并点名 warning', grDuplicate.length === 1 && grView.getGraphic('g-low')?.fill === '#777777' && grWarns.some(message => message.includes('addGraphics') && message.includes('g-low')), JSON.stringify({ count: grDuplicate.length, fill: grView.getGraphic('g-low')?.fill }))
|
|
4682
|
+
check('扩展图形:getGraphics 按 zIndex、再按加入顺序排', grOrdered[0]?.id === 'g-low' && grOrdered[grOrdered.length - 1]?.id === 'g-dyn', grOrdered.map(item => `${item.id}:${item.zIndex}`).join(' / '))
|
|
4683
|
+
const grRemoved = grView.removeGraphic('g-dyn')
|
|
4684
|
+
const grRemovedBulk = grView.removeGraphic('g-low')
|
|
4685
|
+
const grMissing = grView.removeGraphic('missing')
|
|
4686
|
+
const grCleared = grView.clearGraphics()
|
|
4687
|
+
check('扩展图形:removeGraphic / clearGraphics 返回值与清空结果一致',
|
|
4688
|
+
grRemoved?.id === 'g-dyn' && grRemovedBulk?.id === 'g-low' && grMissing === null && grCleared === 3 && grView.getGraphics().length === 0,
|
|
4689
|
+
JSON.stringify({ removed: grRemoved?.id, removedBulk: grRemovedBulk?.id, missing: grMissing, cleared: grCleared }))
|
|
4690
|
+
grView.updateOptions({ graphics: grBase })
|
|
4691
|
+
await settle(120)
|
|
4692
|
+
check('扩展图形:运行时清空后再传回同一份初始数组仍能恢复(配置来源不会因同一引用短路)',
|
|
4693
|
+
grView.getGraphics().length === 3 && Boolean(grNode('g-path')) && Boolean(grNode('g-image')) && Boolean(grNode('g-canvas')),
|
|
4694
|
+
`${grView.getGraphics().length} 条 / DOM ${grHost.querySelectorAll('.gis-view__graphic').length} 个`)
|
|
4695
|
+
grView.destroy()
|
|
4696
|
+
grHost.remove()
|
|
4697
|
+
|
|
4698
|
+
/* 这一轮新增:通道草稿样式 / 多回线预览 / 选起点后点空白取消 / WebGL 自定义层 */
|
|
4699
|
+
const { channelDraftArrowPlacement } = await import('../../src/gis-view/engine.js')
|
|
4700
|
+
const arrowQuarter = channelDraftArrowPlacement([[0, 0], [100, 0]], 0.25)
|
|
4701
|
+
const arrowDefault = channelDraftArrowPlacement([[0, 0], [100, 0]])
|
|
4702
|
+
const arrowClamped = channelDraftArrowPlacement([[0, 0], [100, 0]], 2)
|
|
4703
|
+
check('草稿箭头位置按弧长比例取点(25% / 缺省 50% / 越界夹到端点)',
|
|
4704
|
+
Math.abs(arrowQuarter?.center[0] - 25) < 1e-9 && Math.abs(arrowDefault?.center[0] - 50) < 1e-9 && Math.abs(arrowClamped?.center[0] - 100) < 1e-9 && arrowQuarter.unitX === 1,
|
|
4705
|
+
JSON.stringify({ quarter: arrowQuarter?.center, center: arrowDefault?.center, end: arrowClamped?.center }))
|
|
4706
|
+
|
|
4707
|
+
const draftHost = document.createElement('div')
|
|
4708
|
+
draftHost.style.cssText = 'width:520px;height:340px'
|
|
4709
|
+
document.body.appendChild(draftHost)
|
|
4710
|
+
const draftEvents = []
|
|
4711
|
+
const draftClicks = []
|
|
4712
|
+
const draftDraws = []
|
|
4713
|
+
const draftErrors = []
|
|
4714
|
+
const draftCustomDraw = (context) => {
|
|
4715
|
+
draftDraws.push(context)
|
|
4716
|
+
// 故意把 GL 状态改坏:组件应在 customDraw 返回后收回内置绘制依赖的状态
|
|
4717
|
+
context.gl.enable(context.gl.SCISSOR_TEST)
|
|
4718
|
+
context.gl.scissor(0, 0, 1, 1)
|
|
4719
|
+
context.gl.viewport(1, 1, 1, 1)
|
|
4720
|
+
context.gl.disable(context.gl.BLEND)
|
|
4721
|
+
}
|
|
4722
|
+
const draftView = createGisView(draftHost, {
|
|
4723
|
+
points: [{ id: 'd-a', x: 0, y: 0 }, { id: 'd-b', x: 180, y: 0 }, { id: 'd-c', x: 0, y: 140 }],
|
|
4724
|
+
lines: [],
|
|
4725
|
+
fitPadding: 70,
|
|
4726
|
+
showGrid: false,
|
|
4727
|
+
features: { channelDraft: true },
|
|
4728
|
+
onChannelDraft: payload => draftEvents.push(payload),
|
|
4729
|
+
onChannelDraftClick: payload => draftClicks.push(payload),
|
|
4730
|
+
onError: message => draftErrors.push(String(message))
|
|
4731
|
+
})
|
|
4732
|
+
await settle(150)
|
|
4733
|
+
const draftCanvas = draftView.canvas
|
|
4734
|
+
const dispatchDraftPointer = (type, x, y) => {
|
|
4735
|
+
const screen = draftView.project(x, y)
|
|
4736
|
+
const rect = draftCanvas.getBoundingClientRect()
|
|
4737
|
+
draftCanvas.dispatchEvent(new PointerEvent(type, {
|
|
4738
|
+
bubbles: true,
|
|
4739
|
+
cancelable: true,
|
|
4740
|
+
composed: true,
|
|
4741
|
+
pointerId: 19,
|
|
4742
|
+
pointerType: 'mouse',
|
|
4743
|
+
isPrimary: true,
|
|
4744
|
+
button: 0,
|
|
4745
|
+
clientX: rect.left + screen[0],
|
|
4746
|
+
clientY: rect.top + screen[1]
|
|
4747
|
+
}))
|
|
4748
|
+
}
|
|
4749
|
+
const clickDraftWorld = async (x, y) => {
|
|
4750
|
+
dispatchDraftPointer('pointerdown', x, y)
|
|
4751
|
+
dispatchDraftPointer('pointerup', x, y)
|
|
4752
|
+
await settle(40)
|
|
4753
|
+
}
|
|
4754
|
+
draftView.beginAddChannel({
|
|
4755
|
+
circuits: 3,
|
|
4756
|
+
routing: 'orthogonal',
|
|
4757
|
+
channelDraftStyle: {
|
|
4758
|
+
color: '#1456d8',
|
|
4759
|
+
width: 3,
|
|
4760
|
+
dot: 3,
|
|
4761
|
+
dotColor: '#e58a2b',
|
|
4762
|
+
arrow: true,
|
|
4763
|
+
arrowSize: 15,
|
|
4764
|
+
arrowPositions: [0.25, 0.75],
|
|
4765
|
+
arrowReverse: true,
|
|
4766
|
+
arrowColor: '#128457',
|
|
4767
|
+
circuitSpacing: 18,
|
|
4768
|
+
opacity: 0.65,
|
|
4769
|
+
customDraw: draftCustomDraw
|
|
4770
|
+
}
|
|
4771
|
+
})
|
|
4772
|
+
const draftStart = draftView.getAddChannelDraft()
|
|
4773
|
+
check('beginAddChannel 接收本次草稿完整样式,3 回通道与箭头位置都进载荷',
|
|
4774
|
+
draftStart?.phase === 'start' && draftStart.circuits === 3 && draftStart.style?.color === '#1456d8' && draftStart.style?.dotColor === '#e58a2b' && draftStart.style?.arrow === true && draftStart.style?.arrowReverse === true && draftStart.style?.arrowColor === '#128457' && draftStart.style?.circuitSpacing === 18 && draftStart.style?.opacity === 0.65 && draftStart.style?.arrowPositions.join(',') === '0.25,0.75' && draftStart.style?.customDraw === draftCustomDraw,
|
|
4775
|
+
JSON.stringify(draftStart?.style))
|
|
4776
|
+
|
|
4777
|
+
await clickDraftWorld(0, 0)
|
|
4778
|
+
const styleReturn = draftView.updateAddChannelStyle({ arrowPositions: [0.15, 0.5, 0.85], circuitSpacing: 24, opacity: 0.5 })
|
|
4779
|
+
dispatchDraftPointer('pointermove', 110, 100)
|
|
4780
|
+
await settle(30)
|
|
4781
|
+
const draftStyled = draftView.getAddChannelDraft()
|
|
4782
|
+
check('updateAddChannelStyle 字段级合并箭头位置 / 回线间距 / 透明度,并保持链式返回',
|
|
4783
|
+
styleReturn === draftView && draftStyled?.phase === 'from' && draftStyled.style.arrowPositions.join(',') === '0.15,0.5,0.85' && draftStyled.style.circuitSpacing === 24 && draftStyled.style.opacity === 0.5 && draftStyled.style.color === '#1456d8',
|
|
4784
|
+
JSON.stringify(draftStyled?.style))
|
|
4785
|
+
|
|
4786
|
+
draftDraws.length = 0
|
|
4787
|
+
await draftView.exportImage({ background: 'none', includeLabels: false, scale: 1 })
|
|
4788
|
+
const draftDraw = draftDraws.at(-1)
|
|
4789
|
+
const draftStarts = new Set((draftDraw?.circuitPaths ?? []).map(path => `${path[0]?.[0]?.toFixed(3)},${path[0]?.[1]?.toFixed(3)}`))
|
|
4790
|
+
check('customDraw 拿到最终 GL / 相机 / 完整路径与 3 条平行回线,可完全接管预览绘制',
|
|
4791
|
+
draftDraws.length > 0 && draftDraw?.gl === draftView.gl && draftDraw?.canvas === draftCanvas && draftDraw?.circuits === 3 && draftDraw?.circuitPaths.length === 3 && draftStarts.size === 3 && draftDraw?.path.length >= 2 && draftDraw?.kind === 'channel' && draftDraw?.phase === 'from' && draftDraw?.style.arrowPositions.join(',') === '0.15,0.5,0.85' && draftErrors.length === 0,
|
|
4792
|
+
JSON.stringify({ draws: draftDraws.length, circuits: draftDraw?.circuits, starts: [...draftStarts], path: draftDraw?.path?.length, errors: draftErrors }))
|
|
4793
|
+
|
|
4794
|
+
const draftResetReturn = draftView.updateAddChannelStyle(null)
|
|
4795
|
+
const draftReset = draftView.getAddChannelDraft()
|
|
4796
|
+
check('updateAddChannelStyle(null) 回到全局缺省样式(箭头关、单中点位置、间距 10)',
|
|
4797
|
+
draftResetReturn === draftView && draftReset?.style.arrow === false && draftReset.style.arrowPositions.join(',') === '0.5' && draftReset.style.circuitSpacing === 10 && draftReset.style.opacity === 1 && typeof draftReset.style.customDraw === 'undefined',
|
|
4798
|
+
JSON.stringify(draftReset?.style))
|
|
4799
|
+
|
|
4800
|
+
const draftDataBeforeCancel = draftView.getData()
|
|
4801
|
+
draftEvents.length = 0
|
|
4802
|
+
draftClicks.length = 0
|
|
4803
|
+
await clickDraftWorld(110, 100)
|
|
4804
|
+
const draftCancel = draftEvents.find(payload => payload.phase === 'cancel')
|
|
4805
|
+
const draftCancelClick = draftClicks.at(-1)
|
|
4806
|
+
check('已选起点后点节点以外的位置直接取消:草稿清空、cancel 同时给 onChannelDraft 与 onChannelDraftClick',
|
|
4807
|
+
draftView.getAddChannelDraft() === null && draftCancel?.reason === 'click-outside-node' && draftCancel?.hit === false && draftCancelClick?.phase === 'cancel' && draftCancelClick?.reason === 'click-outside-node' && draftView.getData().lines.length === draftDataBeforeCancel.lines.length,
|
|
4808
|
+
JSON.stringify({ draft: draftView.getAddChannelDraft(), event: draftCancel?.reason, click: draftCancelClick?.reason }))
|
|
4809
|
+
|
|
4810
|
+
draftView.beginAddChannel({ circuits: 2 })
|
|
4811
|
+
draftEvents.length = 0
|
|
4812
|
+
draftClicks.length = 0
|
|
4813
|
+
await clickDraftWorld(110, 100)
|
|
4814
|
+
check('还没选起点时点空白仍保持草稿并发送 pick(不会误取消)',
|
|
4815
|
+
draftView.getAddChannelDraft()?.phase === 'start' && draftClicks.at(-1)?.phase === 'pick' && draftClicks.at(-1)?.hit === false && !draftEvents.some(payload => payload.phase === 'cancel'),
|
|
4816
|
+
JSON.stringify({ draft: draftView.getAddChannelDraft()?.phase, click: draftClicks.at(-1)?.phase, events: draftEvents.map(payload => payload.phase) }))
|
|
4817
|
+
draftView.cancelAddChannel()
|
|
4818
|
+
|
|
4819
|
+
/* WebGL 自定义层:最终画布、排序 / 开关 / 卸载,以及每层开画前的状态重置 */
|
|
4820
|
+
let lowLayerCalls = 0
|
|
4821
|
+
let highLayerCalls = 0
|
|
4822
|
+
let highLayerState = null
|
|
4823
|
+
draftView.addWebGLLayer({
|
|
4824
|
+
id: 'test-high',
|
|
4825
|
+
order: 20,
|
|
4826
|
+
draw: ({ gl }) => {
|
|
4827
|
+
highLayerCalls += 1
|
|
4828
|
+
highLayerState = {
|
|
4829
|
+
scissor: gl.isEnabled(gl.SCISSOR_TEST),
|
|
4830
|
+
blend: gl.isEnabled(gl.BLEND),
|
|
4831
|
+
viewport: Array.from(gl.getParameter(gl.VIEWPORT)),
|
|
4832
|
+
framebuffer: gl.getParameter(gl.FRAMEBUFFER_BINDING)
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
})
|
|
4836
|
+
draftView.addWebGLLayer({
|
|
4837
|
+
id: 'test-low',
|
|
4838
|
+
order: 10,
|
|
4839
|
+
draw: ({ gl }) => {
|
|
4840
|
+
lowLayerCalls += 1
|
|
4841
|
+
gl.enable(gl.SCISSOR_TEST)
|
|
4842
|
+
gl.scissor(0, 0, 2, 2)
|
|
4843
|
+
gl.viewport(3, 3, 4, 4)
|
|
4844
|
+
gl.disable(gl.BLEND)
|
|
4845
|
+
}
|
|
4846
|
+
})
|
|
4847
|
+
const layerOrder = draftView.getWebGLLayers()
|
|
4848
|
+
check('view.gl / addWebGLLayer / getWebGLLayers 可用,层按 order 排序(同 id 可覆盖)',
|
|
4849
|
+
typeof WebGL2RenderingContext !== 'undefined' && draftView.gl instanceof WebGL2RenderingContext && layerOrder.map(layer => layer.id).join(',') === 'test-low,test-high' && layerOrder.every(layer => typeof layer.draw === 'function') && draftView.getOptions().webglLayers.map(layer => layer.id).join(',') === 'test-low,test-high',
|
|
4850
|
+
JSON.stringify({ gl: Boolean(draftView.gl), order: layerOrder.map(layer => `${layer.id}:${layer.order}`), options: draftView.getOptions().webglLayers?.map(layer => layer.id) }))
|
|
4851
|
+
|
|
4852
|
+
await draftView.exportImage({ background: 'none', includeLabels: false, scale: 1 })
|
|
4853
|
+
check('自定义层在内置内容之后执行;前一层改坏的 scissor / viewport / blend 不会漏给下一层',
|
|
4854
|
+
lowLayerCalls > 0 && highLayerCalls > 0 && highLayerState?.scissor === false && highLayerState?.blend === true && highLayerState?.framebuffer === null && highLayerState?.viewport.join(',') === [0, 0, draftCanvas.width, draftCanvas.height].join(','),
|
|
4855
|
+
JSON.stringify({ low: lowLayerCalls, high: highLayerCalls, state: highLayerState }))
|
|
4856
|
+
|
|
4857
|
+
const lowBeforeDisabled = lowLayerCalls
|
|
4858
|
+
const highBeforeDisabled = highLayerCalls
|
|
4859
|
+
const disabledLayer = draftView.updateWebGLLayer('test-low', { enabled: false })
|
|
4860
|
+
await draftView.exportImage({ background: 'none', includeLabels: false, scale: 1 })
|
|
4861
|
+
check('enabled: false 只跳过这一层,其它层照常绘制',
|
|
4862
|
+
disabledLayer?.enabled === false && lowLayerCalls === lowBeforeDisabled && highLayerCalls > highBeforeDisabled,
|
|
4863
|
+
JSON.stringify({ low: [lowBeforeDisabled, lowLayerCalls], high: [highBeforeDisabled, highLayerCalls] }))
|
|
4864
|
+
|
|
4865
|
+
const reenabled = draftView.updateWebGLLayer('test-low', { enabled: true, order: 40 })
|
|
4866
|
+
await draftView.exportImage({ background: 'none', includeLabels: false, scale: 1 })
|
|
4867
|
+
draftView.updateWebGLLayer('test-high', { order: 50 })
|
|
4868
|
+
check('updateWebGLLayer 可原地改 enabled / order,排序马上反映在 getWebGLLayers',
|
|
4869
|
+
reenabled?.enabled === true && reenabled.order === 40 && draftView.getWebGLLayers().map(layer => `${layer.id}:${layer.order}`).join(',') === 'test-low:40,test-high:50',
|
|
4870
|
+
JSON.stringify(draftView.getWebGLLayers().map(layer => `${layer.id}:${layer.order}`)))
|
|
4871
|
+
|
|
4872
|
+
const removedLow = draftView.removeWebGLLayer('test-low')
|
|
4873
|
+
const missingLayer = draftView.updateWebGLLayer('missing', { order: 1 })
|
|
4874
|
+
const unsubscribe = draftView.addWebGLLayer({ id: 'test-off', order: 5, draw: () => {} })
|
|
4875
|
+
const offAdded = draftView.getWebGLLayers().some(layer => layer.id === 'test-off')
|
|
4876
|
+
unsubscribe()
|
|
4877
|
+
check('removeWebGLLayer / addWebGLLayer 的卸载函数 / 不存在的 id 都按接口约定返回',
|
|
4878
|
+
removedLow?.id === 'test-low' && missingLayer === null && draftView.removeWebGLLayer('missing') === null && offAdded && !draftView.getWebGLLayers().some(layer => layer.id === 'test-off'),
|
|
4879
|
+
JSON.stringify({ removed: removedLow?.id, missing: missingLayer, offAdded, layers: draftView.getWebGLLayers().map(layer => layer.id) }))
|
|
4880
|
+
|
|
4881
|
+
const staleUnload = draftView.addWebGLLayer({ id: 'test-replaced', order: 6, draw: () => {} })
|
|
4882
|
+
const replacementDraw = () => {}
|
|
4883
|
+
draftView.addWebGLLayer({ id: 'test-replaced', order: 6, draw: replacementDraw })
|
|
4884
|
+
staleUnload()
|
|
4885
|
+
const replacedLayer = draftView.getWebGLLayers().find(layer => layer.id === 'test-replaced')
|
|
4886
|
+
check('同 id 的 WebGL 层被覆盖后,旧卸载函数不会误删新层',
|
|
4887
|
+
replacedLayer?.draw === replacementDraw && (draftView.getOptions().webglLayers ?? []).some(layer => layer.id === 'test-replaced'),
|
|
4888
|
+
JSON.stringify({ layers: draftView.getWebGLLayers().map(layer => layer.id), options: draftView.getOptions().webglLayers?.map(layer => layer.id) }))
|
|
4889
|
+
draftView.removeWebGLLayer('test-replaced')
|
|
4890
|
+
|
|
4891
|
+
const clearedLayers = draftView.clearWebGLLayers()
|
|
4892
|
+
check('clearWebGLLayers 清空并同步 getOptions().webglLayers;requestRender 可链式调用',
|
|
4893
|
+
clearedLayers === 1 && draftView.getWebGLLayers().length === 0 && (draftView.getOptions().webglLayers ?? []).length === 0 && draftView.requestRender() === draftView,
|
|
4894
|
+
JSON.stringify({ cleared: clearedLayers, layers: draftView.getWebGLLayers().length, options: draftView.getOptions().webglLayers?.length }))
|
|
4895
|
+
|
|
4896
|
+
/* 隐藏 / 显示、节点拆分、通道变化对账:接口级自测 */
|
|
4897
|
+
const splitHost = document.createElement('div')
|
|
4898
|
+
splitHost.style.cssText = 'width:640px;height:380px'
|
|
4899
|
+
document.body.append(splitHost)
|
|
4900
|
+
const splitView = createGisView(splitHost, {
|
|
4901
|
+
points: [
|
|
4902
|
+
{ id: 'sv-a', x: 0, y: 0, name: 'A' },
|
|
4903
|
+
{ id: 'sv-b', x: 8, y: 0, name: 'B' },
|
|
4904
|
+
{ id: 'sv-c', x: 0, y: 8, name: 'C' }
|
|
4905
|
+
],
|
|
4906
|
+
lines: [
|
|
4907
|
+
{ id: 'sv-ab', from: 'sv-a', to: 'sv-b', name: 'AB' },
|
|
4908
|
+
{ id: 'sv-ab2', from: 'sv-a', to: 'sv-b', name: 'AB-2' },
|
|
4909
|
+
{ id: 'sv-ac', from: 'sv-a', to: 'sv-c', name: 'AC' }
|
|
4910
|
+
],
|
|
4911
|
+
channels: [{ id: 'sv-ch', from: 'sv-a', to: 'sv-b', lineIds: ['sv-ab', 'sv-ab2'] }],
|
|
4912
|
+
initialView: { scale: 1, center: [2, 2] },
|
|
4913
|
+
labelOptimize: false,
|
|
4914
|
+
showGrid: false,
|
|
4915
|
+
waypointEditable: true,
|
|
4916
|
+
history: { limit: 20 }
|
|
4917
|
+
})
|
|
4918
|
+
const splitOutcome = splitView.splitPoint('sv-a', [
|
|
4919
|
+
{ x: -1, y: -1, name: 'A-左' },
|
|
4920
|
+
{ x: 1, y: 1, name: 'A-右', lines: ['sv-ab'] }
|
|
4921
|
+
])
|
|
4922
|
+
const splitData = splitView.getData()
|
|
4923
|
+
const splitB = splitData.points.find(point => point.name === 'A-右')
|
|
4924
|
+
const splitAb = splitData.lines.find(line => line.id === 'sv-ab')
|
|
4925
|
+
const splitAb2 = splitData.lines.find(line => line.id === 'sv-ab2')
|
|
4926
|
+
const splitAc = splitData.lines.find(line => line.id === 'sv-ac')
|
|
4927
|
+
const splitChannel = splitView.getOptions().channels.find(channel => channel.id === 'sv-ch')
|
|
4928
|
+
check('splitPoint:第一份复用原下标 / id,第二份追加并拿到新 id', splitOutcome.ok === true && splitOutcome.items.length === 2 && splitOutcome.indexes[0] === 0 && splitData.points.length === 4 && splitOutcome.pointIds[0] === 'sv-a' && splitOutcome.pointIds[1] !== 'sv-a', JSON.stringify({ ids: splitOutcome.pointIds, indexes: splitOutcome.indexes, points: splitData.points.length }))
|
|
4929
|
+
check('splitPoint:显式分给第二份的线路改绑,未分配的线路跟第一份', splitAb?.from === splitB?.id && splitAb2?.from === 'sv-a' && splitAc?.from === 'sv-a', JSON.stringify({ ab: splitAb?.from, ab2: splitAb2?.from, ac: splitAc?.from, b: splitB?.id }))
|
|
4930
|
+
check('splitPoint:通道没写 channels 时跟随第一条被分配过的成员回线', String(splitChannel?.from) === String(splitB?.id) && String(splitChannel?.to) === 'sv-b', JSON.stringify({ from: splitChannel?.from, to: splitChannel?.to, b: splitB?.id }))
|
|
4931
|
+
check('splitPoint:返回可直接提交的 changeSet 与 splitPoint 计数', splitOutcome.changes?.removedPoint?.id === 'sv-a' && splitOutcome.changes?.points?.length === 2 && splitOutcome.changes?.lines?.some(line => line.id === 'sv-ab') && splitOutcome.changes?.channels?.some(channel => channel.id === 'sv-ch') && splitOutcome.linesChanged === 3 && splitOutcome.channelsChanged === 1, JSON.stringify({ lines: splitOutcome.changes?.lines?.map(line => line.id), channels: splitOutcome.changes?.channels?.map(channel => channel.id) }))
|
|
4932
|
+
const changedAfterSplit = splitView.getChangedChannels().find(channel => channel.id === 'sv-ch')
|
|
4933
|
+
check('getChangedChannels:拆分后的端点变化入账,reason 带 endpoints', Boolean(changedAfterSplit) && changedAfterSplit.endpointsChanged && changedAfterSplit.reasons.includes('endpoints'), JSON.stringify(changedAfterSplit?.reasons))
|
|
4934
|
+
splitView.undo()
|
|
4935
|
+
const undoData = splitView.getData()
|
|
4936
|
+
check('splitPoint:撤销一步回到拆分前(节点、线路、通道端点全恢复)', undoData.points.length === 3 && undoData.lines.find(line => line.id === 'sv-ab')?.from === 'sv-a' && splitView.getOptions().channels.find(channel => channel.id === 'sv-ch')?.from === 'sv-a', JSON.stringify({ points: undoData.points.length, from: undoData.lines.find(line => line.id === 'sv-ab')?.from }))
|
|
4937
|
+
splitView.redo()
|
|
4938
|
+
check('splitPoint:重做恢复拆分结果', splitView.getData().points.length === 4 && splitView.getData().lines.find(line => line.id === 'sv-ab')?.from !== 'sv-a', JSON.stringify({ points: splitView.getData().points.length }))
|
|
4939
|
+
splitView.clearChangedChannels()
|
|
4940
|
+
check('clearChangedChannels:全部对账后最近变化为空', splitView.getChangedChannels().length === 0, JSON.stringify(splitView.getChangedChannels().map(item => item.id)))
|
|
4941
|
+
const addedSplitGroup = splitView.addChannelGroup('sv-ch', ['sv-ab2'])
|
|
4942
|
+
const fullGroupChange = splitView.getChangedChannels().find(channel => channel.id === 'sv-ch')
|
|
4943
|
+
const ignoredGroupChange = splitView.getChangedChannels({ ignoreGroups: true }).find(channel => channel.id === 'sv-ch')
|
|
4944
|
+
check('getChangedChannels({ ignoreGroups }):分组新增 / 删除不入账', addedSplitGroup.ok === true && fullGroupChange?.groupsChanged === true && !ignoredGroupChange, JSON.stringify({ added: addedSplitGroup.ok, full: fullGroupChange?.reasons, ignored: ignoredGroupChange?.reasons }))
|
|
4945
|
+
splitView.clearChangedChannels()
|
|
4946
|
+
if (addedSplitGroup.ok) {
|
|
4947
|
+
splitView.removeChannelGroup('sv-ch', addedSplitGroup.groupId)
|
|
4948
|
+
splitView.clearChangedChannels()
|
|
4949
|
+
}
|
|
4950
|
+
const splitGroup = splitView.getChannelGroups('sv-ch')[0]
|
|
4951
|
+
splitView.addChannelGroupWaypoint('sv-ch', splitGroup?.id)
|
|
4952
|
+
const waypointChange = splitView.getChangedChannels({ reasons: ['waypoints'] }).find(channel => channel.id === 'sv-ch')
|
|
4953
|
+
const ignoredWaypointChange = splitView.getChangedChannels({ ignoreGroups: true, reasons: ['waypoints'] }).find(channel => channel.id === 'sv-ch')
|
|
4954
|
+
check('getChangedChannels({ reasons: [waypoints] }):只返回拐点变化并收窄 reasons', Boolean(waypointChange) && waypointChange.waypointsChanged && waypointChange.reasons.length === 1 && waypointChange.reasons[0] === 'waypoints', JSON.stringify(waypointChange?.reasons))
|
|
4955
|
+
check('getChangedChannels({ ignoreGroups }):已有分组的拐点变化照常报', Boolean(ignoredWaypointChange) && ignoredWaypointChange.waypointsChanged && ignoredWaypointChange.reasons.length === 1 && ignoredWaypointChange.reasons[0] === 'waypoints', JSON.stringify(ignoredWaypointChange?.reasons))
|
|
4956
|
+
splitView.clearChangedChannels('sv-ch')
|
|
4957
|
+
splitView.clearChangedLines()
|
|
4958
|
+
splitView.updateLine('sv-ac', { name: 'AC-变更' })
|
|
4959
|
+
const fieldLineChange = splitView.getChangedLines({ reasons: ['fields'] }).find(line => line.id === 'sv-ac')
|
|
4960
|
+
check('getChangedLines({ reasons: [fields] }):线路字段变化入账', Boolean(fieldLineChange) && fieldLineChange.fields.includes('name') && fieldLineChange.reasons.length === 1 && fieldLineChange.reasons[0] === 'fields', JSON.stringify(fieldLineChange?.reasons))
|
|
4961
|
+
splitView.clearChangedLines('sv-ac')
|
|
4962
|
+
splitView.setLineWaypoints('sv-ac', [[4, 4]])
|
|
4963
|
+
const pathLineChange = splitView.getChangedLines({ reasons: ['path'] }).find(line => line.id === 'sv-ac')
|
|
4964
|
+
check('getChangedLines:path 变化独立记 path,读取不改基准', Boolean(pathLineChange) && pathLineChange.pathChanged && pathLineChange.reasons.includes('path') && splitView.getChangedLines({ reasons: ['path'] }).length === 1, JSON.stringify(pathLineChange?.reasons))
|
|
4965
|
+
splitView.clearChangedLines()
|
|
4966
|
+
const hideChannel = splitView.hideFeatures('channel', 'sv-ch')
|
|
4967
|
+
const hiddenChannelRead = splitView.getChannel('sv-ch')
|
|
4968
|
+
const hideChanged = splitView.getChangedChannels().find(channel => channel.id === 'sv-ch')
|
|
4969
|
+
check('hideFeatures(channel):visible 落数据、通道退场,变化账本记 visibility', hideChannel.ok === true && splitView.getOptions().channels.find(channel => channel.id === 'sv-ch')?.visible === false && hiddenChannelRead?.lineIndexes?.length === 0 && hideChanged?.visibilityChanged === true, JSON.stringify({ ok: hideChannel.ok, paths: hiddenChannelRead?.lineIndexes?.length, reasons: hideChanged?.reasons }))
|
|
4970
|
+
const showChannel = splitView.showFeatures('channel', 'sv-ch')
|
|
4971
|
+
check('showFeatures(channel):隐藏后原样恢复(回线、分组、拐点都还在)', showChannel.ok === true && splitView.getChannel('sv-ch')?.lineIndexes?.length === 2 && splitView.getOptions().channels.find(channel => channel.id === 'sv-ch')?.visible === true && splitView.getChannelGroups('sv-ch').length === 1, JSON.stringify({ lines: splitView.getChannel('sv-ch')?.lineIndexes?.length, groups: splitView.getChannelGroups('sv-ch').length }))
|
|
4972
|
+
const generatedId = splitView.getData().points.find(point => point.name === 'A-右')?.id
|
|
4973
|
+
const hidePoint = splitView.hideFeatures('point', generatedId)
|
|
4974
|
+
const hideLine = splitView.hideFeatures('line', 'sv-ac')
|
|
4975
|
+
check('hideFeatures(point / line):只改 visible,数据不删', hidePoint.ok === true && hideLine.ok === true && splitView.getData().points.find(point => point.id === generatedId)?.visible === false && splitView.getData().lines.find(line => line.id === 'sv-ac')?.visible === false, JSON.stringify({ point: hidePoint.ok, line: hideLine.ok }))
|
|
4976
|
+
const hiddenLineChange = splitView.getChangedLines().find(line => line.id === 'sv-ac')
|
|
4977
|
+
check('getChangedLines:线路显隐变化入账', Boolean(hiddenLineChange) && hiddenLineChange.visibilityChanged && hiddenLineChange.reasons.includes('visibility'), JSON.stringify(hiddenLineChange?.reasons))
|
|
4978
|
+
splitView.showFeatures('point', generatedId)
|
|
4979
|
+
splitView.showFeatures('line', 'sv-ac')
|
|
4980
|
+
check('showFeatures(point / line):恢复后 visible 回到 true', splitView.getData().points.find(point => point.id === generatedId)?.visible === true && splitView.getData().lines.find(line => line.id === 'sv-ac')?.visible === true, '')
|
|
4981
|
+
splitView.clearChangedLines('sv-ac')
|
|
4982
|
+
splitView.updateLine('sv-ac', { name: 'AC-删除前' })
|
|
4983
|
+
const lineChangedBeforeDelete = splitView.getChangedLines().some(line => line.id === 'sv-ac')
|
|
4984
|
+
splitView.removeLine('sv-ac')
|
|
4985
|
+
const lineAfterDelete = splitView.getChangedLines().find(line => line.id === 'sv-ac')
|
|
4986
|
+
check('getChangedLines:线路删除同步清账,不再返回已删线路', lineChangedBeforeDelete && !lineAfterDelete, JSON.stringify({ before: lineChangedBeforeDelete, after: lineAfterDelete ?? null }))
|
|
4987
|
+
splitView.clearChangedChannels()
|
|
4988
|
+
splitView.updateChannel('sv-ch', { name: '待删通道' })
|
|
4989
|
+
const channelChangedBeforeDelete = splitView.getChangedChannels().some(channel => channel.id === 'sv-ch')
|
|
4990
|
+
splitView.removeChannel('sv-ch')
|
|
4991
|
+
const channelAfterDelete = splitView.getChangedChannels().find(channel => channel.id === 'sv-ch')
|
|
4992
|
+
check('getChangedChannels:通道删除同步清账,不再返回已删通道', channelChangedBeforeDelete && !channelAfterDelete, JSON.stringify({ before: channelChangedBeforeDelete, after: channelAfterDelete ?? null }))
|
|
4993
|
+
const lineOnlyHost = document.createElement('div')
|
|
4994
|
+
lineOnlyHost.style.cssText = 'width:480px;height:280px'
|
|
4995
|
+
document.body.append(lineOnlyHost)
|
|
4996
|
+
const lineOnlyView = createGisView(lineOnlyHost, {
|
|
4997
|
+
points: [
|
|
4998
|
+
{ id: 'lo-a', x: 0, y: 0 },
|
|
4999
|
+
{ id: 'lo-b', x: 8, y: 0 }
|
|
5000
|
+
],
|
|
5001
|
+
lines: [{ id: 'lo-ab', from: 'lo-a', to: 'lo-b', name: 'AB' }],
|
|
5002
|
+
initialView: { scale: 1, center: [4, 0] },
|
|
5003
|
+
labelOptimize: false,
|
|
5004
|
+
showGrid: false
|
|
5005
|
+
})
|
|
5006
|
+
const lineOnlyInitial = lineOnlyView.getChangedLines().length === 0
|
|
5007
|
+
lineOnlyView.updateLine('lo-ab', { name: 'AB-变更' })
|
|
5008
|
+
const lineOnlyChange = lineOnlyView.getChangedLines({ reasons: ['fields'] }).find(line => line.id === 'lo-ab')
|
|
5009
|
+
lineOnlyView.removeLine('lo-ab')
|
|
5010
|
+
const lineOnlyAfterDelete = lineOnlyView.getChangedLines().find(line => line.id === 'lo-ab')
|
|
5011
|
+
check('getChangedLines:只有节点和线路时独立对账,删线路后同步清账', lineOnlyInitial && Boolean(lineOnlyChange) && lineOnlyChange.fields.includes('name') && !lineOnlyAfterDelete, JSON.stringify({ initial: lineOnlyInitial, change: lineOnlyChange?.reasons, after: lineOnlyAfterDelete ?? null }))
|
|
5012
|
+
lineOnlyView.destroy()
|
|
5013
|
+
lineOnlyHost.remove()
|
|
5014
|
+
splitView.destroy()
|
|
5015
|
+
splitHost.remove()
|
|
5016
|
+
draftView.destroy()
|
|
5017
|
+
draftHost.remove()
|
|
4347
5018
|
auView.destroy()
|
|
4348
5019
|
auHost.remove()
|
|
4349
5020
|
symView.destroy()
|