gis-view-webgl2 0.1.7 → 0.1.9
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 +14 -11
- package/dist/gis-view.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +445 -241
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +65 -498
- package/docs/GisView.md +1 -1
- package/docs/GisView/345/256/236/347/216/260/345/216/237/347/220/206.md +1 -1
- 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 +114 -478
- package/docs/demos/BasicDemo.vue +144 -27
- package/docs/demos/BatchAddDemo.vue +133 -7
- package/docs/demos/ChannelDemo.vue +61 -0
- package/docs/demos/ConnectDemo.vue +164 -33
- package/docs/demos/CopyMergeDemo.vue +144 -42
- package/docs/demos/EventDemo.vue +29 -24
- package/docs/demos/LabelDemo.vue +5 -5
- package/docs/demos/api-index.js +53 -41
- package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +1 -1
- package/docs/demos/gis-view-demo/GisViewDemoStage.vue +1 -1
- package/docs/demos/gis-view-demo/panels/DataPanels.vue +1 -1
- package/docs/demos/gis-view-demo/useGisViewDemo.js +31 -33
- package/docs/format/02-/350/212/202/347/202/271/344/270/216/347/272/277/350/267/257.md +1 -0
- package/docs/format/04-/351/200/232/351/201/223/344/270/216/346/240/207/347/255/276.md +3 -0
- 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 +8 -7
- package/docs/from0/05-/347/233/270/346/234/272/344/270/216/344/272/244/344/272/222.md +1 -1
- package/docs/from0/07-/351/200/232/351/201/223/345/210/206/347/273/204/344/270/216/345/244/232/345/233/236/347/272/277.md +26 -10
- package/docs/index.html +136 -13
- package/docs/internals/15-04-/351/200/220/350/275/256/350/256/260/345/275/225-/347/203/255/345/212/233/345/233/276/344/270/216/346/200/247/350/203/275.md +10 -0
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +1 -1
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +82 -14
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +23 -17
- package/docs/manual/05-/347/224/273/350/264/250/344/270/216/346/211/271/351/207/217/346/233/264/346/226/260.md +65 -0
- 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 +9 -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 +100 -40
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +11 -7
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +66 -6
- 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 +226 -493
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +28 -5
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +46 -14
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +5 -2
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +34 -29
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +30 -22
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +168 -81
- 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 +1 -1
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +5 -3
- 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 +9 -6
- package/docs/steps/README.md +201 -120
- package/docs/steps/bench-scale.html +1 -1
- package/docs/steps/index.html +222 -6
- package/docs/steps/selftest-actions.html +26 -25
- package/docs/steps/selftest-data.html +1 -1
- package/docs/steps/selftest-demos.html +290 -16
- package/docs/steps/selftest-docs.html +17 -6
- package/docs/steps/selftest-features.html +594 -54
- package/docs/steps/selftest-full.html +3 -3
- package/docs/steps/selftest-labels.html +82 -1
- package/docs/steps/selftest-perf.html +62 -7
- package/docs/steps/selftest-select.html +71 -6
- package/docs/steps/step-05-pick.js +4 -8
- package/docs/steps/step-07-channels.js +3 -5
- package/docs/steps/step-08-labels.js +3 -3
- package/docs/steps/step-10-full.js +3 -3
- package/docs/steps/step-12-events.js +41 -24
- package/docs//344/273/2160/345/210/260/345/256/214/346/225/264/347/232/204/345/256/236/347/216/260.md +1 -0
- package/package.json +1 -1
- package/docs/ai/01-/350/265/267/346/272/220/344/270/216/350/277/231/344/273/275/346/261/207/346/200/273/347/232/204/347/224/261/346/235/245.md +0 -29
- package/docs/ai/02-/346/214/211/345/206/205/345/256/271/345/210/206/347/261/273-/350/277/231/344/270/200/350/267/257/351/203/275/345/234/250/346/224/271/344/273/200/344/271/210.md +0 -66
- package/docs/ai/03-/351/207/215/350/246/201/351/227/256/347/255/224/351/200/211/357/274/210/344/270/200/357/274/211/357/274/232/351/200/211/345/236/213/351/200/232/351/201/223/344/270/216/346/240/267/345/274/217.md +0 -178
- package/docs/ai/04-/351/207/215/350/246/201/351/227/256/347/255/224/351/200/211/357/274/210/344/272/214/357/274/211/357/274/232/344/272/244/344/272/222/346/200/247/350/203/275/344/270/216/346/225/260/346/215/256.md +0 -140
- package/docs/ai/05-/351/207/215/350/246/201/351/227/256/347/255/224/351/200/211/357/274/210/344/270/211/357/274/211/357/274/232/345/256/277/344/270/273/346/227/201/346/224/257/344/270/216/346/224/266/345/217/243.md +0 -43
|
@@ -1056,8 +1056,8 @@
|
|
|
1056
1056
|
])
|
|
1057
1057
|
const cmView2 = createGisView(cmHost2, {
|
|
1058
1058
|
points: [
|
|
1059
|
-
{ id: 'a', x: 0, y: 0, name: '甲', vn_kv: 500, size: 30, symbol: 'circle' },
|
|
1060
|
-
{ id: 'b', x: 110, y: 0, name: '乙', size: 12, symbol: 'square' },
|
|
1059
|
+
{ id: 'a', x: 0, y: 0, name: '甲', vn_kv: 500, size: 30, symbol: 'circle', source_only: '甲独有' },
|
|
1060
|
+
{ id: 'b', x: 110, y: 0, name: '乙', vn_kv: 220, size: 12, symbol: 'square', target_only: '乙独有' },
|
|
1061
1061
|
{ id: 'c', x: 220, y: 0, name: '丙' }
|
|
1062
1062
|
],
|
|
1063
1063
|
lines: cmChannelBuild.lines,
|
|
@@ -1118,8 +1118,9 @@
|
|
|
1118
1118
|
check('确认之后发一条 confirm,载荷里是合并结果(尺寸取双方最大、sizeScale 归 1)', Boolean(cmConfirmEvent) && cmConfirmEvent.item?.size === 30 && cmConfirmEvent.item?.sizeScale === 1, JSON.stringify({ size: cmConfirmEvent?.item?.size, sizeScale: cmConfirmEvent?.item?.sizeScale }))
|
|
1119
1119
|
check('合并结果的图标优先目标节点', cmConfirmEvent?.item?.symbol === 'square', JSON.stringify(cmConfirmEvent?.item?.symbol))
|
|
1120
1120
|
check('确认合并:来源节点并进目标节点', cmMerged.points.length === 2 && cmMerged.points[0].id === 'b', JSON.stringify(cmMerged.points.map(point => point.id)))
|
|
1121
|
-
check('
|
|
1121
|
+
check('确认合并:其它字段取保留节点、maxKeys 缺省不取电压最高', cmMerged.points[0].name === '乙' && cmMerged.points[0].vn_kv === 220 && cmMerged.points[0].target_only === '乙独有' && cmMerged.points[0].source_only === undefined, JSON.stringify(cmMerged.points[0]))
|
|
1122
1122
|
check('确认合并:图标大小取双方最大、图标类型取目标', cmMerged.points[0].size === 30 && cmMerged.points[0].symbol === 'square', `${cmMerged.points[0].size}/${cmMerged.points[0].symbol}`)
|
|
1123
|
+
check('确认合并:保留节点带 mergeInfo,记录来源、保留前快照与受影响线 / 通道', cmMerged.points[0].mergeInfo?.version === 1 && JSON.stringify(cmMerged.points[0].mergeInfo.sourceIds) === '["a"]' && cmMerged.points[0].mergeInfo.targetPoint?.id === 'b' && cmMerged.points[0].mergeInfo.targetPoint?.vn_kv === 220 && cmMerged.points[0].mergeInfo.points?.length === 1 && cmMerged.points[0].mergeInfo.lines?.length === 2 && cmMerged.points[0].mergeInfo.channels?.length === 3, JSON.stringify({ sources: cmMerged.points[0].mergeInfo?.sourceIds, targetVoltage: cmMerged.points[0].mergeInfo?.targetPoint?.vn_kv, lines: cmMerged.points[0].mergeInfo?.lines?.length, channels: cmMerged.points[0].mergeInfo?.channels?.length }))
|
|
1123
1124
|
check('确认合并:绑在来源上的线路改绑到目标', cmMerged.lines.length === 2 && cmMerged.lines[0].id === 'l1' && cmMerged.lines[0].from === 'b' && cmMerged.lines[0].to === 'c', JSON.stringify(cmMerged.lines))
|
|
1124
1125
|
check('确认合并:自环线路被删掉、结果里带读数', cmMerged.lines.every(line => line.id !== 'l2') && cmMergeDone.phase === 'merge' && cmMergeDone.removedLines === 1, JSON.stringify(cmMergeDone))
|
|
1125
1126
|
check('两端的通道撞成同一条:并成一条、默认各自保留分组', cmMergeDone.mergedChannels === 1 && cmMergeDone.channelsChanged === 2 && cmView2.getChannelGroups().length === 2, JSON.stringify({ merged: cmMergeDone.mergedChannels, changed: cmMergeDone.channelsChanged, groups: cmView2.getChannelGroups().length }))
|
|
@@ -1145,6 +1146,36 @@
|
|
|
1145
1146
|
JSON.stringify({ changes: cmMergeChangeIds, surviving: [...cmMergeSurvivingChannelIds], affected: cmMergeAffectedChannelIds }))
|
|
1146
1147
|
const cmApi = cmView2.mergePoints('c', 'b')
|
|
1147
1148
|
check('mergePoints 接口也能合并(不弹确认框)', cmApi.ok === true && cmView2.getData().points.length === 1 && cmView2.getData().lines.length === 0, JSON.stringify(cmApi).slice(0, 120))
|
|
1149
|
+
const cmCumulative = cmView2.getData().points[0]
|
|
1150
|
+
check('连续合并到同一保留节点:mergeInfo 累加两个来源,maxKeys 缺省仍保留目标电压', JSON.stringify(cmCumulative.mergeInfo?.sourceIds) === '["a","c"]' && cmCumulative.mergeInfo?.points?.length === 2 && cmCumulative.vn_kv === 220, JSON.stringify({ sources: cmCumulative.mergeInfo?.sourceIds, voltage: cmCumulative.vn_kv }))
|
|
1151
|
+
cmView2.updateOptions({ history: true })
|
|
1152
|
+
cmView2.selectFeatures('b')
|
|
1153
|
+
const cmAutoSplit = cmView2.splitPoint('b')
|
|
1154
|
+
await settle(80)
|
|
1155
|
+
const cmRestoredData = cmView2.getData()
|
|
1156
|
+
const cmRestoredChannels = cmView2.getChannels()
|
|
1157
|
+
const cmRestoredA = cmRestoredData.points.find(point => point.id === 'a')
|
|
1158
|
+
const cmRestoredB = cmRestoredData.points.find(point => point.id === 'b')
|
|
1159
|
+
const cmRestoredC = cmRestoredData.points.find(point => point.id === 'c')
|
|
1160
|
+
check('splitPoint 省略 parts:按 mergeInfo 自动恢复全部来源节点与合并前字段', cmAutoSplit.ok === true && cmAutoSplit.autoRestored === true && cmRestoredData.points.length === 3 && Boolean(cmRestoredA) && Boolean(cmRestoredB) && Boolean(cmRestoredC) && cmRestoredA?.vn_kv === 500 && cmRestoredB?.vn_kv === 220 && cmRestoredB?.mergeInfo === undefined, JSON.stringify({ auto: cmAutoSplit.autoRestored, points: cmRestoredData.points.map(point => point.id), voltages: [cmRestoredA?.vn_kv, cmRestoredB?.vn_kv, cmRestoredC?.vn_kv] }))
|
|
1161
|
+
check('splitPoint 自动解除默认不选中拆分结果(只摘掉来源节点的选中态)', cmView2.getSelectionData().pointCount === 0 && cmView2.getSelections().length === 0, JSON.stringify(cmView2.getSelectionData()))
|
|
1162
|
+
check('splitPoint 自动解除:线路与通道端点、成员及回线全部恢复', cmRestoredData.lines.length === 3 && cmRestoredData.lines.find(line => line.id === 'l2')?.from === 'a' && cmRestoredData.lines.find(line => line.id === 'l2')?.to === 'b' && cmRestoredData.lines.find(line => line.id === 'l1')?.from === 'a' && cmRestoredData.lines.find(line => line.id === 'l3')?.to === 'b' && cmRestoredChannels.length === 3 && cmRestoredChannels.find(channel => channel.id === 'ch-ab')?.from === 'a' && cmRestoredChannels.find(channel => channel.id === 'ch-ac')?.from === 'a' && cmRestoredChannels.find(channel => channel.id === 'ch-bc')?.to === 'b', JSON.stringify({ lines: cmRestoredData.lines.map(line => `${line.id}:${line.from}->${line.to}`), channels: cmRestoredChannels.map(channel => `${channel.id}:${channel.from}->${channel.to}`) }))
|
|
1163
|
+
check('splitPoint 自动解除:返回 mergeSourceIds / 可提交 changes', JSON.stringify(cmAutoSplit.mergeSourceIds) === '["a","c"]' && cmAutoSplit.changes?.points?.length === 3 && cmAutoSplit.changes?.lines?.length === 3 && cmAutoSplit.changes?.channels?.length === 3, JSON.stringify({ sources: cmAutoSplit.mergeSourceIds, changes: { points: cmAutoSplit.changes?.points?.length, lines: cmAutoSplit.changes?.lines?.length, channels: cmAutoSplit.changes?.channels?.length } }))
|
|
1164
|
+
cmView2.undo()
|
|
1165
|
+
await settle(80)
|
|
1166
|
+
const cmUnmergeUndo = cmView2.getData()
|
|
1167
|
+
const cmUnmergeUndoChannels = cmView2.getChannels()
|
|
1168
|
+
cmView2.redo()
|
|
1169
|
+
await settle(80)
|
|
1170
|
+
const cmUnmergeRedo = cmView2.getData()
|
|
1171
|
+
const cmUnmergeRedoChannels = cmView2.getChannels()
|
|
1172
|
+
check('splitPoint 自动解除是一步历史:撤销回到合并态、重做恢复自动解除结果', cmUnmergeUndo.points.length === 1 && cmUnmergeUndo.points[0]?.id === 'b' && cmUnmergeUndo.lines.length === 0 && cmUnmergeUndoChannels.length === 0 && cmUnmergeRedo.points.length === 3 && cmUnmergeRedo.lines.length === 3 && cmUnmergeRedoChannels.length === 3, JSON.stringify({ undo: [cmUnmergeUndo.points.length, cmUnmergeUndo.lines.length, cmUnmergeUndoChannels.length], redo: [cmUnmergeRedo.points.length, cmUnmergeRedo.lines.length, cmUnmergeRedoChannels.length] }))
|
|
1173
|
+
cmView2.undo()
|
|
1174
|
+
await settle(80)
|
|
1175
|
+
const cmAutoSplitSelected = cmView2.splitPoint('b', null, { select: true })
|
|
1176
|
+
await settle(80)
|
|
1177
|
+
const cmAutoSelectedData = cmView2.getSelectionData()
|
|
1178
|
+
check('splitPoint 自动解除 select:true 时选中全部恢复节点', cmAutoSplitSelected.ok === true && cmAutoSplitSelected.autoRestored === true && cmAutoSelectedData.pointCount === 3 && cmAutoSplitSelected.pointIds.every(id => cmAutoSelectedData.points.some(point => String(point.id) === String(id))), JSON.stringify({ pointCount: cmAutoSelectedData.pointCount, ids: cmAutoSplitSelected.pointIds }))
|
|
1148
1179
|
|
|
1149
1180
|
/* 移除节点:绑在它上面的线路与声明了它当端点的通道一起收拾 */
|
|
1150
1181
|
const cmHost7 = document.createElement('div')
|
|
@@ -1168,6 +1199,107 @@
|
|
|
1168
1199
|
cmView7.destroy()
|
|
1169
1200
|
cmHost7.remove()
|
|
1170
1201
|
|
|
1202
|
+
/* merge.maxKeys:缺省不取最高;数组 / 逗号字符串都按列出的数值字段取双方最高 */
|
|
1203
|
+
const mkHost = document.createElement('div')
|
|
1204
|
+
mkHost.style.cssText = 'width:460px;height:300px'
|
|
1205
|
+
document.body.appendChild(mkHost)
|
|
1206
|
+
const mkView = createGisView(mkHost, {
|
|
1207
|
+
points: [
|
|
1208
|
+
{ id: 'mk-a', x: 0, y: 0, name: '来源甲', vn_kv: 500, value: 9, loading: 0.2 },
|
|
1209
|
+
{ id: 'mk-b', x: 80, y: 0, name: '目标乙', vn_kv: 220, value: 3, loading: 0.8 },
|
|
1210
|
+
{ id: 'mk-c', x: 0, y: 80, name: '来源丙', vn_kv: 525, value: 12, loading: 0.1 },
|
|
1211
|
+
{ id: 'mk-d', x: 80, y: 80, name: '目标丁', vn_kv: 1050, value: 4, loading: 0.9 }
|
|
1212
|
+
],
|
|
1213
|
+
merge: { distance: 8, maxKeys: ['vn_kv', 'value'] },
|
|
1214
|
+
fitPadding: 60,
|
|
1215
|
+
showGrid: false
|
|
1216
|
+
})
|
|
1217
|
+
await settle(80)
|
|
1218
|
+
const mkFirst = mkView.mergePoints('mk-a', 'mk-b')
|
|
1219
|
+
const mkFirstItem = mkView.getData().points.find(point => point.id === 'mk-b')
|
|
1220
|
+
check('merge.maxKeys 数组:列出的 vn_kv / value 取双方最高,其余字段取目标', mkFirst.ok === true && mkFirstItem?.vn_kv === 500 && mkFirstItem?.value === 9 && mkFirstItem?.loading === 0.8 && mkFirstItem?.name === '目标乙', JSON.stringify(mkFirstItem))
|
|
1221
|
+
mkView.updateOptions({ merge: { enabled: true, maxKeys: 'vn_kv, value' } })
|
|
1222
|
+
const mkSecond = mkView.mergePoints('mk-c', 'mk-d')
|
|
1223
|
+
const mkSecondItem = mkView.getData().points.find(point => point.id === 'mk-d')
|
|
1224
|
+
check('merge.maxKeys 逗号字符串:同样只对列出的数值字段取最高', mkSecond.ok === true && mkSecondItem?.vn_kv === 1050 && mkSecondItem?.value === 12 && mkSecondItem?.loading === 0.9 && mkSecondItem?.name === '目标丁', JSON.stringify(mkSecondItem))
|
|
1225
|
+
mkView.destroy()
|
|
1226
|
+
mkHost.remove()
|
|
1227
|
+
|
|
1228
|
+
/* splitPoint.select:缺省不选中结果,也不清掉无关的线路选中;显式 true 才选中全部拆分结果 */
|
|
1229
|
+
const ssHost = document.createElement('div')
|
|
1230
|
+
ssHost.style.cssText = 'width:420px;height:260px'
|
|
1231
|
+
document.body.appendChild(ssHost)
|
|
1232
|
+
const ssView = createGisView(ssHost, {
|
|
1233
|
+
points: [{ id: 'ss-a', x: 0, y: 0, name: '拆分源' }, { id: 'ss-b', x: 80, y: 0 }],
|
|
1234
|
+
lines: [{ id: 'ss-line', from: 'ss-a', to: 'ss-b' }],
|
|
1235
|
+
fitPadding: 50,
|
|
1236
|
+
showGrid: false
|
|
1237
|
+
})
|
|
1238
|
+
await settle(80)
|
|
1239
|
+
ssView.selectFeatures(['ss-a', 'ss-line'])
|
|
1240
|
+
const ssDefault = ssView.splitPoint('ss-a', [
|
|
1241
|
+
{ x: -1, y: 0, name: 'A' },
|
|
1242
|
+
{ x: 1, y: 0, name: 'B' }
|
|
1243
|
+
])
|
|
1244
|
+
const ssDefaultSelection = ssView.getSelectionData()
|
|
1245
|
+
check('splitPoint 缺省不选中拆分结果,其它无关选中保持', ssDefault.ok === true && ssDefaultSelection.pointCount === 0 && ssDefaultSelection.lineCount === 1, JSON.stringify(ssDefaultSelection))
|
|
1246
|
+
ssView.undo()
|
|
1247
|
+
await settle(60)
|
|
1248
|
+
ssView.selectFeatures('ss-a')
|
|
1249
|
+
const ssSelected = ssView.splitPoint('ss-a', [
|
|
1250
|
+
{ x: -1, y: 0, name: 'A2' },
|
|
1251
|
+
{ x: 1, y: 0, name: 'B2' }
|
|
1252
|
+
], { select: true })
|
|
1253
|
+
const ssSelectedData = ssView.getSelectionData()
|
|
1254
|
+
const ssSelectedIndexes = [...ssSelectedData.pointIndexes].sort((left, right) => left - right)
|
|
1255
|
+
const ssResultIndexes = [...ssSelected.indexes].sort((left, right) => left - right)
|
|
1256
|
+
check('splitPoint({ select: true }) 选中全部拆分结果', ssSelected.ok === true && ssSelectedData.pointCount === 2 && JSON.stringify(ssSelectedIndexes) === JSON.stringify(ssResultIndexes) && ssSelectedData.lineCount === 0, JSON.stringify({ selected: ssSelectedIndexes, result: ssResultIndexes }))
|
|
1257
|
+
ssView.destroy()
|
|
1258
|
+
ssHost.remove()
|
|
1259
|
+
|
|
1260
|
+
/* 通道简略:只折叠严格超过阈值的分组;没有 groups 的通道按一整组算 */
|
|
1261
|
+
const csHost = document.createElement('div')
|
|
1262
|
+
csHost.style.cssText = 'width:520px;height:300px'
|
|
1263
|
+
document.body.appendChild(csHost)
|
|
1264
|
+
const csLineIds = Array.from({ length: 8 }, (_item, index) => `cs-l${index + 1}`)
|
|
1265
|
+
const csView = createGisView(csHost, {
|
|
1266
|
+
points: [{ id: 'cs-a', x: 0, y: 0 }, { id: 'cs-b', x: 180, y: 0 }],
|
|
1267
|
+
lines: csLineIds.map(id => ({ id, from: 'cs-a', to: 'cs-b' })),
|
|
1268
|
+
channels: [
|
|
1269
|
+
{ id: 'cs-plain', from: 'cs-a', to: 'cs-b', lineIds: csLineIds.slice(0, 3) },
|
|
1270
|
+
{
|
|
1271
|
+
id: 'cs-grouped',
|
|
1272
|
+
from: 'cs-a',
|
|
1273
|
+
to: 'cs-b',
|
|
1274
|
+
groups: [
|
|
1275
|
+
{ id: 'cs-g1', lineIds: csLineIds.slice(3, 6) },
|
|
1276
|
+
{ id: 'cs-g2', lineIds: csLineIds.slice(6) }
|
|
1277
|
+
]
|
|
1278
|
+
}
|
|
1279
|
+
],
|
|
1280
|
+
channelSimplify: { enabled: true, threshold: 2, style: { color: '#ff3300', width: 5 } },
|
|
1281
|
+
fitPadding: 60,
|
|
1282
|
+
showGrid: false
|
|
1283
|
+
})
|
|
1284
|
+
await settle(120)
|
|
1285
|
+
const csStyles = csLineIds.map((_id, index) => csView.getLineStyle(index))
|
|
1286
|
+
const csGroups = csView.getChannelGroups()
|
|
1287
|
+
const csSnapshot = csView.getChannels()
|
|
1288
|
+
const csSnapshotLaneCounts = csSnapshot.map(group => new Set(group.paths.map(path => JSON.stringify(path))).size)
|
|
1289
|
+
const csRepresentative = csStyles[0]
|
|
1290
|
+
const csDataCount = csView.getData().lines.length
|
|
1291
|
+
check('通道简略:超过 threshold 的分组只画一条代表线,无 groups 的通道也按一组折叠', csStyles.filter(Boolean).length === 4 && csStyles[1] === null && csStyles[2] === null && csStyles[3] !== null && csStyles[4] === null && csStyles[5] === null && csStyles[6] !== null && csStyles[7] !== null, JSON.stringify(csStyles.map(style => style ? style.index : null)))
|
|
1292
|
+
check('通道简略:代表线套自定义颜色 / 线宽,原始数据与分组成员不变', csRepresentative?.width === 5 && Math.abs(csRepresentative.color[0] - 1) < 1e-6 && Math.abs(csRepresentative.color[1] - 0.2) < 1e-6 && csDataCount === 8 && csGroups.length === 3 && csGroups.map(group => group.lineIds.length).join(',') === '3,3,2', JSON.stringify({ style: csRepresentative, data: csDataCount, groups: csGroups.map(group => group.lineIds.length) }))
|
|
1293
|
+
check('通道简略:getChannels 快照为每条成员回线保留完整路径与各自车道',
|
|
1294
|
+
csSnapshot.length === 3 && csSnapshot.every((group, index) => group.paths.length === group.lineIndexes.length && group.paths.every(path => Array.isArray(path) && path.length >= 2) && csSnapshotLaneCounts[index] === group.paths.length),
|
|
1295
|
+
JSON.stringify(csSnapshot.map((group, index) => [group.lineIndexes.length, group.paths.length, csSnapshotLaneCounts[index], group.paths.map(path => path.length)])))
|
|
1296
|
+
csView.updateOptions({ channelSimplify: false })
|
|
1297
|
+
await settle(100)
|
|
1298
|
+
const csRestoredStyles = csLineIds.map((_id, index) => csView.getLineStyle(index))
|
|
1299
|
+
check('通道简略关闭后全部回线恢复,数据与分组读数仍不变', csRestoredStyles.every(Boolean) && csView.getData().lines.length === 8 && csView.getChannelGroups().length === 3, JSON.stringify(csRestoredStyles.map(style => style ? style.index : null)))
|
|
1300
|
+
csView.destroy()
|
|
1301
|
+
csHost.remove()
|
|
1302
|
+
|
|
1171
1303
|
diag('复制 / 合并', `复制事件 ${cmEvents.length} 条,合并读数 linesChanged=${cmMergeDone.linesChanged} removedLines=${cmMergeDone.removedLines} removedChannels=${cmMergeDone.removedChannels} mergedChannels=${cmMergeDone.mergedChannels}`)
|
|
1172
1304
|
cmView2.destroy()
|
|
1173
1305
|
cmHost2.remove()
|
|
@@ -2264,7 +2396,7 @@
|
|
|
2264
2396
|
check('拆分发 channelGroupChange(action: splitGroups 带新组表)', cgBus.length === 1 && cgSplit?.action?.type === 'splitGroups' && cgSplit.action.groupIds.length === 2 && cgSplit.channelId === 'ch-1' && cgSplit.groups.length === 2, JSON.stringify({ type: cgSplit?.action?.type, groupIds: cgSplit?.action?.groupIds }))
|
|
2265
2397
|
check('onChannelGroupChange 回调和事件总线收到同一份载荷', cgOption.length === 1 && cgOption[0] === cgBus[0])
|
|
2266
2398
|
|
|
2267
|
-
//
|
|
2399
|
+
// 选中一条回线再合并:选中项不再挂通道字段,实时归属要跟着真值走(回归守卫)
|
|
2268
2400
|
cgView.selectFeatures('c2')
|
|
2269
2401
|
await settle(60)
|
|
2270
2402
|
const cgTruth = () => {
|
|
@@ -2277,7 +2409,10 @@
|
|
|
2277
2409
|
cgView.mergeChannelGroups('ch-1', [cgAfterSplit[1].id], cgAfterSplit[0].id)
|
|
2278
2410
|
await settle(60)
|
|
2279
2411
|
const cgMerged = cgTruth()
|
|
2280
|
-
|
|
2412
|
+
const cgOwner = cgView.getLineChannel('c2')
|
|
2413
|
+
const cgOwnerGroups = cgView.getChannelGroups('ch-1')
|
|
2414
|
+
const cgOwnerGroupIndex = cgOwnerGroups.findIndex(group => String(group.id) === String(cgOwner?.groupId))
|
|
2415
|
+
check('合并回线之后 getLineChannel 的 groupId / groupIndex 一起刷新,选中项仍不挂通道字段', String(cgOwner?.groupId) === cgMerged?.id && cgOwnerGroupIndex === cgMerged?.index && cgMirror()?.groupId === undefined && cgMirror()?.groupIndex === undefined, `归属 ${cgOwner?.groupId}/${cgOwnerGroupIndex} / 真值 ${cgMerged?.id}/${cgMerged?.index} / 镜像 ${cgMirror()?.groupId}/${cgMirror()?.groupIndex}`)
|
|
2281
2416
|
const cgMerge = cgBus[cgBus.length - 1]
|
|
2282
2417
|
check('合并发 channelGroupChange(action: mergeGroups 带目标组与来源组)', cgMerge?.action?.type === 'mergeGroups' && String(cgMerge.action.groupId) === String(cgMerged?.id) && cgMerge.action.groupIds.length === 2, JSON.stringify({ type: cgMerge?.action?.type, groupId: cgMerge?.action?.groupId, groupIds: cgMerge?.action?.groupIds }))
|
|
2283
2418
|
|
|
@@ -2409,6 +2544,24 @@
|
|
|
2409
2544
|
goView.destroy()
|
|
2410
2545
|
goHost.remove()
|
|
2411
2546
|
|
|
2547
|
+
const rocHost = document.createElement('div')
|
|
2548
|
+
rocHost.style.cssText = 'width:420px;height:280px'
|
|
2549
|
+
document.body.appendChild(rocHost)
|
|
2550
|
+
const rocView = createGisView(rocHost, {
|
|
2551
|
+
points: [{ id: 'ra', x: -80, y: 0 }, { id: 'rb', x: 80, y: 0 }],
|
|
2552
|
+
lines: [{ id: 'r1', from: 'ra', to: 'rb' }, { id: 'r2', from: 'ra', to: 'rb' }, { id: 'r3', from: 'ra', to: 'rb' }],
|
|
2553
|
+
channels: [{ id: 'roc', groups: [{ id: 'roc-g1', lineIds: ['r1', 'r2'] }, { id: 'roc-g2', lineIds: ['r3'] }] }],
|
|
2554
|
+
showGrid: false,
|
|
2555
|
+
fitPadding: 60
|
|
2556
|
+
})
|
|
2557
|
+
await settle(100)
|
|
2558
|
+
const rocRemoved = rocView.removeChannel('roc')
|
|
2559
|
+
check('removeChannel 删除只写 groups 的通道时连同组内成员回线一起删净',
|
|
2560
|
+
rocRemoved.ok === true && rocRemoved.lineIds.length === 3 && rocView.getData().lines.length === 0 && rocView.getChannelGroups('roc').length === 0,
|
|
2561
|
+
JSON.stringify({ lineIds: rocRemoved.lineIds, lines: rocView.getData().lines.length, groups: rocView.getChannelGroups('roc').length }))
|
|
2562
|
+
rocView.destroy()
|
|
2563
|
+
rocHost.remove()
|
|
2564
|
+
|
|
2412
2565
|
/* 这一轮新增:updateChannel 第三段(通道声明 + 逐条成员回线一次下发) */
|
|
2413
2566
|
const ucHost = document.createElement('div')
|
|
2414
2567
|
ucHost.style.cssText = 'width:420px;height:280px'
|
|
@@ -2657,7 +2810,7 @@
|
|
|
2657
2810
|
chView.destroy()
|
|
2658
2811
|
chHost.remove()
|
|
2659
2812
|
|
|
2660
|
-
|
|
2813
|
+
/* 这一轮新增:屏幕 ↔ 真实坐标互转 / addChannel 第二段 lineData / beginAddChannel 交接模式 */
|
|
2661
2814
|
const hwHost = document.createElement('div')
|
|
2662
2815
|
hwHost.style.cssText = 'width:420px;height:280px'
|
|
2663
2816
|
document.body.appendChild(hwHost)
|
|
@@ -2701,18 +2854,18 @@
|
|
|
2701
2854
|
const hwClientViaScreen = hwView.screenToClient(hwScreen[0], hwScreen[1])
|
|
2702
2855
|
check('screenToClient(project(世界)) 与 worldToClient(同一世界) 同一结果:反向也等价', Math.abs(hwClientViaScreen[0] - hwClient[0]) < 1e-6 && Math.abs(hwClientViaScreen[1] - hwClient[1]) < 1e-6, JSON.stringify({ viaScreen: hwClientViaScreen, viaWorld: hwClient }))
|
|
2703
2856
|
// ② addChannel / addLine 第二段:连同线路一起新建
|
|
2704
|
-
const hwAdd = hwView.addChannel({ from: 'ha', to: 'hb', name: '第二段通道'
|
|
2857
|
+
const hwAdd = hwView.addChannel({ from: 'ha', to: 'hb', name: '第二段通道' }, [
|
|
2705
2858
|
{ id: 'hw-l1', color: '#112233', width: 4 },
|
|
2706
2859
|
{ id: 'hw-l2', color: '#445566' }
|
|
2707
2860
|
])
|
|
2708
2861
|
const hwLine = id => hwView.getOptions().lines.find(line => String(line.id) === id)
|
|
2709
2862
|
const hwChannelOf = channelId => hwView.getOptions().channels.find(channel => String(channel.id) === String(channelId))
|
|
2710
|
-
check('addChannel(
|
|
2711
|
-
const hwAddOne = hwView.addLine({ from: 'hc', to: 'hd'
|
|
2863
|
+
check('addChannel(spec, lineData):给几条建几条,逐条字段各自落地', hwAdd.ok === true && hwAdd.lineIds.length === 2 && hwAdd.circuits === 2 && (hwChannelOf(hwAdd.channelId)?.lineIds ?? []).length === 2 && hwLine('hw-l1')?.color === '#112233' && hwLine('hw-l1')?.width === 4 && hwLine('hw-l2')?.color === '#445566', JSON.stringify({ ids: hwAdd.lineIds, circuits: hwAdd.circuits, l1: hwLine('hw-l1') }))
|
|
2864
|
+
const hwAddOne = hwView.addLine({ from: 'hc', to: 'hd' }, { id: 'hw-solo', color: '#abcdef' })
|
|
2712
2865
|
check('第二段收单条(非数组)也认:addLine 按它建一条独立线路', hwAddOne.ok === true && hwAddOne.lineIds.length === 1 && hwAddOne.lineIds[0] === 'hw-solo' && hwAddOne.kind === 'line' && hwLine('hw-solo')?.color === '#abcdef', JSON.stringify({ ids: hwAddOne.lineIds, circuits: hwAddOne.circuits }))
|
|
2713
|
-
const hwBatch = hwView.addChannels([{ from: 'ha', to: 'hc',
|
|
2714
|
-
check('addChannels
|
|
2715
|
-
|
|
2866
|
+
const hwBatch = hwView.addChannels([{ from: 'ha', to: 'hc', lineData: [{ id: 'hw-b1' }, { id: 'hw-b2' }, { id: 'hw-b3' }] }])
|
|
2867
|
+
check('addChannels 的每一项写 lineData:三条一起建', hwBatch.ok === true && hwBatch.lineIds.length === 3 && ['hw-b1', 'hw-b2', 'hw-b3'].every(id => hwLine(id) !== undefined), JSON.stringify({ ids: hwBatch.lineIds }))
|
|
2868
|
+
// ③ beginAddChannel:lineData 对齐条数、handoff 交接
|
|
2716
2869
|
const hwClickPoint = id => {
|
|
2717
2870
|
const item = hwView.getData().points.find(point => point.id === id)
|
|
2718
2871
|
return hwView.project(item.x, item.y)
|
|
@@ -2748,9 +2901,13 @@
|
|
|
2748
2901
|
const hwHandAdd = hwReady ? hwView.addChannel({ from: hwReady.from.id, to: hwReady.to.id, name: '交接通道' }) : { ok: false }
|
|
2749
2902
|
check('交接之后自己走 addChannel:真实的通道与回线落地', hwHandAdd.ok === true && hwHandAdd.channelId !== null && hwView.getOptions().lines.length === hwLineBefore + 1, JSON.stringify({ ok: hwHandAdd.ok, channelId: hwHandAdd.channelId }))
|
|
2750
2903
|
hwDraftEvents.length = 0
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2904
|
+
hwView.beginAddChannel({ lineItems: [{ id: 'hw-old-d1' }, { id: 'hw-old-d2' }] })
|
|
2905
|
+
await settle(40)
|
|
2906
|
+
check('beginAddChannel 旧 lineItems 已移除:不进入草稿的 lineData、回数按缺省 1', hwView.getAddChannelDraft()?.lineData === null && hwView.getAddChannelDraft()?.circuits === 1, JSON.stringify({ circuits: hwView.getAddChannelDraft()?.circuits, lineData: hwView.getAddChannelDraft()?.lineData }))
|
|
2907
|
+
hwView.cancelAddChannel()
|
|
2908
|
+
hwView.beginAddChannel({ autoCommit: true, lineData: [{ id: 'hw-d1', color: '#0a0b0c' }, { id: 'hw-d2', color: '#0d0e0f' }] })
|
|
2909
|
+
await settle(40)
|
|
2910
|
+
check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits = 2)', hwView.getAddChannelDraft()?.circuits === 2 && (hwView.getAddChannelDraft()?.lineData?.length ?? 0) === 2, JSON.stringify({ circuits: hwView.getAddChannelDraft()?.circuits }))
|
|
2754
2911
|
hwClick(hwClickPoint('hb'))
|
|
2755
2912
|
await settle(30)
|
|
2756
2913
|
hwClick(hwClickPoint('hd'))
|
|
@@ -2812,17 +2969,17 @@
|
|
|
2812
2969
|
check('位置对账:新加进来的节点以加入时坐标入账(刚加不算,之后再挪才算)', mvAfterAdd === 0 && mvMovedNew.length === 1 && mvMovedNew[0].id === 'me' && mvMovedNew[0].x === 620, JSON.stringify({ afterAdd: mvAfterAdd, afterMove: mvMovedNew.map(item => item.id) }))
|
|
2813
2970
|
check('位置对账:clearMovedPoints 返回实例本身(可以链式调)', mvView.clearMovedPoints('me') === mvView && mvView.clearMovedPoints().getMovedPoints().length === 0, '')
|
|
2814
2971
|
|
|
2815
|
-
const mvAdd1 = mvView.
|
|
2972
|
+
const mvAdd1 = mvView.addChannelGroupLines('mch')
|
|
2816
2973
|
const mvCh1 = mvChannel('mch')
|
|
2817
|
-
check('
|
|
2818
|
-
check('
|
|
2819
|
-
const mvAdd2 = mvView.
|
|
2974
|
+
check('addChannelGroupLines 已有通道:新分组,通道 lineIds 与 groups 一起追加', mvAdd1.ok === true && mvAdd1.resolution?.type === 'newGroup' && mvCh1.lineIds.length === 3 && mvCh1.groups.length === 2 && mvCh1.groups[1].lineIds.length === 1, JSON.stringify({ type: mvAdd1.resolution?.type, lineIds: mvCh1.lineIds, groups: mvCh1.groups.map(group => group.lineIds) }))
|
|
2975
|
+
check('addChannelGroupLines 已有通道:结果回带更新后的通道 lineIds(channelLineIds 与声明一致)', Array.isArray(mvAdd1.channelLineIds) && mvAdd1.channelLineIds.length === 3 && JSON.stringify(mvAdd1.channelLineIds) === JSON.stringify(mvCh1.lineIds), JSON.stringify(mvAdd1.channelLineIds))
|
|
2976
|
+
const mvAdd2 = mvView.joinChannelGroupLines('mch', 0)
|
|
2820
2977
|
const mvCh2 = mvChannel('mch')
|
|
2821
|
-
check('
|
|
2822
|
-
const mvAdd3 = mvView.
|
|
2823
|
-
check('
|
|
2824
|
-
const mvAdd4 = mvView.addChannel({ from: 'mb', to: 'mc',
|
|
2825
|
-
check('addChannel
|
|
2978
|
+
check('joinChannelGroupLines 按分组序号并进指定分组(通道 lineIds 继续追加)', mvAdd2.ok === true && mvAdd2.resolution?.type === 'joinGroup' && mvAdd2.groupId === mvCh2.groups[0].id && mvCh2.lineIds.length === 4 && mvCh2.groups[0].lineIds.length === 3, JSON.stringify({ type: mvAdd2.resolution?.type, groupId: mvAdd2.groupId, lineIds: mvCh2.lineIds, groups: mvCh2.groups.map(group => group.lineIds) }))
|
|
2979
|
+
const mvAdd3 = mvView.addChannelGroupLines('nope')
|
|
2980
|
+
check('分组方法指定了不存在的通道:失败并给出可读原因', mvAdd3.ok === false && typeof mvAdd3.reason === 'string' && mvAdd3.reason.includes('找不到通道'), JSON.stringify({ ok: mvAdd3.ok, reason: mvAdd3.reason }))
|
|
2981
|
+
const mvAdd4 = mvView.addChannel({ from: 'mb', to: 'mc', id: 'mch-x' })
|
|
2982
|
+
check('addChannel 两个节点之间还没有通道:id 是新建通道的 id', mvAdd4.ok === true && mvAdd4.channelId === 'mch-x' && Array.isArray(mvAdd4.channelLineIds) && mvAdd4.channelLineIds.length === 1 && mvChannel('mch-x').lineIds.length === 1, JSON.stringify({ ok: mvAdd4.ok, channelId: mvAdd4.channelId, channelLineIds: mvAdd4.channelLineIds }))
|
|
2826
2983
|
mvView.destroy()
|
|
2827
2984
|
mvHost.remove()
|
|
2828
2985
|
|
|
@@ -3096,8 +3253,8 @@
|
|
|
3096
3253
|
diag('右键回线中段', `世界 ${rcMidWorld.map(value => Number(value).toFixed(2)).join(',')} · 回线折点 ${Array.isArray(rcLine?.path) ? rcLine.path.length : 0}`)
|
|
3097
3254
|
await rcRightClick(rcView.project(rcMidWorld[0], rcMidWorld[1]))
|
|
3098
3255
|
const rcContext = rcHits[1]?.channelContext
|
|
3099
|
-
check('右键落在回线中段:同一份 onContextMenu 载荷里带 channelContext(组数 / 当前分组 / 可用交互)', rcHits.length === 2 && rcContext?.channelId === 'ch-rc' && rcHits[1]?.
|
|
3100
|
-
check('右键通道只发这一条(不再额外发 onChannelContextMenu
|
|
3256
|
+
check('右键落在回线中段:同一份 onContextMenu 载荷里带 channelContext(组数 / 当前分组 / 可用交互)', rcHits.length === 2 && rcContext?.channelId === 'ch-rc' && rcHits[1]?.item?.id === 'rc-1' && rcContext?.groupCount === 1 && (rcContext?.actions ?? []).length > 0, JSON.stringify({ count: rcHits.length, channel: rcContext?.channelId, line: rcHits[1]?.item?.id, groups: rcContext?.groupCount }))
|
|
3257
|
+
check('右键通道只发这一条(不再额外发 onChannelContextMenu),线路下标读 index、id 读 item.id', rcHits[1]?.type === 'line' && rcHits[1]?.channelId === 'ch-rc' && rcHits[1]?.item?.id === 'rc-1' && Number.isInteger(rcHits[1]?.index) && rcHits[1]?.lineId === undefined && rcHits[1]?.lineIndex === undefined && rcHits[1]?.action?.target === 'channel', JSON.stringify({ count: rcHits.length, type: rcHits[1]?.type, index: rcHits[1]?.index, item: rcHits[1]?.item?.id, target: rcHits[1]?.action?.target }))
|
|
3101
3258
|
rcView.destroy()
|
|
3102
3259
|
rcHost.remove()
|
|
3103
3260
|
|
|
@@ -3519,18 +3676,19 @@
|
|
|
3519
3676
|
await settle(60)
|
|
3520
3677
|
}
|
|
3521
3678
|
await rdClick(rdView.project(0, 0))
|
|
3522
|
-
|
|
3679
|
+
const rdClickInfo = rdView.getSelectionInfo()
|
|
3680
|
+
check('单击载荷:selected true / wasSelected false(点之前没选中),总读数走 getSelectionInfo()', rdClicks[0]?.selected === true && rdClicks[0]?.wasSelected === false && rdClicks[0]?.selection === undefined && rdClickInfo.count === 1 && rdClickInfo.points === 1, JSON.stringify({ selected: rdClicks[0]?.selected, was: rdClicks[0]?.wasSelected, count: rdClickInfo.count }))
|
|
3523
3681
|
await rdClick(rdView.project(0, 0))
|
|
3524
|
-
check('再点已经选中的节点:wasSelected 也是 true(点之前就在选中集里)', rdClicks[1]?.selected === true && rdClicks[1]?.wasSelected === true && rdClicks[1]?.selection
|
|
3682
|
+
check('再点已经选中的节点:wasSelected 也是 true(点之前就在选中集里)', rdClicks[1]?.selected === true && rdClicks[1]?.wasSelected === true && rdClicks[1]?.selection === undefined && rdView.getSelectionInfo().count === 1, JSON.stringify({ selected: rdClicks[1]?.selected, was: rdClicks[1]?.wasSelected }))
|
|
3525
3683
|
const rdSelects = []
|
|
3526
3684
|
rdView.on('selectChange', payload => rdSelects.push(payload))
|
|
3527
3685
|
await rdClick(rdView.project(0, 0), { ctrlKey: true })
|
|
3528
3686
|
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 }))
|
|
3529
3687
|
rdView.selectFeatures(['rd-a', 'rd-b'])
|
|
3530
3688
|
await rdRightClick(rdView.project(320, 0))
|
|
3531
|
-
check('右键落在没选中的节点上:selected / wasSelected 都是 false
|
|
3689
|
+
check('右键落在没选中的节点上:selected / wasSelected 都是 false,总读数走 getSelectionInfo()', rdMenus[0]?.type === 'point' && rdMenus[0]?.item?.id === 'rd-c' && rdMenus[0]?.selected === false && rdMenus[0]?.wasSelected === false && rdMenus[0]?.selection === undefined && rdView.getSelectionInfo().count === 2, JSON.stringify({ id: rdMenus[0]?.item?.id, selected: rdMenus[0]?.selected, count: rdView.getSelectionInfo().count }))
|
|
3532
3690
|
await rdRightClick(rdView.project(0, 0))
|
|
3533
|
-
check('右键落在已选中的节点上:wasSelected true(据此决定要不要保住整份多选)', rdMenus[1]?.selected === true && rdMenus[1]?.wasSelected === true && rdMenus[1]?.selection
|
|
3691
|
+
check('右键落在已选中的节点上:wasSelected true(据此决定要不要保住整份多选)', rdMenus[1]?.selected === true && rdMenus[1]?.wasSelected === true && rdMenus[1]?.selection === undefined && rdView.getSelectionInfo().count === 2, JSON.stringify({ selected: rdMenus[1]?.selected, was: rdMenus[1]?.wasSelected }))
|
|
3534
3692
|
const rdPointInfo = rdView.getSelectionData()
|
|
3535
3693
|
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))
|
|
3536
3694
|
rdView.selectFeatures(['rd-1', 'rd-2'])
|
|
@@ -3878,6 +4036,46 @@
|
|
|
3878
4036
|
check('exportData:format 写错直接抛异常(不静默兜底)', /view/.test(rdThrew) && /new/.test(rdThrew) && /test/.test(rdThrew), rdThrew)
|
|
3879
4037
|
rdView.destroy()
|
|
3880
4038
|
rdHost.remove()
|
|
4039
|
+
|
|
4040
|
+
/* 本轮:视口导出的线路 / 通道端点模式。半区只盖住左端点,和整区对照。 */
|
|
4041
|
+
const veHost = document.createElement('div')
|
|
4042
|
+
veHost.style.cssText = 'width:480px;height:300px'
|
|
4043
|
+
document.body.appendChild(veHost)
|
|
4044
|
+
const veView = createGisView(veHost, {
|
|
4045
|
+
points: [
|
|
4046
|
+
{ id: 've-a', x: -130, y: 0, size: 16 },
|
|
4047
|
+
{ id: 've-b', x: 130, y: 0, size: 16 }
|
|
4048
|
+
],
|
|
4049
|
+
lines: [
|
|
4050
|
+
{ id: 've-bound', from: 've-a', to: 've-b', width: 8 },
|
|
4051
|
+
{ id: 've-path', path: [[-80, 60], [80, 60]], width: 8 },
|
|
4052
|
+
{ id: 've-ch-line', from: 've-a', to: 've-b', width: 8 }
|
|
4053
|
+
],
|
|
4054
|
+
channels: [{ id: 've-ch', lineIds: ['ve-ch-line'] }],
|
|
4055
|
+
showGrid: false,
|
|
4056
|
+
fitOnInit: false,
|
|
4057
|
+
initialView: { center: [0, 0], scale: 1 },
|
|
4058
|
+
width: 480,
|
|
4059
|
+
height: 300
|
|
4060
|
+
})
|
|
4061
|
+
await settle(120)
|
|
4062
|
+
const veHalf = { minX: -200, minY: -100, maxX: 0, maxY: 100 }
|
|
4063
|
+
const veAll = { minX: -200, minY: -100, maxX: 200, maxY: 100 }
|
|
4064
|
+
const veLineIds = result => (result.lines ?? []).map(row => row.line_id).sort()
|
|
4065
|
+
const veLegacy = veView.exportData({ scope: 'viewport', rect: veHalf, selectionKinds: ['line'] })
|
|
4066
|
+
check('exportData 视口 line 旧模式:几何沾到视口的独立线路都导出', veLegacy.counts.points === 0 && veLegacy.counts.lines === 2 && veLineIds(veLegacy).join(',') === 've-bound,ve-path' && veLegacy.counts.channels === 0, JSON.stringify({ counts: veLegacy.counts, lines: veLineIds(veLegacy) }))
|
|
4067
|
+
const veLineHalf = veView.exportData({ scope: 'viewport', rect: veHalf, selectionKinds: ['lineEndpoints'] })
|
|
4068
|
+
check('exportData 视口 lineEndpoints:只盖住一端时不导出独立线路', veLineHalf.counts.lines === 0 && veLineHalf.lines.length === 0 && veLineHalf.counts.channels === 0, JSON.stringify(veLineHalf.counts))
|
|
4069
|
+
const veLineAll = veView.exportData({ scope: 'viewport', rect: veAll, selectionKinds: ['line-endpoints'] })
|
|
4070
|
+
check('exportData 视口 lineEndpoints:两端都在视口内才导出,纯 path 线路仍跳过', veLineAll.counts.points === 0 && veLineAll.counts.lines === 1 && veLineIds(veLineAll).join(',') === 've-bound', JSON.stringify({ counts: veLineAll.counts, lines: veLineIds(veLineAll) }))
|
|
4071
|
+
const veChannelLegacy = veView.exportData({ scope: 'viewport', rect: veHalf, selectionKinds: ['channel'] })
|
|
4072
|
+
check('exportData 视口 channel 旧模式:成员回线沾到视口就导出整条通道', veChannelLegacy.counts.channels === 1 && veChannelLegacy.channels[0].channel_name === 've-ch' && veChannelLegacy.counts.lines === 1, JSON.stringify({ counts: veChannelLegacy.counts, channels: veChannelLegacy.channels.map(row => row.channel_name) }))
|
|
4073
|
+
const veChannelHalf = veView.exportData({ scope: 'viewport', rect: veHalf, selectionKinds: ['channelEndpoints'] })
|
|
4074
|
+
check('exportData 视口 channelEndpoints:只盖住一端时不导出通道', veChannelHalf.counts.channels === 0 && veChannelHalf.counts.lines === 0, JSON.stringify(veChannelHalf.counts))
|
|
4075
|
+
const veChannelAll = veView.exportData({ scope: 'viewport', rect: veAll, selectionKinds: ['channel-endpoints'] })
|
|
4076
|
+
check('exportData 视口 channelEndpoints:两端都在视口内才导出整条通道与成员回线', veChannelAll.counts.points === 0 && veChannelAll.counts.channels === 1 && veChannelAll.counts.lines === 1 && veChannelAll.channels[0].channel_name === 've-ch' && veChannelAll.channels[0].line_data[0].line_id === 've-ch-line', JSON.stringify({ counts: veChannelAll.counts, lines: veLineIds({ lines: veChannelAll.channels[0].line_data }) }))
|
|
4077
|
+
veView.destroy()
|
|
4078
|
+
veHost.remove()
|
|
3881
4079
|
}
|
|
3882
4080
|
{
|
|
3883
4081
|
// 接口选中(selectFeatures)、读回选中(getSelectionData)与聚焦(focusOn:单个 / 一批 / 只居中)
|
|
@@ -3982,27 +4180,27 @@
|
|
|
3982
4180
|
const apiCorners = [[0, 0], [400, 40]].map(spot => apiView.project(spot[0], spot[1]))
|
|
3983
4181
|
const apiInside = apiCorners.every(spot => spot[0] >= 0 && spot[0] <= apiStats.cssWidth && spot[1] >= 0 && spot[1] <= apiStats.cssHeight)
|
|
3984
4182
|
check('focusOn(一批目标, { zoom: true }):整体包围盒居中,三个点都在视口里', Math.abs(apiBatch.center[0] - 200) < 1e-6 && Math.abs(apiBatch.center[1] - 20) < 1e-6 && apiBatch.scale > 0 && apiInside, JSON.stringify({ center: apiBatch.center, corners: apiCorners }))
|
|
3985
|
-
//
|
|
3986
|
-
apiView.focusOn(['api-a', 'api-b']
|
|
4183
|
+
// 只挪中心、比例一点不动是 focusOn 的缺省语义
|
|
4184
|
+
apiView.focusOn(['api-a', 'api-b'])
|
|
3987
4185
|
const apiCenter1 = apiView.getCamera()
|
|
3988
|
-
check('focusOn(
|
|
3989
|
-
apiView.focusOn([400, 0]
|
|
4186
|
+
check('focusOn(一批):中心挪到这一段的中点,比例一点不动', Math.abs(apiCenter1.center[0] - 100) < 1e-6 && Math.abs(apiCenter1.center[1] - 20) < 1e-6 && apiCenter1.scale === apiBatch.scale, JSON.stringify(apiCenter1.center))
|
|
4187
|
+
apiView.focusOn([400, 0])
|
|
3990
4188
|
const apiCenter2 = apiView.getCamera().center
|
|
3991
|
-
check('focusOn([x, y]
|
|
3992
|
-
apiView.focusOn('api-b'
|
|
4189
|
+
check('focusOn([x, y]) 与 { x, y } 写法都认', Math.abs(apiCenter2[0] - 400) < 1e-6 && Math.abs(apiCenter2[1]) < 1e-6, JSON.stringify(apiCenter2))
|
|
4190
|
+
apiView.focusOn('api-b')
|
|
3993
4191
|
const apiCenter3 = apiView.getCamera().center
|
|
3994
|
-
check('focusOn(id
|
|
4192
|
+
check('focusOn(id):中心挪到这个要素上', Math.abs(apiCenter3[0] - 200) < 1e-6 && Math.abs(apiCenter3[1] - 40) < 1e-6, JSON.stringify(apiCenter3))
|
|
3995
4193
|
const apiChannelScale = apiView.getCamera().scale
|
|
3996
4194
|
apiView.focusOn({ type: 'channel', channelId: 'api-ch' })
|
|
3997
4195
|
const apiChannelCamera = apiView.getCamera()
|
|
3998
4196
|
const apiCenter4 = apiChannelCamera.center
|
|
3999
4197
|
check('focusOn(通道写法) 缺省只居中,线路 / 通道不再自动放大', Math.abs(apiCenter4[0] - 100) < 5 && Math.abs(apiCenter4[1] - 20) < 5 && apiChannelCamera.scale === apiChannelScale, JSON.stringify({ center: apiCenter4, scale: [apiChannelScale, apiChannelCamera.scale] }))
|
|
4000
|
-
apiView.focusOn('没有这个 id'
|
|
4198
|
+
apiView.focusOn('没有这个 id')
|
|
4001
4199
|
const apiCenter5 = apiView.getCamera().center
|
|
4002
4200
|
check('focusOn:认不出来的目标什么都不做(视角停在原处)', apiCenter5[0] === apiCenter4[0] && apiCenter5[1] === apiCenter4[1], JSON.stringify(apiCenter5))
|
|
4003
|
-
apiView.focusOn({ minX: 0, minY: 0, maxX: 400, maxY: 40 }
|
|
4201
|
+
apiView.focusOn({ minX: 0, minY: 0, maxX: 400, maxY: 40 })
|
|
4004
4202
|
const apiCenter6 = apiView.getCamera().center
|
|
4005
|
-
check('focusOn(
|
|
4203
|
+
check('focusOn(一段范围):取中心', Math.abs(apiCenter6[0] - 200) < 1e-6 && Math.abs(apiCenter6[1] - 20) < 1e-6, JSON.stringify(apiCenter6))
|
|
4006
4204
|
// select: true:被聚焦的要素同时变成选中状态(与接口选中同一份 state);mode 决定怎么并进当前选中集
|
|
4007
4205
|
const apiBeforeFocusSelect = apiView.getSelectionData().ids.join(',')
|
|
4008
4206
|
apiView.focusOn(['api-a', 'api-b', 'api-c'], { select: true, zoom: true, padding: 20 })
|
|
@@ -4733,13 +4931,13 @@
|
|
|
4733
4931
|
check('addChannel 新签名:lineIds 自动按线路数组每项的 id 构建', JSON.stringify(nwCh1?.lineIds) === JSON.stringify(['nl1', 'nl2']) && JSON.stringify(nwAdd.lineIds) === JSON.stringify(['nl1', 'nl2']), JSON.stringify(nwCh1?.lineIds))
|
|
4734
4932
|
check('addChannel 新签名:线路数组逐条落地(自己的字段保留、from / to 固定两端)', nwLine('nl1')?.color === '#112233' && nwLine('nl1')?.from === 'na' && nwLine('nl1')?.to === 'nb' && nwAdd.circuits === 2, JSON.stringify({ l1: nwLine('nl1'), count: nwAdd.circuits }))
|
|
4735
4933
|
check('addChannel 新签名:第三段分组信息写进声明(id / lineIds / path)', nwCh1?.groups?.length === 1 && nwCh1.groups[0].id === 'ng1' && JSON.stringify(nwCh1.groups[0].lineIds) === JSON.stringify(['nl1', 'nl2']) && JSON.stringify(nwCh1.groups[0].path) === JSON.stringify([[0, 30]]), JSON.stringify(nwCh1?.groups))
|
|
4736
|
-
check('addChannel
|
|
4934
|
+
check('addChannel 缺省不改变选中集(select 是显式选项)', nwView.getSelectionData().ids.length === 0, JSON.stringify(nwView.getSelectionData().ids))
|
|
4737
4935
|
|
|
4738
|
-
// ②
|
|
4936
|
+
// ② 旧写法已移除;只写 { from, to } 是新签名,会新建一条回线
|
|
4739
4937
|
const nwLegacy = nwView.addChannel({ from: 'nc', to: 'nd', circuits: 2, name: '丙丁' })
|
|
4740
|
-
check('addChannel
|
|
4938
|
+
check('addChannel 旧 circuits 写法已移除:明确失败且不写数据', nwLegacy.ok === false && String(nwLegacy.reason).includes('circuits') && nwView.getOptions().lines.filter(item => item.from === 'nc' && item.to === 'nd').length === 0, JSON.stringify({ ok: nwLegacy.ok, reason: nwLegacy.reason }))
|
|
4741
4939
|
const nwBare = nwView.addChannel({ from: 'nc', to: 'nd' })
|
|
4742
|
-
check('只写 { from, to }
|
|
4940
|
+
check('只写 { from, to }:新建通道并自动建一条回线', nwBare.ok === true && nwBare.resolution?.type === 'newChannel' && nwBare.lineIds.length === 1, JSON.stringify({ ok: nwBare.ok, type: nwBare.resolution?.type, reason: nwBare.reason }))
|
|
4743
4941
|
|
|
4744
4942
|
// ③ 两个节点之间已有通道:新签名整条失败,plan 里带分组信息(拿来给用户看「已经有几组」)
|
|
4745
4943
|
const nwLinesBefore = nwView.getOptions().lines.length
|
|
@@ -4748,9 +4946,11 @@
|
|
|
4748
4946
|
check('失败结果里的 plan 带 hasChannel / hasGroup / groups / channel / group', nwBlocked.plan?.hasChannel === true && nwBlocked.plan?.hasGroup === true && nwBlocked.plan.groups.length === 1 && nwBlocked.plan.channel?.id === 'nc1' && nwBlocked.plan.group?.id === 'ng1', JSON.stringify({ hasChannel: nwBlocked.plan?.hasChannel, groups: nwBlocked.plan?.groups?.map(item => item.id) }))
|
|
4749
4947
|
|
|
4750
4948
|
// ④ 拆出来的两个方法:新加一组 / 并进某一组
|
|
4751
|
-
const
|
|
4949
|
+
const nwSelectBefore = nwSelectChanges.length
|
|
4950
|
+
const nwNewGroup = nwView.addChannelGroupLines('nc1', [{ id: 'nl3', color: '#334455' }], { id: 'ng2' }, { select: true })
|
|
4752
4951
|
const nwCh1b = nwChannel('nc1')
|
|
4753
4952
|
check('addChannelGroupLines:新回线进新分组(成员表一起长)', nwNewGroup.ok === true && nwNewGroup.groupId === 'ng2' && nwNewGroup.resolution?.type === 'newGroup' && JSON.stringify(nwCh1b.lineIds) === JSON.stringify(['nl1', 'nl2', 'nl3']) && nwCh1b.groups.length === 2, JSON.stringify({ ok: nwNewGroup.ok, groupId: nwNewGroup.groupId }))
|
|
4953
|
+
check('addChannelGroupLines({ select: true }):落地后只选中这次的新回线,并只发一次选中变化', nwSelectChanges.length === nwSelectBefore + 1 && JSON.stringify(nwView.getSelectionData().ids) === JSON.stringify(['nl3']), JSON.stringify({ changes: nwSelectChanges.length - nwSelectBefore, ids: nwView.getSelectionData().ids }))
|
|
4754
4954
|
const nwJoin = nwView.joinChannelGroupLines('nc1', 'ng1', [{ id: 'nl4' }])
|
|
4755
4955
|
check('joinChannelGroupLines:按分组 id 并进某一组', nwJoin.ok === true && nwJoin.groupId === 'ng1' && JSON.stringify(nwChannel('nc1').groups.find(item => item.id === 'ng1').lineIds) === JSON.stringify(['nl1', 'nl2', 'nl4']), JSON.stringify({ groups: nwChannel('nc1')?.groups?.map(item => [item.id, item.lineIds]) }))
|
|
4756
4956
|
const nwJoinIndex = nwView.joinChannelGroupLines('nc1', 1, 'nl5')
|
|
@@ -4759,8 +4959,10 @@
|
|
|
4759
4959
|
check('通道找不到时拆出来的方法给出 reason、不抛错', nwJoinMiss.ok === false && String(nwJoinMiss.reason).includes('找不到通道'), JSON.stringify({ ok: nwJoinMiss.ok, reason: nwJoinMiss.reason }))
|
|
4760
4960
|
|
|
4761
4961
|
// ⑤ addChannels 的每一项也认新签名(lineData + group 写在对象上)
|
|
4762
|
-
const
|
|
4962
|
+
const nwBatchSelectBefore = nwSelectChanges.length
|
|
4963
|
+
const nwMany = nwView.addChannels([{ from: 'na', to: 'nc', name: '甲丙', id: 'nc2', lineData: [{ id: 'nl6' }, { id: 'nl7' }], group: { id: 'ng3' } }], { select: true })
|
|
4763
4964
|
check('addChannels 的每一项认新签名(lineData + group)', nwMany.ok === true && nwMany.channelIds[0] === 'nc2' && JSON.stringify(nwChannel('nc2')?.lineIds) === JSON.stringify(['nl6', 'nl7']) && nwChannel('nc2')?.groups?.[0]?.id === 'ng3', JSON.stringify({ ok: nwMany.ok, ids: nwMany.channelIds }))
|
|
4965
|
+
check('addChannels({ select: true }):两条回线一次选中,不逐条发选中变化', nwSelectChanges.length === nwBatchSelectBefore + 1 && JSON.stringify(nwView.getSelectionData().ids) === JSON.stringify(['nl6', 'nl7']), JSON.stringify({ changes: nwSelectChanges.length - nwBatchSelectBefore, ids: nwView.getSelectionData().ids }))
|
|
4764
4966
|
|
|
4765
4967
|
// ⑥ updateChannel 的 group(单数:改一组的信息)/ groups(复数:整份换)
|
|
4766
4968
|
nwView.updateChannel('nc1', { group: { id: 'ng1', path: [[7, 8]] } })
|
|
@@ -4784,7 +4986,7 @@
|
|
|
4784
4986
|
send('pointerdown', { buttons: 1 })
|
|
4785
4987
|
send('pointerup', { buttons: 0 })
|
|
4786
4988
|
}
|
|
4787
|
-
const
|
|
4989
|
+
const nwSelectedBeforeDraft = nwView.getSelectionData().ids
|
|
4788
4990
|
nwEvents.length = 0
|
|
4789
4991
|
nwSelectChanges.length = 0
|
|
4790
4992
|
nwView.beginAddChannel({ select: false })
|
|
@@ -4794,7 +4996,7 @@
|
|
|
4794
4996
|
await settle(40)
|
|
4795
4997
|
nwClick(nwScreenOf('nb'))
|
|
4796
4998
|
await settle(60)
|
|
4797
|
-
check('select 关着:点两个节点不改变选中集(只画两个草稿点)', JSON.stringify(nwView.getSelectionData().ids) === JSON.stringify(
|
|
4999
|
+
check('select 关着:点两个节点不改变选中集(只画两个草稿点)', JSON.stringify(nwView.getSelectionData().ids) === JSON.stringify(nwSelectedBeforeDraft) && nwSelectChanges.length === 0, JSON.stringify(nwView.getSelectionData().ids))
|
|
4798
5000
|
const nwReadyPlain = nwEvents.find(payload => payload.phase === 'ready')
|
|
4799
5001
|
check('结束事件(ready)带两个节点与「没有分组」的读数', nwReadyPlain !== undefined && nwReadyPlain.from.id === 'nd' && nwReadyPlain.to.id === 'nb' && nwReadyPlain.finished === true && nwReadyPlain.hasChannel === false && nwReadyPlain.hasGroup === false && nwReadyPlain.groups.length === 0 && nwReadyPlain.group === null && nwReadyPlain.channel === null, JSON.stringify({ from: nwReadyPlain?.from?.id, to: nwReadyPlain?.to?.id, hasGroup: nwReadyPlain?.hasGroup }))
|
|
4800
5002
|
check('beginAddChannel 只负责示例路径:ready 之后草稿还在、一条数据都没落', nwView.getAddChannelDraft()?.phase === 'ready' && nwEvents.every(payload => payload.phase !== 'commit'), JSON.stringify({ draft: nwView.getAddChannelDraft()?.phase, phases: nwEvents.map(payload => payload.phase) }))
|
|
@@ -4817,6 +5019,7 @@
|
|
|
4817
5019
|
nwDraftMode = 'commit'
|
|
4818
5020
|
nwEvents.length = 0
|
|
4819
5021
|
const nwLinesBefore2 = nwView.getOptions().lines.length
|
|
5022
|
+
const nwSelectedBeforeInside = nwView.getSelectionData().ids
|
|
4820
5023
|
nwView.beginAddChannel()
|
|
4821
5024
|
await settle(40)
|
|
4822
5025
|
nwClick(nwScreenOf('nd'))
|
|
@@ -4824,7 +5027,7 @@
|
|
|
4824
5027
|
nwClick(nwScreenOf('nb'))
|
|
4825
5028
|
await settle(80)
|
|
4826
5029
|
check('事件里 endAddChannel + addChannel:真实通道与回线落地、假通道不留', nwInside?.ok === true && nwChannel('nc9') !== undefined && nwLine('nl9') !== undefined && nwLine('nl10') !== undefined && nwView.getOptions().lines.length === nwLinesBefore2 + 2, JSON.stringify({ ok: nwInside?.ok, lines: nwView.getOptions().lines.map(item => item.id) }))
|
|
4827
|
-
check('
|
|
5030
|
+
check('事件里落地之后草稿收掉、缺省不改选中集', nwView.getAddChannelDraft() === null && JSON.stringify(nwView.getSelectionData().ids) === JSON.stringify(nwSelectedBeforeInside), JSON.stringify({ before: nwSelectedBeforeInside, after: nwView.getSelectionData().ids }))
|
|
4828
5031
|
|
|
4829
5032
|
// ⑩ select: true 与结束事件里的分组读数;提前结束用 cancelAddChannel
|
|
4830
5033
|
nwDraftMode = null
|
|
@@ -4912,6 +5115,53 @@
|
|
|
4912
5115
|
JSON.stringify({ removedChannels: cbManyRemoved.removedChannels?.map(item => item.id) }))
|
|
4913
5116
|
nwView.destroy()
|
|
4914
5117
|
|
|
5118
|
+
/* 这一轮新增:getDirectConnections 只取传入节点集合内部的直接通道 / 线路 */
|
|
5119
|
+
const dcHost = document.createElement('div')
|
|
5120
|
+
dcHost.style.cssText = 'width:420px;height:280px'
|
|
5121
|
+
document.body.appendChild(dcHost)
|
|
5122
|
+
const dcView = createGisView(dcHost, {
|
|
5123
|
+
points: [
|
|
5124
|
+
{ id: 'da', x: -120, y: -60 },
|
|
5125
|
+
{ id: 'db', x: 120, y: -60 },
|
|
5126
|
+
{ id: 'dc', x: 120, y: 60 },
|
|
5127
|
+
{ id: 'dd', x: -120, y: 60 }
|
|
5128
|
+
],
|
|
5129
|
+
lines: [
|
|
5130
|
+
{ id: 'dc-ab-1', from: 'da', to: 'db' },
|
|
5131
|
+
{ id: 'dc-ab-2', from: 'db', to: 'da' },
|
|
5132
|
+
{ id: 'dc-bc', from: 'db', to: 'dc' },
|
|
5133
|
+
{ id: 'dc-cd', from: 'dc', to: 'dd' },
|
|
5134
|
+
{ id: 'dc-loop', from: 'da', to: 'da' },
|
|
5135
|
+
{ id: 'dc-free', path: [[0, 0], [40, 40]] }
|
|
5136
|
+
],
|
|
5137
|
+
channels: [
|
|
5138
|
+
{ id: 'dc-ch-ab', groups: [{ id: 'dc-ch-ab-g1', lineIds: ['dc-ab-1'] }] },
|
|
5139
|
+
{ id: 'dc-ch-bc', from: 'db', to: 'dc', lineIds: ['dc-bc'] },
|
|
5140
|
+
{ id: 'dc-ch-ad', from: 'da', to: 'dd', lineIds: ['dc-cd'] },
|
|
5141
|
+
{ id: 'dc-ch-miss', from: 'da', to: 'missing', lineIds: [] },
|
|
5142
|
+
{ id: 'dc-ch-free', groups: [{ id: 'dc-ch-free-g1', lineIds: ['dc-free'] }] }
|
|
5143
|
+
],
|
|
5144
|
+
showGrid: false,
|
|
5145
|
+
fitPadding: 50
|
|
5146
|
+
})
|
|
5147
|
+
await settle(80)
|
|
5148
|
+
const dcAB = dcView.getDirectConnections(['da', 'db'])
|
|
5149
|
+
const dcAll = dcView.getDirectConnections(['da', 'db', 'dc'])
|
|
5150
|
+
check('getDirectConnections:只返回集合内部的直接通道 / 线路,通道与成员回线可同时命中',
|
|
5151
|
+
dcAB.map(item => item.type).join(',') === 'channel,line,line' && dcAB.map(item => item.channelId ?? item.lineId).join(',') === 'dc-ch-ab,dc-ab-1,dc-ab-2',
|
|
5152
|
+
JSON.stringify(dcAB.map(item => ({ type: item.type, id: item.channelId ?? item.lineId, from: item.from.id, to: item.to.id }))))
|
|
5153
|
+
check('getDirectConnections:通道端点可继承成员回线,线路保留声明方向与节点下标',
|
|
5154
|
+
dcAB[0].from.id === 'da' && dcAB[0].to.id === 'db' && JSON.stringify(dcAB[0].lineIds) === JSON.stringify(['dc-ab-1']) && dcAB[2].from.id === 'db' && dcAB[2].to.id === 'da' && dcAB[2].from.index === 1 && dcAB[2].to.index === 0,
|
|
5155
|
+
JSON.stringify({ channel: dcAB[0], reverse: dcAB[2] }))
|
|
5156
|
+
check('getDirectConnections:三个节点时连上集合内的第二条边,不把集合外的边一起带回来',
|
|
5157
|
+
dcAll.length === 5 && dcAll.some(item => item.type === 'channel' && item.channelId === 'dc-ch-bc') && dcAll.filter(item => item.type === 'line').map(item => item.lineId).join(',') === 'dc-ab-1,dc-ab-2,dc-bc',
|
|
5158
|
+
JSON.stringify(dcAll.map(item => ({ type: item.type, id: item.channelId ?? item.lineId }))))
|
|
5159
|
+
check('getDirectConnections:节点不足两个 / 有节点不存在 / 端点认不出 / 自环与未绑定线路都返回空表',
|
|
5160
|
+
dcView.getDirectConnections([]).length === 0 && dcView.getDirectConnections('da').length === 0 && dcView.getDirectConnections(['da', 'missing']).length === 0 && dcView.getDirectConnections(['da', 'db', 'da']).length === 3,
|
|
5161
|
+
JSON.stringify({ empty: dcView.getDirectConnections([]), missing: dcView.getDirectConnections(['da', 'missing']), duplicate: dcView.getDirectConnections(['da', 'db', 'da']).length }))
|
|
5162
|
+
dcView.destroy()
|
|
5163
|
+
dcHost.remove()
|
|
5164
|
+
|
|
4915
5165
|
/* 这一轮新增:addLine / addChannel 第二段的裸 id 归一(见 15.98)+ 视口量不出来时挂起的落位请求封存的是「请求那一刻」的目标(见 16.68) */
|
|
4916
5166
|
const bvHost = document.createElement('div')
|
|
4917
5167
|
bvHost.style.cssText = 'width:520px;height:340px'
|
|
@@ -4938,10 +5188,10 @@
|
|
|
4938
5188
|
check('addLine 不传 id:按 from-to-序号 自动编号(撞车由 uniqueLineId 往后排)',
|
|
4939
5189
|
bvAuto.ok === true && String(bvAuto.lineId) === 'bb-bc-0',
|
|
4940
5190
|
JSON.stringify({ ok: bvAuto.ok, lineId: bvAuto.lineId }))
|
|
4941
|
-
const
|
|
4942
|
-
check('
|
|
4943
|
-
|
|
4944
|
-
JSON.stringify({ ok:
|
|
5191
|
+
const bvChannel = bvView.addChannel({ from: 'ba', to: 'bb' }, ['bare-4', 'bare-5'])
|
|
5192
|
+
check('addChannel 第二段同样认裸 id:给了两条就建两条回线',
|
|
5193
|
+
bvChannel.ok === true && JSON.stringify(bvChannel.lineIds.map(String)) === JSON.stringify(['bare-4', 'bare-5']),
|
|
5194
|
+
JSON.stringify({ ok: bvChannel.ok, lineIds: bvChannel.lineIds }))
|
|
4945
5195
|
bvView.destroy()
|
|
4946
5196
|
bvHost.remove()
|
|
4947
5197
|
// 容器先 display:none(量不出视口)再显示:挂起的那次落位兑现的是「请求那一刻」的范围,中途新加的远处节点不算进来
|
|
@@ -4993,7 +5243,96 @@
|
|
|
4993
5243
|
JSON.stringify(dfView.getCamera()))
|
|
4994
5244
|
dfView.destroy()
|
|
4995
5245
|
dfHost.remove()
|
|
4996
|
-
|
|
5246
|
+
|
|
5247
|
+
/* 新增接口:选中由 options.select 显式打开;addLine 一步撤销;有通道声明时仍走追加式补线 */
|
|
5248
|
+
const awHost = document.createElement('div')
|
|
5249
|
+
awHost.style.cssText = 'width:520px;height:340px'
|
|
5250
|
+
document.body.appendChild(awHost)
|
|
5251
|
+
const awSelectChanges = []
|
|
5252
|
+
const awView = createGisView(awHost, {
|
|
5253
|
+
points: [
|
|
5254
|
+
{ id: 'aa', x: -180, y: -100 },
|
|
5255
|
+
{ id: 'ab', x: -90, y: -100 },
|
|
5256
|
+
{ id: 'ac', x: 0, y: -100 },
|
|
5257
|
+
{ id: 'ad', x: 90, y: -100 },
|
|
5258
|
+
{ id: 'ae', x: 0, y: 100 },
|
|
5259
|
+
{ id: 'af', x: 180, y: 100 }
|
|
5260
|
+
],
|
|
5261
|
+
lines: [],
|
|
5262
|
+
channels: [],
|
|
5263
|
+
features: { channelDraft: true },
|
|
5264
|
+
history: true,
|
|
5265
|
+
showGrid: false,
|
|
5266
|
+
onSelectChange: event => awSelectChanges.push(event)
|
|
5267
|
+
})
|
|
5268
|
+
await settle(60)
|
|
5269
|
+
const awIds = () => awView.getSelectionData().ids
|
|
5270
|
+
const awLine = id => awView.getOptions().lines.find(item => String(item.id) === String(id))
|
|
5271
|
+
const awInitialChanges = awSelectChanges.length
|
|
5272
|
+
const awPointPlain = awView.addPoint({ id: 'aw-p1', x: 40, y: 30 })
|
|
5273
|
+
check('addPoint 缺省不改变选中集', awPointPlain.ok === true && awIds().length === 0 && awSelectChanges.length === awInitialChanges, JSON.stringify({ ids: awIds(), changes: awSelectChanges.length - awInitialChanges }))
|
|
5274
|
+
const awPointsSelected = awView.addPoints([{ id: 'aw-p2', x: 50, y: 35 }, { id: 'aw-p3', x: 60, y: 40 }], { select: true })
|
|
5275
|
+
check('addPoints({ select: true }):一次选中这批新节点', awPointsSelected.ok === true && JSON.stringify(awIds()) === JSON.stringify(['aw-p2', 'aw-p3']), JSON.stringify(awIds()))
|
|
5276
|
+
awView.selectFeatures(null)
|
|
5277
|
+
const awLinesPlain = awView.addLines([{ id: 'aw-l1', from: 'aa', to: 'ab' }])
|
|
5278
|
+
check('addLines 缺省不改变选中集', awLinesPlain.ok === true && awIds().length === 0, JSON.stringify(awIds()))
|
|
5279
|
+
const awLinesSelected = awView.addLines([{ id: 'aw-l2', from: 'ac', to: 'ad' }, { id: 'aw-l3', from: 'ae', to: 'af' }], { select: true })
|
|
5280
|
+
check('addLines({ select: true }):一次选中这批新线路', awLinesSelected.ok === true && JSON.stringify(awIds()) === JSON.stringify(['aw-l2', 'aw-l3']), JSON.stringify(awIds()))
|
|
5281
|
+
awView.selectFeatures(null)
|
|
5282
|
+
const awLinePlain = awView.addLine({ from: 'aa', to: 'ac', id: 'aw-l4' })
|
|
5283
|
+
check('addLine 缺省不改变选中集', awLinePlain.ok === true && awIds().length === 0, JSON.stringify(awIds()))
|
|
5284
|
+
const awLineSelected = awView.addLine({ from: 'ae', to: 'af', id: 'aw-l5' }, undefined, { select: true })
|
|
5285
|
+
check('addLine({ select: true }):只选中新增回线', awLineSelected.ok === true && JSON.stringify(awIds()) === JSON.stringify(['aw-l5']), JSON.stringify(awIds()))
|
|
5286
|
+
awView.selectFeatures(null)
|
|
5287
|
+
const awChannelPlain = awView.addChannel({ from: 'aa', to: 'ab', id: 'aw-c1' }, ['aw-c1-1'])
|
|
5288
|
+
check('addChannel 缺省不改变选中集', awChannelPlain.ok === true && awIds().length === 0, JSON.stringify(awIds()))
|
|
5289
|
+
const awChannelSelected = awView.addChannel({ from: 'ab', to: 'ad', id: 'aw-c2' }, ['aw-c2-1', 'aw-c2-2'], undefined, { select: true })
|
|
5290
|
+
check('addChannel({ select: true }):只选中这次新建的全部回线', awChannelSelected.ok === true && JSON.stringify(awIds()) === JSON.stringify(['aw-c2-1', 'aw-c2-2']), JSON.stringify(awIds()))
|
|
5291
|
+
const awLineLegacy = awView.addLine({ from: 'ac', to: 'af', circuits: 2 })
|
|
5292
|
+
check('addLine 旧 circuits 入参已移除:明确失败且不写数据', awLineLegacy.ok === false && String(awLineLegacy.reason).includes('circuits') && awLine('ac-af-0') === undefined, JSON.stringify({ ok: awLineLegacy.ok, reason: awLineLegacy.reason }))
|
|
5293
|
+
const awCommitLegacy = awView.commitAddChannel({ lineItems: [{ id: 'aw-old-commit' }] })
|
|
5294
|
+
check('commitAddChannel 旧 lineItems 入参已移除:明确失败且不写数据', awCommitLegacy.ok === false && String(awCommitLegacy.reason).includes('lineItems') && awLine('aw-old-commit') === undefined, JSON.stringify({ ok: awCommitLegacy.ok, reason: awCommitLegacy.reason }))
|
|
5295
|
+
const awChannelGroupsBefore = JSON.stringify(awView.getOptions().channels)
|
|
5296
|
+
const awLineCountBeforeGroups = awView.getOptions().lines.length
|
|
5297
|
+
const awGroupLegacy = awView.addChannelGroups('aw-c2', [{ lineData: [{ id: 'aw-group-new' }], lineItems: [{ id: 'aw-group-old' }] }])
|
|
5298
|
+
check('addChannelGroups 旧 lineItems 入参已移除:整组拒绝、不改数据', awGroupLegacy.ok === false && String(awGroupLegacy.reason).includes('lineItems') && JSON.stringify(awView.getOptions().channels) === awChannelGroupsBefore && awView.getOptions().lines.length === awLineCountBeforeGroups, JSON.stringify({ ok: awGroupLegacy.ok, reason: awGroupLegacy.reason }))
|
|
5299
|
+
|
|
5300
|
+
awView.beginAddChannel({ kind: 'line' })
|
|
5301
|
+
await settle(20)
|
|
5302
|
+
const awDraftKind = awView.getAddChannelDraft()?.kind
|
|
5303
|
+
awView.continueAddChannel({ kind: 'line' })
|
|
5304
|
+
await settle(20)
|
|
5305
|
+
const awContinuedKind = awView.getAddChannelDraft()?.kind
|
|
5306
|
+
awView.cancelAddChannel()
|
|
5307
|
+
awView.beginAddLine({ kind: 'channel' })
|
|
5308
|
+
await settle(20)
|
|
5309
|
+
const awLineDraftKind = awView.getAddChannelDraft()?.kind
|
|
5310
|
+
awView.cancelAddChannel()
|
|
5311
|
+
check('beginAddChannel 固定通道语义:运行时 kind: line 不再切换入口', awDraftKind === 'channel', JSON.stringify({ kind: awDraftKind }))
|
|
5312
|
+
check('continueAddChannel 不切换入口语义:通道草稿传 kind: line 仍是通道', awContinuedKind === 'channel', JSON.stringify({ kind: awContinuedKind }))
|
|
5313
|
+
check('beginAddLine 固定线路语义:运行时 kind: channel 不再切换入口', awLineDraftKind === 'line', JSON.stringify({ kind: awLineDraftKind }))
|
|
5314
|
+
|
|
5315
|
+
const awChannelsBeforeLine = JSON.stringify(awView.getOptions().channels)
|
|
5316
|
+
awView.clearHistory()
|
|
5317
|
+
const awHistoryLine = awView.addLine({ from: 'aa', to: 'af', id: 'aw-history' })
|
|
5318
|
+
await settle(20)
|
|
5319
|
+
const awCenter = awView.project(0, 0)
|
|
5320
|
+
const awHistoryHit = awView.pick(awCenter[0], awCenter[1])
|
|
5321
|
+
const awHistoryState = awView.getHistoryState()
|
|
5322
|
+
const awUndo = awView.undo()
|
|
5323
|
+
await settle(20)
|
|
5324
|
+
const awUndoHit = awView.pick(awCenter[0], awCenter[1])
|
|
5325
|
+
const awChannelsAfterUndo = JSON.stringify(awView.getOptions().channels)
|
|
5326
|
+
check('addLine 有通道声明时仍可落地与拾取,通道成员 / 分组不变', awHistoryLine.ok === true && awHistoryHit?.item?.id === 'aw-history' && awChannelsBeforeLine === JSON.stringify(awView.getOptions().channels), JSON.stringify({ hit: awHistoryHit?.item?.id }))
|
|
5327
|
+
check('addLine 的撤销只记一步:一次 undo 就摘掉这条回线', awHistoryState.undoCount === 1 && awUndo?.type === 'addLine' && awLine('aw-history') === undefined && awUndoHit?.item?.id !== 'aw-history' && awChannelsAfterUndo === awChannelsBeforeLine, JSON.stringify({ before: awHistoryState.undoCount, undo: awUndo, hit: awUndoHit?.item?.id }))
|
|
5328
|
+
const awRedo = awView.redo()
|
|
5329
|
+
await settle(20)
|
|
5330
|
+
const awRemovedLine = awView.removeLine('aw-history')
|
|
5331
|
+
check('addLine 重做后再删除:只摘回线,通道声明保持不变', awRedo?.type === 'addLine' && awRemovedLine.ok === true && awLine('aw-history') === undefined && JSON.stringify(awView.getOptions().channels) === awChannelsBeforeLine, JSON.stringify({ redo: awRedo, removed: awRemovedLine.ok }))
|
|
5332
|
+
check('有通道时 addLine 仍返回独立线路读数(channelId / channelLineIds 为 null)', awHistoryLine.channelId === null && awHistoryLine.channelLineIds === null && awHistoryLine.kind === 'line', JSON.stringify({ channelId: awHistoryLine.channelId, channelLineIds: awHistoryLine.channelLineIds }))
|
|
5333
|
+
awView.destroy()
|
|
5334
|
+
awHost.remove()
|
|
5335
|
+
|
|
4997
5336
|
nwHost.remove()
|
|
4998
5337
|
/* 这一轮新增:组件方法审计(getPointStyle / updateCopyPoint / setCurrentChannelGroup(null) / options 透传 / getElementBounds / 拐点中间点口径 / minimap.setZoom / off / runChannelAction 认不出的动作) */
|
|
4999
5338
|
const auHost = document.createElement('div')
|
|
@@ -5475,6 +5814,104 @@
|
|
|
5475
5814
|
clearedLayers === 1 && draftView.getWebGLLayers().length === 0 && (draftView.getOptions().webglLayers ?? []).length === 0 && draftView.requestRender() === draftView,
|
|
5476
5815
|
JSON.stringify({ cleared: clearedLayers, layers: draftView.getWebGLLayers().length, options: draftView.getOptions().webglLayers?.length }))
|
|
5477
5816
|
|
|
5817
|
+
/* beginAddLine / beginAddChannel:单一通道阻断、已有通道新分组 / 指定分组、beginAddLine 挂回线 */
|
|
5818
|
+
const bdHost = document.createElement('div')
|
|
5819
|
+
bdHost.style.cssText = 'width:520px;height:320px'
|
|
5820
|
+
document.body.append(bdHost)
|
|
5821
|
+
const bdEvents = []
|
|
5822
|
+
const bdView = createGisView(bdHost, {
|
|
5823
|
+
points: [
|
|
5824
|
+
{ id: 'bda', x: 0, y: 0, name: 'A' },
|
|
5825
|
+
{ id: 'bdb', x: 80, y: 0, name: 'B' },
|
|
5826
|
+
{ id: 'bdc', x: 0, y: 70, name: 'C' }
|
|
5827
|
+
],
|
|
5828
|
+
lines: [],
|
|
5829
|
+
fitPadding: 60,
|
|
5830
|
+
showGrid: false,
|
|
5831
|
+
features: { channelDraft: true },
|
|
5832
|
+
onChannelDraft: payload => bdEvents.push(payload)
|
|
5833
|
+
})
|
|
5834
|
+
await settle(120)
|
|
5835
|
+
const bdSeed = bdView.addChannel({ id: 'bd-ch', name: '已有通道', from: 'bda', to: 'bdb' }, [
|
|
5836
|
+
{ id: 'bd-l1' },
|
|
5837
|
+
{ id: 'bd-l2' }
|
|
5838
|
+
], [
|
|
5839
|
+
{ id: 'bd-g1', lineIds: ['bd-l1'] },
|
|
5840
|
+
{ id: 'bd-g2', lineIds: ['bd-l2'] }
|
|
5841
|
+
])
|
|
5842
|
+
check('beginAddChannel 前置:已有通道带两个命名分组,供 singleChannel / newGroup / joinGroup 测试', bdSeed.ok === true && bdView.getChannelGroups('bd-ch').length === 2, JSON.stringify({ ok: bdSeed.ok, groups: bdView.getChannelGroups('bd-ch').map(group => group.id) }))
|
|
5843
|
+
const bdCanvas = bdView.canvas
|
|
5844
|
+
const bdClick = async (id) => {
|
|
5845
|
+
const point = bdView.getData().points.find(item => item.id === id)
|
|
5846
|
+
const screen = bdView.project(point.x, point.y)
|
|
5847
|
+
const rect = bdCanvas.getBoundingClientRect()
|
|
5848
|
+
for (const type of ['pointerdown', 'pointerup']) {
|
|
5849
|
+
bdCanvas.dispatchEvent(new PointerEvent(type, {
|
|
5850
|
+
bubbles: true,
|
|
5851
|
+
cancelable: true,
|
|
5852
|
+
composed: true,
|
|
5853
|
+
pointerId: 31,
|
|
5854
|
+
pointerType: 'mouse',
|
|
5855
|
+
isPrimary: true,
|
|
5856
|
+
button: 0,
|
|
5857
|
+
clientX: rect.left + screen[0],
|
|
5858
|
+
clientY: rect.top + screen[1]
|
|
5859
|
+
}))
|
|
5860
|
+
}
|
|
5861
|
+
await settle(50)
|
|
5862
|
+
}
|
|
5863
|
+
|
|
5864
|
+
bdView.beginAddLine({ from: 'bda', singleChannel: true })
|
|
5865
|
+
const bdLineStart = bdView.getAddChannelDraft()
|
|
5866
|
+
await bdClick('bdb')
|
|
5867
|
+
const bdLineReady = bdView.getAddChannelDraft()
|
|
5868
|
+
const bdLineBefore = bdView.getData().lines.length
|
|
5869
|
+
const bdLineResult = bdView.commitAddChannel()
|
|
5870
|
+
check('beginAddLine 不把 singleChannel 当成挂通道开关:两端已有通道仍出独立线路草稿', bdLineStart?.kind === 'line' && bdLineStart.singleChannel === false && bdLineStart.existingChannel === null && bdLineReady?.blocked === false, JSON.stringify({ kind: bdLineStart?.kind, single: bdLineStart?.singleChannel, existing: bdLineStart?.existingChannel, readyBlocked: bdLineReady?.blocked }))
|
|
5871
|
+
check('beginAddLine 提交后只加独立线路,不碰已有通道', bdLineResult.ok === true && bdLineResult.kind === 'line' && bdView.getData().lines.length === bdLineBefore + 1 && bdView.getChannelGroups('bd-ch').length === 2, JSON.stringify({ kind: bdLineResult.kind, channelId: bdLineResult.channelId, groups: bdView.getChannelGroups('bd-ch').length }))
|
|
5872
|
+
|
|
5873
|
+
bdEvents.length = 0
|
|
5874
|
+
const bdBlockLines = bdView.getData().lines.length
|
|
5875
|
+
bdView.beginAddChannel({ from: 'bda', singleChannel: true })
|
|
5876
|
+
await bdClick('bdb')
|
|
5877
|
+
const bdBlockDraft = bdView.getAddChannelDraft()
|
|
5878
|
+
const bdBlockResult = bdView.commitAddChannel()
|
|
5879
|
+
const bdBlockEvent = bdEvents.find(payload => payload.phase === 'blocked')
|
|
5880
|
+
check('singleChannel:两端已有通道时 ready 直接 blocked,并给出 singleChannel 原因', bdBlockDraft?.blocked === true && bdBlockDraft.existingChannel === 'block' && /singleChannel/.test(bdBlockDraft.reason ?? ''), JSON.stringify({ blocked: bdBlockDraft?.blocked, existing: bdBlockDraft?.existingChannel, reason: bdBlockDraft?.reason }))
|
|
5881
|
+
check('singleChannel:隐式提交发 blocked 事件且不改数据,事件仍报 channel / 当前电路数', bdBlockResult.ok === false && bdBlockEvent?.kind === 'channel' && bdBlockEvent?.singleChannel === true && bdView.getData().lines.length === bdBlockLines, JSON.stringify({ result: bdBlockResult.ok, event: bdBlockEvent?.phase, kind: bdBlockEvent?.kind, lines: bdView.getData().lines.length }))
|
|
5882
|
+
|
|
5883
|
+
bdEvents.length = 0
|
|
5884
|
+
bdView.beginAddChannel({ from: 'bda', singleChannel: true, existingChannel: 'newGroup' })
|
|
5885
|
+
await bdClick('bdb')
|
|
5886
|
+
const bdNewGroupDraft = bdView.getAddChannelDraft()
|
|
5887
|
+
const bdNewGroupResult = bdView.commitAddChannel()
|
|
5888
|
+
check('existingChannel: newGroup 覆盖 singleChannel:已有通道新加一组回线', bdNewGroupDraft?.blocked === false && bdNewGroupDraft.preferred === 'newGroup' && bdNewGroupResult.ok === true && bdNewGroupResult.channelId === 'bd-ch' && bdView.getChannelGroups('bd-ch').length === 3, JSON.stringify({ preferred: bdNewGroupDraft?.preferred, ok: bdNewGroupResult.ok, groupId: bdNewGroupResult.groupId, groups: bdView.getChannelGroups('bd-ch').length }))
|
|
5889
|
+
|
|
5890
|
+
bdEvents.length = 0
|
|
5891
|
+
bdView.beginAddChannel({ from: 'bda', existingChannel: 'joinGroup', groupId: 'bd-g1' })
|
|
5892
|
+
await bdClick('bdb')
|
|
5893
|
+
const bdJoinDraft = bdView.getAddChannelDraft()
|
|
5894
|
+
const bdJoinBefore = bdView.getChannelGroups('bd-ch').find(group => group.id === 'bd-g1')?.lineIndexes.length ?? 0
|
|
5895
|
+
const bdJoinResult = bdView.commitAddChannel()
|
|
5896
|
+
const bdJoinAfter = bdView.getChannelGroups('bd-ch').find(group => group.id === 'bd-g1')?.lineIndexes.length ?? 0
|
|
5897
|
+
check('existingChannel: joinGroup + groupId:新回线落进指定分组,读数带回 targetGroupId', bdJoinDraft?.targetGroupId === 'bd-g1' && bdJoinResult.ok === true && bdJoinResult.groupId === 'bd-g1' && bdJoinAfter === bdJoinBefore + 1, JSON.stringify({ draftTarget: bdJoinDraft?.targetGroupId, groupId: bdJoinResult.groupId, before: bdJoinBefore, after: bdJoinAfter }))
|
|
5898
|
+
|
|
5899
|
+
bdEvents.length = 0
|
|
5900
|
+
bdView.beginAddLine({ from: 'bda', existingChannel: 'joinGroup', groupId: 'bd-g2' })
|
|
5901
|
+
await bdClick('bdb')
|
|
5902
|
+
const bdAttachDraft = bdView.getAddChannelDraft()
|
|
5903
|
+
const bdAttachChannels = bdView.getOptions().channels.length
|
|
5904
|
+
const bdAttachResult = bdView.commitAddChannel()
|
|
5905
|
+
check('beginAddLine + existingChannel: joinGroup:线路入口也能把新回线挂进已有分组(不新建通道)', bdAttachDraft?.kind === 'line' && bdAttachDraft.existingChannel === 'joinGroup' && bdAttachDraft.targetGroupId === 'bd-g2' && bdAttachResult.ok === true && bdAttachResult.kind === 'channel' && bdAttachResult.groupId === 'bd-g2' && bdView.getOptions().channels.length === bdAttachChannels, JSON.stringify({ draftKind: bdAttachDraft?.kind, target: bdAttachDraft?.targetGroupId, resultKind: bdAttachResult.kind, groupId: bdAttachResult.groupId, channels: bdView.getOptions().channels.length }))
|
|
5906
|
+
|
|
5907
|
+
bdView.beginAddChannel({ from: 'bda', existingChannel: 'auto' })
|
|
5908
|
+
await bdClick('bdb')
|
|
5909
|
+
const bdAutoDraft = bdView.getAddChannelDraft()
|
|
5910
|
+
check('existingChannel: auto:有组余量时优先推荐新分组', bdAutoDraft?.blocked === false && bdAutoDraft.preferred === 'newGroup' && bdAutoDraft.existingChannel === 'auto', JSON.stringify({ preferred: bdAutoDraft?.preferred, existing: bdAutoDraft?.existingChannel }))
|
|
5911
|
+
bdView.cancelAddChannel()
|
|
5912
|
+
bdView.destroy()
|
|
5913
|
+
bdHost.remove()
|
|
5914
|
+
|
|
5478
5915
|
/* 隐藏 / 显示、节点拆分、通道变化对账:接口级自测 */
|
|
5479
5916
|
const splitHost = document.createElement('div')
|
|
5480
5917
|
splitHost.style.cssText = 'width:640px;height:380px'
|
|
@@ -5591,6 +6028,109 @@
|
|
|
5591
6028
|
lineOnlyView.removeLine('lo-ab')
|
|
5592
6029
|
const lineOnlyAfterDelete = lineOnlyView.getChangedLines().find(line => line.id === 'lo-ab')
|
|
5593
6030
|
check('getChangedLines:只有节点和线路时独立对账,删线路后同步清账', lineOnlyInitial && Boolean(lineOnlyChange) && lineOnlyChange.fields.includes('name') && !lineOnlyAfterDelete, JSON.stringify({ initial: lineOnlyInitial, change: lineOnlyChange?.reasons, after: lineOnlyAfterDelete ?? null }))
|
|
6031
|
+
const autoSplitHost = document.createElement('div')
|
|
6032
|
+
autoSplitHost.style.cssText = 'width:480px;height:280px'
|
|
6033
|
+
document.body.append(autoSplitHost)
|
|
6034
|
+
const autoSplitView = createGisView(autoSplitHost, {
|
|
6035
|
+
points: [
|
|
6036
|
+
{ id: 'as-hub', x: 0, y: 0 },
|
|
6037
|
+
{ id: 'as-a', x: 8, y: 0 },
|
|
6038
|
+
{ id: 'as-b', x: 0, y: 8 }
|
|
6039
|
+
],
|
|
6040
|
+
lines: [
|
|
6041
|
+
{ id: 'as-c1', from: 'as-hub', to: 'as-a' },
|
|
6042
|
+
{ id: 'as-c2', from: 'as-hub', to: 'as-a' },
|
|
6043
|
+
{ id: 'as-i1', from: 'as-hub', to: 'as-b' },
|
|
6044
|
+
{ id: 'as-i2', from: 'as-hub', to: 'as-b' },
|
|
6045
|
+
{ id: 'as-i3', from: 'as-hub', to: 'as-a' }
|
|
6046
|
+
],
|
|
6047
|
+
channels: [{ id: 'as-ch', from: 'as-hub', to: 'as-a', lineIds: ['as-c1', 'as-c2'] }],
|
|
6048
|
+
showGrid: false
|
|
6049
|
+
})
|
|
6050
|
+
const autoSplit = autoSplitView.splitPoint('as-hub', [
|
|
6051
|
+
{ x: -1, y: 0, name: '解除 A' },
|
|
6052
|
+
{ x: 1, y: 0, name: '解除 B' }
|
|
6053
|
+
], { autoLines: 'alternate' })
|
|
6054
|
+
const autoAssignSlot = id => autoSplit.lineAssignments.find(assignment => assignment.lineIds.includes(id))?.index
|
|
6055
|
+
const autoChannelSlotA = autoAssignSlot('as-c1')
|
|
6056
|
+
const autoChannelSlotB = autoAssignSlot('as-c2')
|
|
6057
|
+
const autoSplitChannel = autoSplitView.getOptions().channels.find(channel => channel.id === 'as-ch')
|
|
6058
|
+
const autoSplitNewPoint = autoSplit.pointIds[1]
|
|
6059
|
+
check('splitPoint({ autoLines: alternate }):按通道成组轮流分配,两条同通道回线不会拆到不同节点', autoSplit.ok === true && autoSplit.autoLines === 'alternate' && autoSplit.autoAssignedLines === 5 && autoChannelSlotA !== undefined && autoChannelSlotA === autoChannelSlotB, JSON.stringify({ autoLines: autoSplit.autoLines, assigned: autoSplit.autoAssignedLines, slots: [autoChannelSlotA, autoChannelSlotB], assignments: autoSplit.lineAssignments.map(item => `${item.index}:${item.lineIds.join('+')}`) }))
|
|
6060
|
+
check('splitPoint({ autoLines: alternate }):通道端点跟随整组成员,lineAssignments 报出每一份的线路条数', autoSplit.lineAssignments.length === 2 && autoSplit.lineAssignments.reduce((sum, item) => sum + item.lineCount, 0) === 5 && String(autoSplitChannel?.from) === String(autoSplit.pointIds[autoChannelSlotA]) && String(autoSplitChannel?.to) === 'as-a' && autoSplitNewPoint !== 'as-hub', JSON.stringify({ channelFrom: autoSplitChannel?.from, newPoint: autoSplitNewPoint, counts: autoSplit.lineAssignments.map(item => item.lineCount) }))
|
|
6061
|
+
autoSplitView.destroy()
|
|
6062
|
+
autoSplitHost.remove()
|
|
6063
|
+
|
|
6064
|
+
/* 首屏 center / scale 简写、比例 API 与空闲检测 */
|
|
6065
|
+
const cameraApiHost = document.createElement('div')
|
|
6066
|
+
cameraApiHost.style.cssText = 'width:480px;height:280px'
|
|
6067
|
+
document.body.append(cameraApiHost)
|
|
6068
|
+
const cameraApiView = createGisView(cameraApiHost, {
|
|
6069
|
+
points: [{ id: 'camera-api-a', x: 0, y: 0 }],
|
|
6070
|
+
center: [12, -8],
|
|
6071
|
+
scale: 3,
|
|
6072
|
+
showGrid: false
|
|
6073
|
+
})
|
|
6074
|
+
await settle()
|
|
6075
|
+
const shorthandCamera = cameraApiView.getCamera()
|
|
6076
|
+
check('创建时顶层 center / scale 直接决定首屏相机', Math.abs(shorthandCamera.center[0] - 12) < 0.01 && Math.abs(shorthandCamera.center[1] + 8) < 0.01 && Math.abs(shorthandCamera.scale - 3) < 0.001, JSON.stringify(shorthandCamera))
|
|
6077
|
+
check('getScale / setScale:读写同一份相机比例', Math.abs(cameraApiView.getScale() - 3) < 0.001 && cameraApiView.setScale(5, [100, 80]) === cameraApiView && Math.abs(cameraApiView.getScale() - 5) < 0.001 && Math.abs(cameraApiView.getCamera().scale - 5) < 0.001, JSON.stringify({ before: 3, after: cameraApiView.getScale() }))
|
|
6078
|
+
cameraApiView.updateOptions({ scale: 4 })
|
|
6079
|
+
cameraApiView.applyInitialView()
|
|
6080
|
+
const mergedScaleCamera = cameraApiView.getCamera()
|
|
6081
|
+
cameraApiView.updateOptions({ center: [20, -10] })
|
|
6082
|
+
cameraApiView.applyInitialView()
|
|
6083
|
+
const mergedInitialView = cameraApiView.getCamera()
|
|
6084
|
+
check('updateOptions 的 center / scale 简写合进同一份 initialView,不会互相冲掉', Math.abs(mergedScaleCamera.center[0] - 12) < 0.01 && Math.abs(mergedScaleCamera.center[1] + 8) < 0.01 && Math.abs(mergedScaleCamera.scale - 4) < 0.001 && Math.abs(mergedInitialView.center[0] - 20) < 0.01 && Math.abs(mergedInitialView.center[1] + 10) < 0.01 && Math.abs(mergedInitialView.scale - 4) < 0.001, JSON.stringify({ afterScale: mergedScaleCamera, afterCenter: mergedInitialView }))
|
|
6085
|
+
cameraApiView.destroy()
|
|
6086
|
+
cameraApiHost.remove()
|
|
6087
|
+
|
|
6088
|
+
const precedenceHost = document.createElement('div')
|
|
6089
|
+
precedenceHost.style.cssText = 'width:480px;height:280px'
|
|
6090
|
+
document.body.append(precedenceHost)
|
|
6091
|
+
const precedenceView = createGisView(precedenceHost, {
|
|
6092
|
+
points: [{ id: 'precedence-a', x: 0, y: 0 }],
|
|
6093
|
+
center: [1, 2],
|
|
6094
|
+
scale: 9,
|
|
6095
|
+
initialView: { center: [30, 40], scale: 2 },
|
|
6096
|
+
showGrid: false
|
|
6097
|
+
})
|
|
6098
|
+
await settle()
|
|
6099
|
+
const precedenceCamera = precedenceView.getCamera()
|
|
6100
|
+
check('显式 initialView 整份优先于顶层 center / scale 简写', Math.abs(precedenceCamera.center[0] - 30) < 0.01 && Math.abs(precedenceCamera.center[1] - 40) < 0.01 && Math.abs(precedenceCamera.scale - 2) < 0.001, JSON.stringify(precedenceCamera))
|
|
6101
|
+
precedenceView.destroy()
|
|
6102
|
+
precedenceHost.remove()
|
|
6103
|
+
|
|
6104
|
+
const idleHost = document.createElement('div')
|
|
6105
|
+
idleHost.style.cssText = 'width:480px;height:280px'
|
|
6106
|
+
document.body.append(idleHost)
|
|
6107
|
+
const idleCalls = []
|
|
6108
|
+
const idleBusCalls = []
|
|
6109
|
+
const idleView = createGisView(idleHost, {
|
|
6110
|
+
points: [{ id: 'idle-a', x: 0, y: 0 }],
|
|
6111
|
+
idleTimeout: 45,
|
|
6112
|
+
onIdle: payload => idleCalls.push(payload),
|
|
6113
|
+
showGrid: false
|
|
6114
|
+
})
|
|
6115
|
+
idleView.on('idle', payload => idleBusCalls.push(payload))
|
|
6116
|
+
const idleStartedAt = idleView.getIdleTime()
|
|
6117
|
+
await settleMs(110)
|
|
6118
|
+
const firstIdle = idleCalls[0]
|
|
6119
|
+
const afterFirstIdle = idleView.getIdleTime()
|
|
6120
|
+
await settleMs(80)
|
|
6121
|
+
check('idleTimeout:到点只回调一次,getIdleTime 持续增长', idleStartedAt >= 0 && idleCalls.length === 1 && idleBusCalls.length === 1 && firstIdle?.timeout === 45 && firstIdle?.idleTime >= 40 && afterFirstIdle >= firstIdle?.idleTime, JSON.stringify({ start: idleStartedAt, after: afterFirstIdle, calls: idleCalls.length, bus: idleBusCalls.length, payload: firstIdle }))
|
|
6122
|
+
check('idleTimeout:同一空闲段不重复触发', idleCalls.length === 1 && idleBusCalls.length === 1, JSON.stringify({ calls: idleCalls.length, bus: idleBusCalls.length }))
|
|
6123
|
+
const idleBox = idleView.canvas.getBoundingClientRect()
|
|
6124
|
+
idleView.canvas.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, cancelable: true, pointerId: 31, pointerType: 'mouse', clientX: idleBox.left + 20, clientY: idleBox.top + 20 }))
|
|
6125
|
+
const afterActivity = idleView.getIdleTime()
|
|
6126
|
+
await settleMs(110)
|
|
6127
|
+
check('idleTimeout:用户操作重置 getIdleTime,并开启下一段空闲触发', afterActivity < 40 && idleCalls.length === 2 && idleBusCalls.length === 2, JSON.stringify({ afterActivity, calls: idleCalls.length, bus: idleBusCalls.length }))
|
|
6128
|
+
idleView.destroy()
|
|
6129
|
+
const idleBeforeDestroyWait = idleCalls.length
|
|
6130
|
+
await settleMs(80)
|
|
6131
|
+
check('destroy 后清除 idle 定时器,不再触发回调', idleCalls.length === idleBeforeDestroyWait, JSON.stringify({ before: idleBeforeDestroyWait, after: idleCalls.length }))
|
|
6132
|
+
idleHost.remove()
|
|
6133
|
+
|
|
5594
6134
|
lineOnlyView.destroy()
|
|
5595
6135
|
lineOnlyHost.remove()
|
|
5596
6136
|
splitView.destroy()
|