gis-view-webgl2 0.1.1 → 0.1.3
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 +23 -7
- package/dist/gis-view.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +563 -61
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +182 -0
- package/docs/GisView.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 +165 -0
- package/docs/api/index.html +1 -1
- package/docs/demos/ApiCoverageDemo.vue +644 -0
- package/docs/demos/ChannelDemo.vue +580 -18
- package/docs/demos/ClusterDemo.vue +251 -0
- package/docs/demos/CopyMergeDemo.vue +60 -10
- package/docs/demos/EventDemo.vue +3 -3
- package/docs/demos/GisViewDemo.vue +64 -8809
- package/docs/demos/LabelDemo.vue +67 -11
- package/docs/demos/api-index.js +58 -41
- package/docs/demos/demos.js +33 -16
- package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +28 -0
- package/docs/demos/gis-view-demo/GisViewDemoDialogs.vue +82 -0
- package/docs/demos/gis-view-demo/GisViewDemoFooter.vue +16 -0
- package/docs/demos/gis-view-demo/GisViewDemoHeading.vue +16 -0
- package/docs/demos/gis-view-demo/GisViewDemoPanelStack.vue +71 -0
- package/docs/demos/gis-view-demo/GisViewDemoStage.vue +219 -0
- package/docs/demos/gis-view-demo/demo.css +1230 -0
- package/docs/demos/gis-view-demo/demoContext.js +12 -0
- package/docs/demos/gis-view-demo/panels/DataPanels.vue +303 -0
- package/docs/demos/gis-view-demo/panels/OpsPanels.vue +174 -0
- package/docs/demos/gis-view-demo/panels/StylePanels.vue +561 -0
- package/docs/demos/gis-view-demo/panels/ViewPanels.vue +396 -0
- package/docs/demos/gis-view-demo/useGisViewDemo.js +6188 -0
- 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 +4 -2
- 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/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 +3 -2
- package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +1 -1
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +10 -4
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +105 -16
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +8 -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 +100 -0
- 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 +78 -3
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +31 -5
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +64 -10
- 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 +65 -8
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +54 -1
- package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +3 -0
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +35 -19
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +5 -0
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +18 -6
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +47 -22
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +80 -13
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +27 -0
- 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 +5 -5
- package/docs/steps/README.md +18 -10
- package/docs/steps/selftest-actions.html +3 -5
- package/docs/steps/selftest-demo.html +27 -18
- package/docs/steps/selftest-demos.html +578 -53
- package/docs/steps/selftest-docs.html +7 -6
- package/docs/steps/selftest-effects.html +200 -0
- package/docs/steps/selftest-features.html +725 -12
- package/docs/steps/selftest-labels.html +323 -4
- package/docs/steps/selftest-perf.html +293 -20
- package/docs/steps/selftest-scale.html +52 -29
- package/docs/steps/step-10-full.js +1 -1
- package/docs/steps/step-12-events.js +1 -1
- package/docs//346/216/245/345/217/243/345/215/217/350/256/256.md +17 -11
- package/package.json +2 -2
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import { createApp } from 'vue'
|
|
26
26
|
import App from '/src/App.vue'
|
|
27
27
|
import { channelDraftArrowPlacement } from '/src/gis-view/engine.js'
|
|
28
|
+
import { createMinimap } from '/src/gis-view/minimap.js'
|
|
28
29
|
const results = []
|
|
29
30
|
const flush = () => { document.getElementById('out').textContent = results.join('\n') }
|
|
30
31
|
const check = (name, ok, extra = '') => {
|
|
@@ -90,8 +91,8 @@
|
|
|
90
91
|
// 1. 外壳:默认综合示例,页签齐全
|
|
91
92
|
const loaded = await canvasReady()
|
|
92
93
|
check('演示外壳把默认演示(综合示例)挂起来了', Boolean(loaded), loaded ? 'canvas 就绪' : '没等到 canvas')
|
|
93
|
-
check('页签列出全部演示(
|
|
94
|
-
check('页头的演示数量直接跟随注册表(
|
|
94
|
+
check('页签列出全部演示(22 个:21 个功能 + 综合)', tabs().length === 22, `${tabs().length} 个页签:${tabs().map(tab => tab.textContent.trim()).join(' / ')}`)
|
|
95
|
+
check('页头的演示数量直接跟随注册表(22 个)', (document.querySelector('.page-header__count')?.textContent ?? '').includes('22 个'), document.querySelector('.page-header__count')?.textContent?.trim() ?? '没有页头计数')
|
|
95
96
|
check('默认页签是综合示例', (document.querySelector('.demo-shell__tab--active')?.textContent ?? '').includes('综合'), document.querySelector('.demo-shell__tab--active')?.textContent?.trim() ?? '')
|
|
96
97
|
check('默认页签写进地址栏(刷新还停在同一个演示)', location.search.includes('demo=full'), location.search)
|
|
97
98
|
check('综合示例的面板照旧在(拆分没有动它)', document.querySelectorAll('.demo-panel-buttons button').length >= 6, `${document.querySelectorAll('.demo-panel-buttons button').length} 个面板按钮`)
|
|
@@ -121,7 +122,7 @@
|
|
|
121
122
|
const afterBack = document.querySelector('.demo-shell__tab--active')?.textContent.trim() ?? ''
|
|
122
123
|
check('键盘 [ 往回翻(基础视图回到综合示例)', afterBack.includes('综合'), afterBack)
|
|
123
124
|
const eyebrow = document.querySelector('.demo-shell__eyebrow')?.textContent.trim() ?? ''
|
|
124
|
-
check('当前演示显示分组与序号(综合 ·
|
|
125
|
+
check('当前演示显示分组与序号(综合 · 22 / 22)', eyebrow.includes('综合') && eyebrow.includes('22 / 22'), eyebrow)
|
|
125
126
|
document.querySelector('[data-demo=demo-next]')?.click()
|
|
126
127
|
await wait(60)
|
|
127
128
|
const afterStepNext = document.querySelector('.demo-shell__tab--active')?.textContent.trim() ?? ''
|
|
@@ -133,10 +134,93 @@
|
|
|
133
134
|
document.body.appendChild(keyGuard)
|
|
134
135
|
keyGuard.dispatchEvent(new KeyboardEvent('keydown', { key: ']', bubbles: true }))
|
|
135
136
|
await wait(60)
|
|
136
|
-
const afterGuard = document.querySelector('.demo-shell__tab--active')?.textContent.trim() ?? ''
|
|
137
|
-
keyGuard.remove()
|
|
138
|
-
check('焦点在输入框里时键盘 [ / ] 不抢键', afterGuard.includes('综合'), afterGuard)
|
|
139
|
-
// 2
|
|
137
|
+
const afterGuard = document.querySelector('.demo-shell__tab--active')?.textContent.trim() ?? ''
|
|
138
|
+
keyGuard.remove()
|
|
139
|
+
check('焦点在输入框里时键盘 [ / ] 不抢键', afterGuard.includes('综合'), afterGuard)
|
|
140
|
+
// 1.2 鹰眼图线路层:同一份数据补画命中缓存,固定范围外的线路不投影 / 不建路径
|
|
141
|
+
const miniHost = document.createElement('div')
|
|
142
|
+
miniHost.style.cssText = 'position:absolute;left:-9999px;width:180px;height:120px'
|
|
143
|
+
document.body.append(miniHost)
|
|
144
|
+
const miniData = {
|
|
145
|
+
points: [{ x: 50, y: 50 }],
|
|
146
|
+
lines: [
|
|
147
|
+
{ path: [[0, 0], [100, 0]], minX: 0, minY: 0, maxX: 100, maxY: 0 },
|
|
148
|
+
{ path: [[1000, 1000], [1100, 1000]], minX: 1000, minY: 1000, maxX: 1100, maxY: 1000 }
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
const mini = createMinimap(miniHost, {
|
|
152
|
+
enabled: true,
|
|
153
|
+
width: 180,
|
|
154
|
+
height: 120,
|
|
155
|
+
background: null,
|
|
156
|
+
extent: { minX: 0, minY: 0, maxX: 100, maxY: 100 },
|
|
157
|
+
maxPixelRatio: 1
|
|
158
|
+
}, {
|
|
159
|
+
getData: () => miniData,
|
|
160
|
+
getCamera: () => ({ center: [50, 50], scale: 1 }),
|
|
161
|
+
getViewport: () => ({ width: 180, height: 120, pixelRatio: 1, displayScale: 1 }),
|
|
162
|
+
getFallbackBounds: () => ({ minX: 0, minY: 0, maxX: 100, maxY: 100 }),
|
|
163
|
+
getZoomBaseScale: () => 0,
|
|
164
|
+
getBackground: () => null
|
|
165
|
+
})
|
|
166
|
+
await wait(220)
|
|
167
|
+
const miniFirst = mini.getPaintStats()
|
|
168
|
+
check('鹰眼图线路层:首帧只铺固定范围里的线路(范围外那条被跳过)',
|
|
169
|
+
miniFirst.lineRasterDraws > 0 && miniFirst.linePathsDrawn === miniFirst.lineRasterDraws,
|
|
170
|
+
JSON.stringify(miniFirst))
|
|
171
|
+
mini.refresh()
|
|
172
|
+
await wait(100)
|
|
173
|
+
const miniRepeat = mini.getPaintStats()
|
|
174
|
+
check('鹰眼图线路层:同一份数据补画命中缓存(不再重复投影 / stroke)',
|
|
175
|
+
miniRepeat.lineRasterHits > miniFirst.lineRasterHits && miniRepeat.lineRasterDraws === miniFirst.lineRasterDraws,
|
|
176
|
+
JSON.stringify({ first: miniFirst, repeat: miniRepeat }))
|
|
177
|
+
mini.setZoom(2)
|
|
178
|
+
await wait(100)
|
|
179
|
+
const miniZoomed = mini.getPaintStats()
|
|
180
|
+
check('鹰眼图线路层:小图自身缩放变化时只重铺线路层一次(范围外线路仍被裁剪)',
|
|
181
|
+
miniZoomed.lineRasterDraws === miniRepeat.lineRasterDraws + 1 && miniZoomed.linePathsDrawn === miniRepeat.linePathsDrawn,
|
|
182
|
+
JSON.stringify({ repeat: miniRepeat, zoomed: miniZoomed }))
|
|
183
|
+
mini.destroy()
|
|
184
|
+
miniHost.remove()
|
|
185
|
+
// 节点抗锯齿:单独一个无底图 / 无线路的小图,边缘应出现 0 < alpha < 255 的覆盖率像素。
|
|
186
|
+
const aaHost = document.createElement('div')
|
|
187
|
+
aaHost.style.cssText = 'position:absolute;left:-9999px;width:180px;height:120px'
|
|
188
|
+
document.body.append(aaHost)
|
|
189
|
+
const aaData = { points: [{ x: 50, y: 50, size: 5.5, color: '#ff0000' }], lines: [] }
|
|
190
|
+
const aa = createMinimap(aaHost, {
|
|
191
|
+
enabled: true,
|
|
192
|
+
width: 180,
|
|
193
|
+
height: 120,
|
|
194
|
+
background: null,
|
|
195
|
+
extent: { minX: 0, minY: 0, maxX: 100, maxY: 100 },
|
|
196
|
+
maxPixelRatio: 1
|
|
197
|
+
}, {
|
|
198
|
+
getData: () => aaData,
|
|
199
|
+
getCamera: () => ({ center: [50, 50], scale: 1 }),
|
|
200
|
+
getViewport: () => ({ width: 180, height: 120, pixelRatio: 1, displayScale: 1 }),
|
|
201
|
+
getFallbackBounds: () => ({ minX: 0, minY: 0, maxX: 100, maxY: 100 }),
|
|
202
|
+
getZoomBaseScale: () => 0,
|
|
203
|
+
getBackground: () => null
|
|
204
|
+
})
|
|
205
|
+
await wait(180)
|
|
206
|
+
const aaCanvas = aaHost.querySelector('canvas')
|
|
207
|
+
const aaPixels = aaCanvas.getContext('2d').getImageData(0, 0, aaCanvas.width, aaCanvas.height).data
|
|
208
|
+
let aaSolid = 0
|
|
209
|
+
let aaSoft = 0
|
|
210
|
+
for (let offset = 3; offset < aaPixels.length; offset += 4) {
|
|
211
|
+
if (aaPixels[offset] === 255) {
|
|
212
|
+
aaSolid += 1
|
|
213
|
+
}
|
|
214
|
+
else if (aaPixels[offset] > 0) {
|
|
215
|
+
aaSoft += 1
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
check('鹰眼图:节点边缘按像素覆盖率抗锯齿(既有实心像素也有半透明边缘)',
|
|
219
|
+
aaSolid > 0 && aaSoft > 0,
|
|
220
|
+
`实心 ${aaSolid} px / 半透明 ${aaSoft} px`)
|
|
221
|
+
aa.destroy()
|
|
222
|
+
aaHost.remove()
|
|
223
|
+
// 2. 每个功能演示都切一遍:画布起得来、说明与读数有东西
|
|
140
224
|
const demoSpecs = [
|
|
141
225
|
{ tab: '基础视图', id: 'basic' },
|
|
142
226
|
{ tab: '标签交互', id: 'labels' },
|
|
@@ -153,6 +237,7 @@
|
|
|
153
237
|
{ tab: '扩展能力', id: 'extension' },
|
|
154
238
|
{ tab: '编辑与保存', id: 'backend' },
|
|
155
239
|
{ tab: '性能与质量', id: 'perf' },
|
|
240
|
+
{ tab: '点聚合', id: 'cluster' },
|
|
156
241
|
{ tab: '动画效果', id: 'effects' },
|
|
157
242
|
{ tab: '扩展图形', id: 'graphic' }
|
|
158
243
|
]
|
|
@@ -162,10 +247,82 @@
|
|
|
162
247
|
const panel = document.querySelector('.dv-demo .dv-panel')
|
|
163
248
|
const active = document.querySelector('.demo-shell__tab--active')?.textContent.trim() ?? ''
|
|
164
249
|
check(`切到「${spec.tab}」:画布与读数面板都起来了`, Boolean(ready) && Boolean(panel) && active === spec.tab, `${ready ? 'canvas 就绪' : '没有 canvas'} / ${panel ? `${panel.querySelectorAll('.dv-group').length} 组读数` : '没有面板'} / 当前页签 ${active}`)
|
|
165
|
-
await wait(260)
|
|
250
|
+
await wait(260)
|
|
166
251
|
check(`「${spec.tab}」把演示 id 写进地址栏(?demo=${spec.id})`, location.search.includes(`demo=${spec.id}`), location.search)
|
|
167
252
|
}
|
|
168
253
|
|
|
254
|
+
// 2.0 点聚合:控件齐全、聚合圆点击只发 clusterClick,maxZoom 展开后又回来
|
|
255
|
+
if (await clickTab('点聚合')) {
|
|
256
|
+
const ready = await canvasReady()
|
|
257
|
+
await wait(500)
|
|
258
|
+
const demo = document.querySelector('.dv-demo')
|
|
259
|
+
const setup = demo?.__vueParentComponent?.setupState
|
|
260
|
+
const view = await until(() => setup?.view, 8000)
|
|
261
|
+
const text = key => document.querySelector(`[data-demo=${key}]`)?.textContent.trim() ?? ''
|
|
262
|
+
const canvas = demo?.querySelector('canvas.gis-view__canvas') ?? demo?.querySelector('canvas')
|
|
263
|
+
const toggle = document.querySelector('[data-demo=cluster-toggle]')
|
|
264
|
+
const radiusInput = document.querySelector('[data-demo=cluster-radius]')
|
|
265
|
+
const minPointsSelect = document.querySelector('[data-demo=cluster-min-points]')
|
|
266
|
+
const maxZoomSelect = document.querySelector('[data-demo=cluster-max-zoom]')
|
|
267
|
+
const countSelect = document.querySelector('[data-demo=cluster-count]')
|
|
268
|
+
const anchor = await until(() => {
|
|
269
|
+
const node = document.querySelector('[data-demo=cluster-first-client]')
|
|
270
|
+
return node?.dataset.clientX !== '' && node?.dataset.clientY !== '' ? node : null
|
|
271
|
+
}, 8000)
|
|
272
|
+
if (ready && view && canvas && toggle && radiusInput && minPointsSelect && maxZoomSelect && countSelect) {
|
|
273
|
+
const initialClusters = view.getClusters().filter(item => item.count > 1)
|
|
274
|
+
check('点聚合:配置控件齐全,初始数据已聚合并给出读数',
|
|
275
|
+
initialClusters.length > 0 && text('cluster-stats').includes('原始点') && text('cluster-stats').includes('个聚合圆'),
|
|
276
|
+
`${initialClusters.length} 个多成员圆 / ${text('cluster-stats')}`)
|
|
277
|
+
const selectedBefore = view.getSelections().map(entry => entry.item?.id ?? entry.index).join(',')
|
|
278
|
+
const clientX = Number(anchor?.dataset.clientX)
|
|
279
|
+
const clientY = Number(anchor?.dataset.clientY)
|
|
280
|
+
if (Number.isFinite(clientX) && Number.isFinite(clientY)) {
|
|
281
|
+
tap(canvas, { clientX, clientY })
|
|
282
|
+
await wait(180)
|
|
283
|
+
}
|
|
284
|
+
const selectedAfter = view.getSelections().map(entry => entry.item?.id ?? entry.index).join(',')
|
|
285
|
+
check('点聚合:点中聚合圆只增加 clusterClick,普通 click 为 0、选中集不变',
|
|
286
|
+
text('cluster-click-count').includes('0 次普通点击 / 1 次聚合点击') && text('cluster-event').includes('clusterClick') && selectedBefore === selectedAfter,
|
|
287
|
+
`${text('cluster-click-count')} / ${text('cluster-event')} / 选中 ${selectedBefore || '空'} → ${selectedAfter || '空'}`)
|
|
288
|
+
maxZoomSelect.value = '2'
|
|
289
|
+
maxZoomSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
290
|
+
await wait(220)
|
|
291
|
+
view.zoomBy(3)
|
|
292
|
+
await wait(380)
|
|
293
|
+
const zoomed = view.getPointCluster()
|
|
294
|
+
check('点聚合:maxZoom 控件生效,放大超过阈值后展开原始点',
|
|
295
|
+
zoomed?.active === false && view.getStats().clusterCount === 0,
|
|
296
|
+
`active=${zoomed?.active} clusterCount=${view.getStats().clusterCount} maxZoom=${zoomed?.maxZoom}`)
|
|
297
|
+
document.querySelector('[data-demo=cluster-fit]')?.click()
|
|
298
|
+
await wait(420)
|
|
299
|
+
check('点聚合:适应全部后回到聚合档,聚合圆重新出现',
|
|
300
|
+
view.getPointCluster()?.active === true && view.getStats().clusterCount > 0,
|
|
301
|
+
`active=${view.getPointCluster()?.active} clusterCount=${view.getStats().clusterCount}`)
|
|
302
|
+
radiusInput.value = '24'
|
|
303
|
+
radiusInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
304
|
+
radiusInput.dispatchEvent(new Event('change', { bubbles: true }))
|
|
305
|
+
await wait(220)
|
|
306
|
+
countSelect.value = '2000'
|
|
307
|
+
countSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
308
|
+
document.querySelector('[data-demo=cluster-rebuild]')?.click()
|
|
309
|
+
const rebuilt = await until(() => view.getData().points.length === 2000, 8000)
|
|
310
|
+
await wait(260)
|
|
311
|
+
check('点聚合:半径可调、重建到 2000 点后统计跟着换',
|
|
312
|
+
Boolean(rebuilt) && view.getPointCluster()?.radius === 24 && view.getStats().pointCount === 2000 && view.getStats().clusterCount > 0,
|
|
313
|
+
`radius=${view.getPointCluster()?.radius} pointCount=${view.getStats().pointCount} clusterCount=${view.getStats().clusterCount}`)
|
|
314
|
+
toggle.checked = false
|
|
315
|
+
toggle.dispatchEvent(new Event('change', { bubbles: true }))
|
|
316
|
+
await wait(300)
|
|
317
|
+
check('点聚合:关掉开关后原始点恢复、聚合统计归零',
|
|
318
|
+
view.getPointCluster()?.enabled === false && view.getStats().clusterCount === 0 && view.getStats().clusterPointCount === 0,
|
|
319
|
+
JSON.stringify({ cluster: view.getPointCluster(), stats: { count: view.getStats().clusterCount, points: view.getStats().clusterPointCount } }))
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
check('点聚合:演示页、实例与六个控件都起来了', false, ready ? '缺少 demo / setupState.view / 控件' : '没有 canvas')
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
169
326
|
// 2.0 动态装饰器:吸附方式与位置来源正交,两种吸附方式都能选三种位置
|
|
170
327
|
if (await clickTab('动态装饰器')) {
|
|
171
328
|
await canvasReady()
|
|
@@ -806,10 +963,24 @@
|
|
|
806
963
|
`${conflictPreview.slice(-320).replace(/\s+/g, ' ')} / ${text('copy-backend-readout')}`)
|
|
807
964
|
copyRetryButton.click()
|
|
808
965
|
const copyRebased = await until(() => text('copy-backend-readout').includes('200 OK') && !text('copy-backend-readout').includes('失败'), 5000)
|
|
809
|
-
check('批量复制与合并:409 后 rebase 生成新 opId 并成功落库',
|
|
810
|
-
Boolean(copyRebased) && setup.backendSnapshot?.version === conflictVersion + 2 && setup.backendSnapshot?.applied === conflictApplied + 1,
|
|
811
|
-
`v${conflictVersion} → v${setup.backendSnapshot?.version} / applied ${conflictApplied} → ${setup.backendSnapshot?.applied} / ${text('copy-backend-readout')}`)
|
|
812
|
-
|
|
966
|
+
check('批量复制与合并:409 后 rebase 生成新 opId 并成功落库',
|
|
967
|
+
Boolean(copyRebased) && setup.backendSnapshot?.version === conflictVersion + 2 && setup.backendSnapshot?.applied === conflictApplied + 1,
|
|
968
|
+
`v${conflictVersion} → v${setup.backendSnapshot?.version} / applied ${conflictApplied} → ${setup.backendSnapshot?.applied} / ${text('copy-backend-readout')}`)
|
|
969
|
+
// 拆分:先选一组,再点演示按钮;节点 +1、读数落到面板,撤销一步回到拆分前
|
|
970
|
+
click('copy-select-sources')
|
|
971
|
+
const splitSelectionReady = await until(() => view.getSelectionData().pointCount === 4, 5000)
|
|
972
|
+
const splitSource = view.getSelectionData().points?.[0] ?? view.getData().points[0]
|
|
973
|
+
const pointsBeforeSplit = view.getData().points.length
|
|
974
|
+
click('split-selected')
|
|
975
|
+
const splitLanded = await until(() => view.getData().points.length === pointsBeforeSplit + 1 && text('split-readout').includes('splitPoint'), 6000)
|
|
976
|
+
const splitReadout = text('split-readout')
|
|
977
|
+
check('批量复制与合并:拆分选中节点走 splitPoint,第二份与改线 / 改通道读数落面板',
|
|
978
|
+
Boolean(splitSelectionReady) && Boolean(splitLanded) && text('copy-action').includes('splitPoint') && splitReadout.includes('改线') && splitReadout.includes('改通道'),
|
|
979
|
+
`${pointsBeforeSplit} → ${view.getData().points.length} · ${splitReadout}`)
|
|
980
|
+
view.undo()
|
|
981
|
+
const splitUndone = await until(() => view.getData().points.length === pointsBeforeSplit && view.getData().points.some(point => String(point.id) === String(splitSource?.id)), 5000)
|
|
982
|
+
check('批量复制与合并:拆分是一步历史,撤销后节点与连线恢复', Boolean(splitUndone), `${view.getData().points.length} 点 / ${splitSource?.id ?? '—'}`)
|
|
983
|
+
}
|
|
813
984
|
else {
|
|
814
985
|
check('批量复制与合并:画布与实例都起来了', false, ready ? '没有 setupState.view' : '没有 canvas')
|
|
815
986
|
}
|
|
@@ -1059,14 +1230,29 @@
|
|
|
1059
1230
|
|
|
1060
1231
|
// 3. 回到标签演示,逐条验这一轮新增的交互
|
|
1061
1232
|
await clickTab('标签交互')
|
|
1062
|
-
await canvasReady()
|
|
1063
|
-
await until(() => document.querySelectorAll('.dv-demo .gis-view__label').length > 0, 15000)
|
|
1064
|
-
const labelNodes = () => [...document.querySelectorAll('.dv-demo .gis-view__label')]
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
const
|
|
1233
|
+
await canvasReady()
|
|
1234
|
+
await until(() => document.querySelectorAll('.dv-demo .gis-view__label').length > 0, 15000)
|
|
1235
|
+
const labelNodes = () => [...document.querySelectorAll('.dv-demo .gis-view__label')]
|
|
1236
|
+
const perItemLabelNode = id => document.querySelector(`.dv-demo .gis-view__label[data-label-kind="point"][data-label-label="${id}"]`)
|
|
1237
|
+
const labelView = () => document.querySelector('.dv-demo')?.__vueParentComponent?.setupState?.view ?? null
|
|
1238
|
+
check('标签演示:节点标签都画出来了', labelNodes().length > 0, `${labelNodes().length} 个标签节点`)
|
|
1239
|
+
document.querySelector('[data-demo="label-per-item-focus"]')?.click()
|
|
1240
|
+
const perItemReady = await until(() => {
|
|
1241
|
+
const readout = document.querySelector('[data-demo="label-per-item-readout"]')?.textContent.trim() ?? ''
|
|
1242
|
+
return readout.includes('per-item-draggable') && readout.includes('per-item-locked') && perItemLabelNode('per-item-draggable') && perItemLabelNode('per-item-locked')
|
|
1243
|
+
}, 6000)
|
|
1244
|
+
const editablePerItem = perItemLabelNode('per-item-draggable')
|
|
1245
|
+
const lockedPerItem = perItemLabelNode('per-item-locked')
|
|
1246
|
+
check('标签演示:逐条配置 A 可拖可点、B 只读(false 覆盖全局开关)',
|
|
1247
|
+
Boolean(perItemReady) &&
|
|
1248
|
+
editablePerItem?.classList.contains('gis-view__label--draggable') === true &&
|
|
1249
|
+
editablePerItem?.classList.contains('gis-view__label--editable') === true &&
|
|
1250
|
+
lockedPerItem?.classList.contains('gis-view__label--draggable') === false &&
|
|
1251
|
+
lockedPerItem?.classList.contains('gis-view__label--editable') === false &&
|
|
1252
|
+
getComputedStyle(lockedPerItem).pointerEvents === 'none',
|
|
1253
|
+
`A=${editablePerItem?.className ?? '没有'} / B=${lockedPerItem?.className ?? '没有'} / 当前 ${labelNodes().map(node => `${node.dataset.labelKind}:${node.dataset.labelIndex}:${node.dataset.labelLabel}:${node.textContent}`).join(' | ')} / ${document.querySelector('[data-demo="label-per-item-readout"]')?.textContent.trim() ?? ''}`)
|
|
1254
|
+
// 3.0 一键换标签内容:setLabelContent 只换全局 label / lineLabel 的 content(节点 + 线路一起)
|
|
1255
|
+
const overlapHideButton = document.querySelector('[data-demo="label-overlap-hide"]')
|
|
1070
1256
|
overlapHideButton?.click()
|
|
1071
1257
|
await wait(220)
|
|
1072
1258
|
const hideOverlapOptions = labelView()?.getOptions()
|
|
@@ -1094,8 +1280,8 @@
|
|
|
1094
1280
|
contentSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
1095
1281
|
await wait(300)
|
|
1096
1282
|
|
|
1097
|
-
// 3.1 点一下标签 → labelClick
|
|
1098
|
-
const target = labelNodes()[0]
|
|
1283
|
+
// 3.1 点一下标签 → labelClick
|
|
1284
|
+
const target = labelNodes().find(node => node.classList.contains('gis-view__label--editable')) ?? labelNodes()[0]
|
|
1099
1285
|
tap(target)
|
|
1100
1286
|
const clickLogged = await until(() => logText().includes('labelClick'), 8000)
|
|
1101
1287
|
check('点一下标签发 labelClick,日志记下「哪一条、内容是什么」', Boolean(clickLogged), logText().slice(0, 160))
|
|
@@ -1426,10 +1612,61 @@
|
|
|
1426
1612
|
const channelSetup = channelDemo.__vueParentComponent.setupState
|
|
1427
1613
|
const channelView = await until(() => channelSetup.view, 8000)
|
|
1428
1614
|
await until(() => (channelView?.getChannels() ?? []).length > 0, 8000)
|
|
1429
|
-
const channelField = key => channelDemo.querySelector(`[data-demo="${key}"]`)?.textContent.trim() ?? ''
|
|
1430
|
-
const channelCanvas = channelDemo.querySelector('canvas.gis-view__canvas')
|
|
1431
|
-
if (channelView && channelCanvas) {
|
|
1432
|
-
const
|
|
1615
|
+
const channelField = key => channelDemo.querySelector(`[data-demo="${key}"]`)?.textContent.trim() ?? ''
|
|
1616
|
+
const channelCanvas = channelDemo.querySelector('canvas.gis-view__canvas')
|
|
1617
|
+
if (channelView && channelCanvas) {
|
|
1618
|
+
const visibilityChannelId = channelView.getOptions().channels?.[0]?.id
|
|
1619
|
+
const groupsBeforeHide = channelView.getChannelGroups(visibilityChannelId).length
|
|
1620
|
+
const membersBeforeHide = channelView.getChannel(visibilityChannelId)?.lineIndexes?.length ?? 0
|
|
1621
|
+
channelView.clearChangedChannels()
|
|
1622
|
+
channelDemo.querySelector('[data-demo="channel-hide"]')?.click()
|
|
1623
|
+
const channelHidden = await until(() => {
|
|
1624
|
+
const declared = channelView.getOptions().channels?.find(channel => String(channel.id) === String(visibilityChannelId))
|
|
1625
|
+
return declared?.visible === false && channelView.getChannel(visibilityChannelId)?.lineIndexes?.length === 0 && channelField('channel-visibility').includes('隐藏')
|
|
1626
|
+
}, 5000)
|
|
1627
|
+
const hiddenChange = channelView.getChangedChannels().find(channel => String(channel.id) === String(visibilityChannelId))
|
|
1628
|
+
check('通道与数据:隐藏第一条通道只改 visible,成员回线一起退场且最近变化记可见性',
|
|
1629
|
+
Boolean(channelHidden) && hiddenChange?.visibilityChanged === true && hiddenChange?.reasons.includes('visibility') === true && channelField('channel-changed').includes('可见性'),
|
|
1630
|
+
`${channelField('channel-visibility')} / ${channelField('channel-changed')}`)
|
|
1631
|
+
channelDemo.querySelector('[data-demo="channel-show"]')?.click()
|
|
1632
|
+
const channelShown = await until(() => {
|
|
1633
|
+
const declared = channelView.getOptions().channels?.find(channel => String(channel.id) === String(visibilityChannelId))
|
|
1634
|
+
return declared?.visible === true && channelView.getChannel(visibilityChannelId)?.lineIndexes?.length === membersBeforeHide && channelField('channel-visibility').includes('显示')
|
|
1635
|
+
}, 5000)
|
|
1636
|
+
channelDemo.querySelector('[data-demo="channel-clear-changed"]')?.click()
|
|
1637
|
+
const changesCleared = await until(() => channelView.getChangedChannels().length === 0 && channelField('channel-changed').includes('没有'), 5000)
|
|
1638
|
+
check('通道与数据:显示后成员与分组原样恢复,通道对账后最近变化清空',
|
|
1639
|
+
Boolean(channelShown) && membersBeforeHide > 0 && channelView.getChannelGroups(visibilityChannelId).length === groupsBeforeHide && Boolean(changesCleared) && channelField('channel-visibility').includes('新基准'),
|
|
1640
|
+
`${membersBeforeHide} 回 / ${groupsBeforeHide} 组 · ${channelField('channel-visibility')} / ${channelField('channel-changed')}`)
|
|
1641
|
+
const demoPointId = channelView.getOptions().points?.[0]?.id
|
|
1642
|
+
channelDemo.querySelector('[data-demo="point-hide"]')?.click()
|
|
1643
|
+
const demoPointHidden = await until(() => {
|
|
1644
|
+
const point = channelView.getOptions().points?.find(item => String(item.id) === String(demoPointId))
|
|
1645
|
+
return point?.visible === false && channelField('channel-visibility').includes('隐藏节点')
|
|
1646
|
+
}, 5000)
|
|
1647
|
+
channelDemo.querySelector('[data-demo="point-show"]')?.click()
|
|
1648
|
+
const demoPointShown = await until(() => {
|
|
1649
|
+
const point = channelView.getOptions().points?.find(item => String(item.id) === String(demoPointId))
|
|
1650
|
+
return point?.visible === true && channelField('channel-visibility').includes('显示节点')
|
|
1651
|
+
}, 5000)
|
|
1652
|
+
check('通道与数据:demo 的节点隐藏 / 显示按钮只改 visible 并恢复原数据',
|
|
1653
|
+
Boolean(demoPointHidden) && Boolean(demoPointShown) && demoPointId !== undefined,
|
|
1654
|
+
`${String(demoPointId)} · ${channelField('channel-visibility')}`)
|
|
1655
|
+
const demoLineId = channelView.getOptions().lines?.[0]?.id
|
|
1656
|
+
channelDemo.querySelector('[data-demo="line-hide"]')?.click()
|
|
1657
|
+
const demoLineHidden = await until(() => {
|
|
1658
|
+
const line = channelView.getOptions().lines?.find(item => String(item.id) === String(demoLineId))
|
|
1659
|
+
return line?.visible === false && channelField('channel-visibility').includes('隐藏线路')
|
|
1660
|
+
}, 5000)
|
|
1661
|
+
channelDemo.querySelector('[data-demo="line-show"]')?.click()
|
|
1662
|
+
const demoLineShown = await until(() => {
|
|
1663
|
+
const line = channelView.getOptions().lines?.find(item => String(item.id) === String(demoLineId))
|
|
1664
|
+
return line?.visible === true && channelField('channel-visibility').includes('显示线路')
|
|
1665
|
+
}, 5000)
|
|
1666
|
+
check('通道与数据:demo 的线路隐藏 / 显示按钮只改 visible 并恢复原数据',
|
|
1667
|
+
Boolean(demoLineHidden) && Boolean(demoLineShown) && demoLineId !== undefined,
|
|
1668
|
+
`${String(demoLineId)} · ${channelField('channel-visibility')}`)
|
|
1669
|
+
const channelBox = channelCanvas.getBoundingClientRect()
|
|
1433
1670
|
const channelRightClick = (screen) => {
|
|
1434
1671
|
channelCanvas.dispatchEvent(new MouseEvent('contextmenu', {
|
|
1435
1672
|
bubbles: true,
|
|
@@ -1466,7 +1703,7 @@
|
|
|
1466
1703
|
}
|
|
1467
1704
|
}
|
|
1468
1705
|
const channelMenuNode = channelDemo.querySelector('.dv-channel-menu')
|
|
1469
|
-
check('通道与数据:右键通道回线弹出交互框(features.
|
|
1706
|
+
check('通道与数据:右键通道回线弹出交互框(onContextMenu.channelContext + features.contextMenu)', Boolean(menuPayload) && Boolean(channelMenuNode) && channelView.getFeatures().contextMenu === true, menuPayload ? '通道 ' + menuPayload.channelId + ' · 命中回线 ' + menuPayload.lineId + ' · 共 ' + menuPayload.groupCount + ' 组' : '没弹出')
|
|
1470
1707
|
const groupsBeforeSplit = channelView.getChannelGroups(menuPayload?.channelId).length
|
|
1471
1708
|
const channelAction = text => [...channelDemo.querySelectorAll('.dv-channel-menu__actions button')].find(node => node.textContent.trim() === text)
|
|
1472
1709
|
check('通道与数据:交互框列出 6 个 runChannelAction 动作(含取消该分组选中),拐点编辑已开(waypointEditable)', channelDemo.querySelectorAll('.dv-channel-menu__actions button').length === 6 && Boolean(channelAction('加拐点')) && !channelAction('加拐点').disabled && Boolean(channelAction('取消该分组选中')), [...channelDemo.querySelectorAll('.dv-channel-menu__actions button')].map(node => node.textContent.trim() + (node.disabled ? '(灰)' : '')).join(' / '))
|
|
@@ -1510,10 +1747,10 @@
|
|
|
1510
1747
|
// getChannelGroups 每一项只给 lineIds(lineCount 是 getChannelContext().group 的字段),回线数按 lineIds.length 算
|
|
1511
1748
|
const targetLineCount = targetGroup?.lineIds?.length ?? 0
|
|
1512
1749
|
check('通道与数据:点分组 = setCurrentChannelGroup + 选中它的回线', Boolean(targetGroup) && switchedContext?.groupId === targetGroup.id && channelView.getSelections().length === targetLineCount, switchedContext ? '当前「' + switchedContext.groupId + '」/ 选中 ' + channelView.getSelections().length + ' 回线(目标「' + (targetGroup?.id ?? '—') + '」' + targetLineCount + ' 回)' : '没有上下文')
|
|
1513
|
-
//
|
|
1514
|
-
channelRightClick(fromScreen)
|
|
1515
|
-
await wait(200)
|
|
1516
|
-
check('
|
|
1750
|
+
// 右键落到节点上:onContextMenu 不带 channelContext,框自己收掉
|
|
1751
|
+
channelRightClick(fromScreen)
|
|
1752
|
+
await wait(200)
|
|
1753
|
+
check('通道与数据:右键落到节点上时 channelContext 为空,框收掉', !channelDemo.querySelector('.dv-channel-menu') && channelSetup.channelMenu === null, '')
|
|
1517
1754
|
// 再右键回回线上、按 Esc 也能收
|
|
1518
1755
|
channelRightClick(menuScreen)
|
|
1519
1756
|
const reopenedMenu = await until(() => channelDemo.querySelector('.dv-channel-menu'), 5000)
|
|
@@ -1583,9 +1820,77 @@
|
|
|
1583
1820
|
await wait(80)
|
|
1584
1821
|
splitButton.click()
|
|
1585
1822
|
await wait(320)
|
|
1586
|
-
const straightGroup = groupsOfFirst().find(group => group.lineIds.length === 2)
|
|
1587
|
-
check('切「直连」再拆:options.path 写 [] 就真的一个拐点都不带', Boolean(straightGroup) && straightGroup.path.length === 0, JSON.stringify(groupsOfFirst().map(group => [group.id, group.lineIds, group.path])))
|
|
1588
|
-
|
|
1823
|
+
const straightGroup = groupsOfFirst().find(group => group.lineIds.length === 2)
|
|
1824
|
+
check('切「直连」再拆:options.path 写 [] 就真的一个拐点都不带', Boolean(straightGroup) && straightGroup.path.length === 0, JSON.stringify(groupsOfFirst().map(group => [group.id, group.lineIds, group.path])))
|
|
1825
|
+
// 线路分组弹框:行内确认移入只改草稿,底部确定时一次 splitChannelGroups 提交整份 groups
|
|
1826
|
+
const dialogOpen = channelDemo.querySelector('[data-demo="channel-group-dialog-open"]')
|
|
1827
|
+
const groupsBeforeDialog = JSON.stringify(groupsOfFirst().map(group => ({ id: String(group.id), lineIds: group.lineIds.map(String).sort() })))
|
|
1828
|
+
dialogOpen?.click()
|
|
1829
|
+
const groupDialogNode = await until(() => channelDemo.querySelector('[data-demo="channel-group-dialog"]'), 3000)
|
|
1830
|
+
const dialogCards = [...(groupDialogNode?.querySelectorAll('[data-demo="channel-group-dialog-card"]') ?? [])]
|
|
1831
|
+
const dialogRows = [...(groupDialogNode?.querySelectorAll('[data-demo="channel-group-dialog-line"]') ?? [])]
|
|
1832
|
+
const dialogTitle = groupDialogNode?.querySelector('h3')?.textContent.trim() ?? ''
|
|
1833
|
+
const dialogSubtitle = groupDialogNode?.querySelector('.dv-group-dialog__head p')?.textContent ?? ''
|
|
1834
|
+
check('通道与数据:线路分组弹框按通道列全分组与回线,标题 / 场站对标标识都在',
|
|
1835
|
+
Boolean(groupDialogNode) && dialogTitle === '线路分组' && dialogSubtitle.includes('场站对标标识') && dialogCards.length === groupsOfFirst().length && dialogRows.length === membersOfFirst(),
|
|
1836
|
+
`${dialogCards.length} 组 / ${dialogRows.length} 回 / ${dialogTitle} · ${dialogSubtitle.replace(/\s+/g, ' ').slice(0, 100)}`)
|
|
1837
|
+
const sourceCard = dialogCards.find(card => card.querySelectorAll('[data-demo="channel-group-dialog-line"]').length > 1) ?? dialogCards[0]
|
|
1838
|
+
const sourceRow = sourceCard?.querySelector('[data-demo="channel-group-dialog-line"]')
|
|
1839
|
+
const targetCard = dialogCards.find(card => card !== sourceCard)
|
|
1840
|
+
const draftLineId = sourceRow?.dataset.lineId
|
|
1841
|
+
const draftTargetId = targetCard?.dataset.groupId
|
|
1842
|
+
const draftTargetSelect = sourceRow?.querySelector('[data-demo="channel-group-dialog-target"]')
|
|
1843
|
+
if (draftTargetSelect && draftTargetId) {
|
|
1844
|
+
draftTargetSelect.value = draftTargetId
|
|
1845
|
+
draftTargetSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
1846
|
+
}
|
|
1847
|
+
await wait(80)
|
|
1848
|
+
sourceRow?.querySelector('[data-demo="channel-group-dialog-move"]')?.click()
|
|
1849
|
+
await wait(120)
|
|
1850
|
+
const draftGroups = JSON.stringify(groupsOfFirst().map(group => ({ id: String(group.id), lineIds: group.lineIds.map(String).sort() })))
|
|
1851
|
+
const draftText = groupDialogNode?.querySelector('[data-demo="channel-group-dialog-text"]')?.textContent ?? ''
|
|
1852
|
+
check('通道与数据:弹框里的「确认移入」只记草稿,组件数据不动',
|
|
1853
|
+
Boolean(draftLineId) && Boolean(draftTargetId) && draftGroups === groupsBeforeDialog && draftText.includes('数据还没动'),
|
|
1854
|
+
`${draftLineId} → ${draftTargetId} · ${draftText.replace(/\s+/g, ' ').slice(0, 110)}`)
|
|
1855
|
+
groupDialogNode?.querySelector('[data-demo="channel-group-dialog-cancel"]')?.click()
|
|
1856
|
+
await wait(100)
|
|
1857
|
+
const cancelledGroups = JSON.stringify(groupsOfFirst().map(group => ({ id: String(group.id), lineIds: group.lineIds.map(String).sort() })))
|
|
1858
|
+
check('通道与数据:取消分组弹框不写数据、弹框收起',
|
|
1859
|
+
!channelDemo.querySelector('[data-demo="channel-group-dialog"]') && cancelledGroups === groupsBeforeDialog,
|
|
1860
|
+
cancelledGroups === groupsBeforeDialog ? '数据未变' : '取消后分组被改了')
|
|
1861
|
+
// 重开再做一次同样的移动,这次点「确定分组」:确认只调用一次整组变更方法
|
|
1862
|
+
dialogOpen?.click()
|
|
1863
|
+
const reopenedDialog = await until(() => channelDemo.querySelector('[data-demo="channel-group-dialog"]'), 3000)
|
|
1864
|
+
const reopenedCards = [...(reopenedDialog?.querySelectorAll('[data-demo="channel-group-dialog-card"]') ?? [])]
|
|
1865
|
+
const reopenedSourceCard = reopenedCards.find(card => card.querySelectorAll('[data-demo="channel-group-dialog-line"]').length > 1) ?? reopenedCards[0]
|
|
1866
|
+
const reopenedRow = reopenedSourceCard?.querySelector('[data-demo="channel-group-dialog-line"]')
|
|
1867
|
+
const reopenedTargetCard = reopenedCards.find(card => card !== reopenedSourceCard)
|
|
1868
|
+
const movedLineId = reopenedRow?.dataset.lineId
|
|
1869
|
+
const movedTargetId = reopenedTargetCard?.dataset.groupId
|
|
1870
|
+
const reopenedSelect = reopenedRow?.querySelector('[data-demo="channel-group-dialog-target"]')
|
|
1871
|
+
if (reopenedSelect && movedTargetId) {
|
|
1872
|
+
reopenedSelect.value = movedTargetId
|
|
1873
|
+
reopenedSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
1874
|
+
}
|
|
1875
|
+
await wait(80)
|
|
1876
|
+
reopenedRow?.querySelector('[data-demo="channel-group-dialog-move"]')?.click()
|
|
1877
|
+
await wait(100)
|
|
1878
|
+
const confirmButton = reopenedDialog?.querySelector('[data-demo="channel-group-dialog-confirm"]')
|
|
1879
|
+
let splitGroupCalls = 0
|
|
1880
|
+
const originalSplitChannelGroups = channelView.splitChannelGroups
|
|
1881
|
+
channelView.splitChannelGroups = function (...args) {
|
|
1882
|
+
splitGroupCalls += 1
|
|
1883
|
+
return originalSplitChannelGroups.apply(this, args)
|
|
1884
|
+
}
|
|
1885
|
+
confirmButton?.click()
|
|
1886
|
+
await wait(600)
|
|
1887
|
+
channelView.splitChannelGroups = originalSplitChannelGroups
|
|
1888
|
+
const confirmedGroups = groupsOfFirst()
|
|
1889
|
+
const movedGroup = confirmedGroups.find(group => String(group.id) === String(movedTargetId))
|
|
1890
|
+
check('通道与数据:确定分组只调用一次 splitChannelGroups,整份声明一次落地',
|
|
1891
|
+
Boolean(confirmButton) && splitGroupCalls === 1 && membersOfFirst() === mergedGroups[0].lineIds.length && movedGroup?.lineIds.some(id => String(id) === String(movedLineId)) && logText().includes('splitChannelGroups'),
|
|
1892
|
+
`调用 ${splitGroupCalls} 次 / ${confirmedGroups.length} 组 / ${membersOfFirst()} 回线 / ${movedLineId} → ${movedTargetId}`)
|
|
1893
|
+
const waypointGroupId = straightGroup?.id ?? groupsOfFirst()[0]?.id
|
|
1589
1894
|
const backendModeSelect = channelDemo.querySelector('[data-demo="channel-backend-mode"]')
|
|
1590
1895
|
const backendRetryButton = channelDemo.querySelector('[data-demo="channel-backend-retry"]')
|
|
1591
1896
|
backendModeSelect.value = 'network'
|
|
@@ -1640,11 +1945,28 @@
|
|
|
1640
1945
|
channelCanvas.dispatchEvent(new PointerEvent('pointerdown', { ...base, button: 0, buttons: 1 }))
|
|
1641
1946
|
channelCanvas.dispatchEvent(new PointerEvent('pointerup', { ...base, button: 0, buttons: 0 }))
|
|
1642
1947
|
}
|
|
1643
|
-
channelClick(menuScreen)
|
|
1644
|
-
await wait(240)
|
|
1645
|
-
const modeInfo = channelView.getSelectionData()
|
|
1646
|
-
check('通道与数据:channelSelectMode = channel 时单击一条回线选中整条通道', Boolean(modeSelect) && channelLineTotal > 0 && modeInfo.lineCount === channelLineTotal && modeInfo.channelCount === 1, '选中 ' + modeInfo.lineCount + ' / 通道共 ' + channelLineTotal + ' 回(模式 ' + channelView.getOptions().channelSelectMode + ')')
|
|
1647
|
-
//
|
|
1948
|
+
channelClick(menuScreen)
|
|
1949
|
+
await wait(240)
|
|
1950
|
+
const modeInfo = channelView.getSelectionData()
|
|
1951
|
+
check('通道与数据:channelSelectMode = channel 时单击一条回线选中整条通道', Boolean(modeSelect) && channelLineTotal > 0 && modeInfo.lineCount === channelLineTotal && modeInfo.channelCount === 1, '选中 ' + modeInfo.lineCount + ' / 通道共 ' + channelLineTotal + ' 回(模式 ' + channelView.getOptions().channelSelectMode + ')')
|
|
1952
|
+
// 分组拐点拖动模式:下拉更新 option,准备按钮拆两组并各加两个拐点
|
|
1953
|
+
const waypointDragModeSelect = channelDemo.querySelector('[data-demo="channel-waypoint-drag-mode"]')
|
|
1954
|
+
if (waypointDragModeSelect) {
|
|
1955
|
+
waypointDragModeSelect.value = 'vertex'
|
|
1956
|
+
waypointDragModeSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
1957
|
+
}
|
|
1958
|
+
await wait(160)
|
|
1959
|
+
const waypointDragModeApplied = channelView.getOptions().channelWaypointDragMode === 'vertex'
|
|
1960
|
+
channelDemo.querySelector('[data-demo="channel-waypoint-drag-prepare"]')?.click()
|
|
1961
|
+
const preparedWaypointGroups = await until(() => {
|
|
1962
|
+
const channelId = channelView.getChannels()[0]?.channelId
|
|
1963
|
+
const groups = channelView.getChannelGroups(channelId)
|
|
1964
|
+
return groups.length === 2 && groups.every(group => group.path.length === 2) ? groups : null
|
|
1965
|
+
}, 5000)
|
|
1966
|
+
check('通道与数据:分组拐点拖动可切 vertex,准备按钮拆两组且每组两个拐点',
|
|
1967
|
+
Boolean(waypointDragModeSelect) && Boolean(preparedWaypointGroups) && waypointDragModeApplied && channelField('channel-waypoint-drag-text').includes('已准备 2 组'),
|
|
1968
|
+
`${channelView.getOptions().channelWaypointDragMode} / ${(preparedWaypointGroups ?? []).map(group => group.path.length).join(' / ')} / ${channelField('channel-waypoint-drag-text')}`)
|
|
1969
|
+
// 删除:requestRemove 先出载荷(待确认期间数据不动),确认框由 demo 自己按载荷画
|
|
1648
1970
|
const linesBeforeRemove = channelView.getData().lines.length
|
|
1649
1971
|
const channelsBeforeRemove = channelView.getOptions().channels.length
|
|
1650
1972
|
;[...channelDemo.querySelectorAll('.dv-panel button')].find(node => node.textContent.includes('删除选中'))?.click()
|
|
@@ -1844,19 +2166,80 @@
|
|
|
1844
2166
|
else {
|
|
1845
2167
|
check('编辑与保存:画布与视图都起来了', false, '没等到 canvas 或 view')
|
|
1846
2168
|
}
|
|
1847
|
-
// 2.9 综合示例的「实例方法速查」面板:整份清单、搜索、点一条填表达式、求值、options 清单
|
|
1848
|
-
if (await clickTab('综合')) {
|
|
1849
|
-
const ready = await canvasReady()
|
|
1850
|
-
//
|
|
1851
|
-
|
|
2169
|
+
// 2.9 综合示例的「实例方法速查」面板:整份清单、搜索、点一条填表达式、求值、options 清单
|
|
2170
|
+
if (await clickTab('综合')) {
|
|
2171
|
+
const ready = await canvasReady()
|
|
2172
|
+
// 级联高亮与合并样式:综合示例的「交互」面板里现场开关、点画布看读数,并确认合并样式不是额外 API
|
|
2173
|
+
const cascadeDemo = document.querySelector('.demo-view')
|
|
2174
|
+
const cascadeSetup = cascadeDemo?.__vueParentComponent?.setupState
|
|
2175
|
+
const cascadeView = await until(() => cascadeSetup?.viewRef, 8000)
|
|
2176
|
+
const cascadeCanvas = cascadeDemo?.querySelector('canvas.gis-view__canvas') ?? cascadeDemo?.querySelector('canvas')
|
|
2177
|
+
if (ready && cascadeView && cascadeCanvas) {
|
|
2178
|
+
;[...document.querySelectorAll('.demo-tabs button')].find(node => node.textContent.includes('视图 · 交互'))?.click()
|
|
2179
|
+
await wait(80)
|
|
2180
|
+
;[...document.querySelectorAll('.demo-panel-buttons button')].find(node => node.textContent.includes('交互开关与快捷键'))?.click()
|
|
2181
|
+
await wait(100)
|
|
2182
|
+
const cascadeToggle = document.querySelector('[data-demo=cascade-highlight-enabled]')
|
|
2183
|
+
const cascadeScope = document.querySelector('[data-demo=cascade-highlight-scope]')
|
|
2184
|
+
const cascadeReadoutNode = document.querySelector('[data-demo=cascade-highlight-readout]')
|
|
2185
|
+
check('级联高亮 demo:交互面板有开关、范围下拉与读数,默认开启且范围为节点 + 线路',
|
|
2186
|
+
Boolean(cascadeToggle && cascadeScope && cascadeReadoutNode) && cascadeToggle.checked && cascadeScope.value === 'both' && cascadeReadoutNode.textContent.includes('级联高亮'),
|
|
2187
|
+
`${cascadeToggle?.checked} / ${cascadeScope?.value} / ${cascadeReadoutNode?.textContent.trim() ?? '没有读数'}`)
|
|
2188
|
+
const cascadeData = cascadeView.getData()
|
|
2189
|
+
const cascadeLine = cascadeData.lines.find(line => line.from !== undefined && line.to !== undefined)
|
|
2190
|
+
let cascadeTarget = cascadeData.points.find(point => String(point.id) === String(cascadeLine?.from))
|
|
2191
|
+
if (!cascadeTarget && Array.isArray(cascadeLine?.path) && cascadeLine.path.length >= 2) {
|
|
2192
|
+
const [x, y] = cascadeLine.path[0]
|
|
2193
|
+
cascadeTarget = cascadeData.points.find(point => Math.abs(Number(point.x) - Number(x)) < 1e-9 && Math.abs(Number(point.y) - Number(y)) < 1e-9)
|
|
2194
|
+
}
|
|
2195
|
+
const cascadePoint = cascadeTarget ? cascadeView.getPoint(cascadeTarget.id) : null
|
|
2196
|
+
if (cascadePoint?.screen) {
|
|
2197
|
+
const cascadeRect = cascadeCanvas.getBoundingClientRect()
|
|
2198
|
+
tap(cascadeCanvas, { clientX: cascadeRect.left + cascadePoint.screen[0], clientY: cascadeRect.top + cascadePoint.screen[1] })
|
|
2199
|
+
await wait(220)
|
|
2200
|
+
}
|
|
2201
|
+
const cascadeInfo = cascadeView.getCascadeHighlight()
|
|
2202
|
+
check('级联高亮 demo:点节点后画布进入级联态,面板读数给出节点 / 线路条数',
|
|
2203
|
+
cascadeInfo?.origin?.target === 'point' && cascadeInfo.pointIndexes.length >= 1 && cascadeInfo.lineIndexes.length >= 1 && cascadeReadoutNode.textContent.includes('不改选中集'),
|
|
2204
|
+
JSON.stringify({ origin: cascadeInfo?.origin?.item?.id, points: cascadeInfo?.pointIndexes, lines: cascadeInfo?.lineIndexes, readout: cascadeReadoutNode.textContent.trim() }))
|
|
2205
|
+
cascadeScope.value = 'points'
|
|
2206
|
+
cascadeScope.dispatchEvent(new Event('change', { bubbles: true }))
|
|
2207
|
+
await wait(140)
|
|
2208
|
+
const cascadePointsOnly = cascadeView.getCascadeHighlight()
|
|
2209
|
+
check('级联高亮 demo:范围切到 points 后只亮节点,面板与 getOptions 同步',
|
|
2210
|
+
cascadeView.getOptions().cascadeHighlight?.scope === 'points' && cascadePointsOnly?.scope === 'points' && cascadePointsOnly.lineIndexes.length === 0,
|
|
2211
|
+
JSON.stringify({ scope: cascadePointsOnly?.scope, lines: cascadePointsOnly?.lineIndexes, option: cascadeView.getOptions().cascadeHighlight?.scope }))
|
|
2212
|
+
cascadeToggle.click()
|
|
2213
|
+
await wait(140)
|
|
2214
|
+
check('级联高亮 demo:关掉开关后 getCascadeHighlight() 归零,读数写明已关闭',
|
|
2215
|
+
cascadeView.getOptions().cascadeHighlight === false && cascadeView.getCascadeHighlight() === null && cascadeReadoutNode.textContent.includes('关闭'),
|
|
2216
|
+
`${cascadeView.getOptions().cascadeHighlight} / ${cascadeView.getCascadeHighlight()} / ${cascadeReadoutNode.textContent.trim()}`)
|
|
2217
|
+
// 还原成默认开 + both,别把后面的综合示例断言带进一个关掉的状态
|
|
2218
|
+
cascadeScope.value = 'both'
|
|
2219
|
+
cascadeScope.dispatchEvent(new Event('change', { bubbles: true }))
|
|
2220
|
+
cascadeToggle.click()
|
|
2221
|
+
await wait(100)
|
|
2222
|
+
|
|
2223
|
+
;[...document.querySelectorAll('.demo-panel-buttons button')].find(node => node.textContent.includes('复制与合并'))?.click()
|
|
2224
|
+
await wait(100)
|
|
2225
|
+
const mergeHint = document.querySelector('[data-demo=merge-style-hint]')
|
|
2226
|
+
check('合并样式 demo:读数明确说明它是最终 size / symbol,并给出规则与改写入口',
|
|
2227
|
+
Boolean(mergeHint) && mergeHint.textContent.includes('合并样式') && mergeHint.textContent.includes('不是一项额外配置') && mergeHint.textContent.includes('尺寸取双方较大值') && mergeHint.textContent.includes('merge.createItem'),
|
|
2228
|
+
mergeHint?.textContent.trim() ?? '没有合并样式读数')
|
|
2229
|
+
}
|
|
2230
|
+
else {
|
|
2231
|
+
check('级联高亮 demo:画布与视图都起来了', false, ready ? '没有 setupState.viewRef' : '没有 canvas')
|
|
2232
|
+
}
|
|
2233
|
+
// 面板在弹层里(v-show):先切到「开关 · 历史」页签,再点出「实例方法速查」那一块
|
|
2234
|
+
;[...document.querySelectorAll('.demo-tabs button')].find(node => node.textContent.includes('开关'))?.click()
|
|
1852
2235
|
await wait(80)
|
|
1853
2236
|
;[...document.querySelectorAll('.demo-panel-buttons button')].find(node => node.textContent.includes('实例方法速查'))?.click()
|
|
1854
2237
|
await wait(150)
|
|
1855
2238
|
const apiItems = () => [...document.querySelectorAll('.demo-api-list:not(.demo-api-list--options) .demo-api-item')]
|
|
1856
2239
|
const optionItems = () => [...document.querySelectorAll('.demo-api-list--options .demo-api-item')]
|
|
1857
|
-
check('实例方法速查:整份成员清单按分组列全(16 组 /
|
|
1858
|
-
|
|
1859
|
-
`${document.querySelectorAll('.demo-api-group').length} 组 / ${apiItems().length} 个成员`)
|
|
2240
|
+
check('实例方法速查:整份成员清单按分组列全(16 组 / 253 个成员)',
|
|
2241
|
+
Boolean(ready) && document.querySelectorAll('.demo-api-group').length === 16 && apiItems().length === 253,
|
|
2242
|
+
`${document.querySelectorAll('.demo-api-group').length} 组 / ${apiItems().length} 个成员`)
|
|
1860
2243
|
const apiNames = apiItems().map(node => node.querySelector('b')?.textContent)
|
|
1861
2244
|
const roundNames = ['registerSymbol', 'unregisterSymbol', 'listSymbols']
|
|
1862
2245
|
check('实例方法速查:本轮新增的三个方法都在清单里(registerSymbol / unregisterSymbol / listSymbols)',
|
|
@@ -1870,7 +2253,7 @@ check('实例方法速查:整份成员清单按分组列全(16 组 / 241 个
|
|
|
1870
2253
|
check('实例方法速查:这一轮拆出来的方法也在清单里(addChannelGroupLines / joinChannelGroupLines / endAddChannel)',
|
|
1871
2254
|
roundNames1597.every(name => apiNames.includes(name)),
|
|
1872
2255
|
roundNames1597.filter(name => !apiNames.includes(name)).join(' / ') || '三个都在')
|
|
1873
|
-
check('实例方法速查:options 折叠清单列全
|
|
2256
|
+
check('实例方法速查:options 折叠清单列全 95 个键', optionItems().length === 95, `${optionItems().length} 个键`)
|
|
1874
2257
|
const apiSearch = document.querySelector('.demo-api-search input[type="search"]')
|
|
1875
2258
|
apiSearch.value = 'focusOn'
|
|
1876
2259
|
apiSearch.dispatchEvent(new Event('input', { bubbles: true }))
|
|
@@ -1955,7 +2338,7 @@ check('实例方法速查:options 折叠清单列全 90 个键', optionItems()
|
|
|
1955
2338
|
const openChannelPanel = async () => {
|
|
1956
2339
|
;[...document.querySelectorAll('.demo-panel-buttons button')].find(node => node.textContent.trim() === '通道与拐点')?.click()
|
|
1957
2340
|
await wait(140)
|
|
1958
|
-
return [...document.querySelectorAll('.demo-
|
|
2341
|
+
return [...document.querySelectorAll('.demo-panel-inline button')].find(node => node.textContent.trim() === '新建通道(点两个节点)') ?? null
|
|
1959
2342
|
}
|
|
1960
2343
|
let beginDraftButton = await openChannelPanel()
|
|
1961
2344
|
if (!beginDraftButton) {
|
|
@@ -2043,10 +2426,152 @@ check('实例方法速查:options 折叠清单列全 90 个键', optionItems()
|
|
|
2043
2426
|
check('新增通道:综合示例的实例拿得到(setupState.viewRef)', false, '没有 setupState.viewRef')
|
|
2044
2427
|
}
|
|
2045
2428
|
}
|
|
2046
|
-
else {
|
|
2047
|
-
check('实例方法速查:综合示例的页签在', false, '没找到「综合」页签')
|
|
2048
|
-
}
|
|
2049
|
-
// 2.
|
|
2429
|
+
else {
|
|
2430
|
+
check('实例方法速查:综合示例的页签在', false, '没找到「综合」页签')
|
|
2431
|
+
}
|
|
2432
|
+
// 2.9b 接口能力:级联三种触发、通道 / 分组 / 拐点选中、lineFollowsNode 与 focusOn
|
|
2433
|
+
if (await clickTab('接口能力')) {
|
|
2434
|
+
const ready = await canvasReady()
|
|
2435
|
+
const demo = await until(() => [...document.querySelectorAll('.dv-demo')].find(node => node.textContent.includes('级联高亮三种触发')), 8000)
|
|
2436
|
+
const setup = demo?.__vueParentComponent?.setupState
|
|
2437
|
+
const apiView = await until(() => setup?.view?.getSelectionData ? setup.view : null, 8000)
|
|
2438
|
+
if (ready && apiView) {
|
|
2439
|
+
await until(() => apiView.listPoints().length > 0, 5000)
|
|
2440
|
+
const triggerClick = document.querySelector('[data-demo=cascade-trigger-click]')
|
|
2441
|
+
const triggerMulti = document.querySelector('[data-demo=cascade-trigger-multi]')
|
|
2442
|
+
const triggerBox = document.querySelector('[data-demo=cascade-trigger-box]')
|
|
2443
|
+
check('接口能力 demo:三种级联触发都有控件,默认全部打开并写进 getOptions',
|
|
2444
|
+
Boolean(triggerClick && triggerMulti && triggerBox) && triggerClick.checked && triggerMulti.checked && triggerBox.checked &&
|
|
2445
|
+
apiView.getOptions().cascadeHighlight?.triggerOnClick === true &&
|
|
2446
|
+
apiView.getOptions().cascadeHighlight?.triggerOnMultiSelect === true &&
|
|
2447
|
+
apiView.getOptions().cascadeHighlight?.triggerOnBoxSelect === true,
|
|
2448
|
+
`${triggerClick?.checked} / ${triggerMulti?.checked} / ${triggerBox?.checked} · ${JSON.stringify(apiView.getOptions().cascadeHighlight)}`)
|
|
2449
|
+
triggerBox.click()
|
|
2450
|
+
await wait(100)
|
|
2451
|
+
check('接口能力 demo:triggerOnBoxSelect 可以单独关掉,不影响另外两路',
|
|
2452
|
+
apiView.getOptions().cascadeHighlight?.triggerOnBoxSelect === false &&
|
|
2453
|
+
apiView.getOptions().cascadeHighlight?.triggerOnClick === true &&
|
|
2454
|
+
apiView.getOptions().cascadeHighlight?.triggerOnMultiSelect === true,
|
|
2455
|
+
JSON.stringify(apiView.getOptions().cascadeHighlight))
|
|
2456
|
+
triggerBox.click()
|
|
2457
|
+
document.querySelector('[data-demo=select-channel]')?.click()
|
|
2458
|
+
await wait(100)
|
|
2459
|
+
const channelSelection = apiView.getSelectionData()
|
|
2460
|
+
check('接口能力 demo:selectFeatures 显式选中整条通道,getSelectionData 读回通道计数',
|
|
2461
|
+
channelSelection.count > 0 && channelSelection.channelCount > 0 && channelSelection.lineCount > 0,
|
|
2462
|
+
JSON.stringify({ count: channelSelection.count, channels: channelSelection.channelCount, lines: channelSelection.lineCount }))
|
|
2463
|
+
document.querySelector('[data-demo=select-group]')?.click()
|
|
2464
|
+
await wait(100)
|
|
2465
|
+
const groupSelection = apiView.getSelectionData()
|
|
2466
|
+
check('接口能力 demo:selectFeatures 显式选中分组,读回只落在这一组的回线上',
|
|
2467
|
+
groupSelection.count > 0 && groupSelection.channelCount > 0 && groupSelection.lineCount > 0,
|
|
2468
|
+
JSON.stringify({ count: groupSelection.count, channels: groupSelection.channelCount, lines: groupSelection.lineCount }))
|
|
2469
|
+
document.querySelector('[data-demo=select-waypoint]')?.click()
|
|
2470
|
+
await wait(120)
|
|
2471
|
+
const waypointSelection = apiView.getSelectionData()
|
|
2472
|
+
const waypointLine = apiView.listLines().find(line => String(line.item?.id) === 'line-0')
|
|
2473
|
+
check('接口能力 demo:selectWaypoint 补出中间点并同时选中所在线路',
|
|
2474
|
+
Boolean(waypointLine) && apiView.getWaypoints(waypointLine.index).length >= 1 && waypointSelection.lineCount >= 1,
|
|
2475
|
+
JSON.stringify({ waypoints: waypointLine ? apiView.getWaypoints(waypointLine.index).length : -1, lines: waypointSelection.lineCount }))
|
|
2476
|
+
const linesBeforeHide = apiView.listLines({ visibleOnly: true }).length
|
|
2477
|
+
const toggleEndpoint = document.querySelector('[data-demo=toggle-endpoint]')
|
|
2478
|
+
const lineFollows = document.querySelector('[data-demo=line-follows-node]')
|
|
2479
|
+
toggleEndpoint?.click()
|
|
2480
|
+
await wait(260)
|
|
2481
|
+
const linesWhileFollowing = apiView.listLines({ visibleOnly: true }).length
|
|
2482
|
+
lineFollows?.click()
|
|
2483
|
+
await wait(260)
|
|
2484
|
+
const linesWithoutFollowing = apiView.listLines({ visibleOnly: true }).length
|
|
2485
|
+
check('接口能力 demo:lineFollowsNode 开时隐藏端点会带走绑定线路,关掉后线路保留',
|
|
2486
|
+
linesWhileFollowing < linesBeforeHide && linesWithoutFollowing > linesWhileFollowing && apiView.getOptions().lineFollowsNode === false,
|
|
2487
|
+
`线路 ${linesBeforeHide} → 跟随 ${linesWhileFollowing} → 不跟随 ${linesWithoutFollowing}`)
|
|
2488
|
+
lineFollows?.click()
|
|
2489
|
+
toggleEndpoint?.click()
|
|
2490
|
+
await wait(260)
|
|
2491
|
+
const cameraBefore = apiView.getCamera()
|
|
2492
|
+
document.querySelector('[data-demo=focus-channel]')?.click()
|
|
2493
|
+
await wait(120)
|
|
2494
|
+
const cameraAfter = apiView.getCamera()
|
|
2495
|
+
check('接口能力 demo:focusOn({ type: channel }) 走统一包围盒适配并更新相机',
|
|
2496
|
+
cameraAfter.scale > 0 && (cameraAfter.scale !== cameraBefore.scale || cameraAfter.center[0] !== cameraBefore.center[0] || cameraAfter.center[1] !== cameraBefore.center[1]),
|
|
2497
|
+
`scale ${cameraBefore.scale} → ${cameraAfter.scale} · center ${JSON.stringify(cameraBefore.center)} → ${JSON.stringify(cameraAfter.center)}`)
|
|
2498
|
+
document.querySelector('[data-demo=style-apis]')?.click()
|
|
2499
|
+
await wait(80)
|
|
2500
|
+
const styledOptions = apiView.getOptions()
|
|
2501
|
+
const styleReadout = document.querySelector('[data-demo=advanced-readout]')?.textContent ?? ''
|
|
2502
|
+
check('接口能力 demo:updatePointStyle / updateLineStyle 字段级合并并给出读数',
|
|
2503
|
+
styledOptions.pointStyle?.color === '#fde68a' && styledOptions.lineStyle?.color === '#fbbf24' && styleReadout.includes('updatePointStyle / updateLineStyle'),
|
|
2504
|
+
JSON.stringify({ point: styledOptions.pointStyle, line: styledOptions.lineStyle }))
|
|
2505
|
+
document.querySelector('[data-demo=style-apis]')?.click()
|
|
2506
|
+
await wait(80)
|
|
2507
|
+
document.querySelector('[data-demo=point-decoration-apis]')?.click()
|
|
2508
|
+
document.querySelector('[data-demo=line-decoration-apis]')?.click()
|
|
2509
|
+
await wait(120)
|
|
2510
|
+
const decorationReadout = document.querySelector('[data-demo=advanced-readout]')?.textContent ?? ''
|
|
2511
|
+
check('接口能力 demo:节点 / 线路装饰的 add、update、remove 三个入口都能收尾',
|
|
2512
|
+
decorationReadout.includes('线路装饰 CRUD') && decorationReadout.includes('updateLineDecoration size=20') && apiView.getPointDecorations('node-0').length === 0 && apiView.getLineDecorations('line-0').length === 0,
|
|
2513
|
+
decorationReadout.slice(-160))
|
|
2514
|
+
document.querySelector('[data-demo=channel-line-apis]')?.click()
|
|
2515
|
+
await wait(120)
|
|
2516
|
+
const channelApiReadout = document.querySelector('[data-demo=advanced-readout]')?.textContent ?? ''
|
|
2517
|
+
check('接口能力 demo:通道线宽、getChannelBetween、线路对账与 removeLines 同屏跑通',
|
|
2518
|
+
channelApiReadout.includes('setChannelLineWidth=3.4→2.6') && channelApiReadout.includes('getChangedLines=1 条') && channelApiReadout.includes('removeLines=ok') && !apiView.getData().lines.some(line => String(line.id) === 'api-temp-line'),
|
|
2519
|
+
channelApiReadout.slice(-180))
|
|
2520
|
+
document.querySelector('[data-demo=geo-background-apis]')?.click()
|
|
2521
|
+
await wait(160)
|
|
2522
|
+
const geoReadout = document.querySelector('[data-demo=advanced-readout]')?.textContent ?? ''
|
|
2523
|
+
check('接口能力 demo:updateBackground 后能读 getBackgroundLabels / getGeoProjection / mapDataToGeoJson',
|
|
2524
|
+
Boolean(apiView.getGeoProjection()) && (apiView.getBackgroundLabels()?.length ?? 0) > 0 && geoReadout.includes('mapDataToGeoJson=') && geoReadout.includes('节点'),
|
|
2525
|
+
geoReadout.slice(-180))
|
|
2526
|
+
document.querySelector('[data-demo=geo-background-apis]')?.click()
|
|
2527
|
+
await wait(120)
|
|
2528
|
+
check('接口能力 demo:再次 updateBackground({}) 恢复透明底、投影读数回到 null', apiView.getGeoProjection() === null, String(apiView.getGeoProjection()))
|
|
2529
|
+
const pointsBeforeAdvanced = apiView.getData().points.length
|
|
2530
|
+
document.querySelector('[data-demo=history-apis]')?.click()
|
|
2531
|
+
await wait(120)
|
|
2532
|
+
const historyReadout = document.querySelector('[data-demo=advanced-readout]')?.textContent ?? ''
|
|
2533
|
+
check('接口能力 demo:undo / canRedo / redo、合并收尾、落点预览与 isLayoutReady 都有读数',
|
|
2534
|
+
historyReadout.includes('canRedo=true') && historyReadout.includes('isLayoutReady=') && historyReadout.includes('getDropPreview=point') && apiView.getDropPreview() === null,
|
|
2535
|
+
historyReadout.slice(-220))
|
|
2536
|
+
document.querySelector('[data-demo=copy-apis]')?.click()
|
|
2537
|
+
await wait(120)
|
|
2538
|
+
const copyReadout = document.querySelector('[data-demo=advanced-readout]')?.textContent ?? ''
|
|
2539
|
+
check('接口能力 demo:beginCopyPoint / updateCopyPoint / commitPointCopy 演示完会清理临时复制品',
|
|
2540
|
+
copyReadout.includes('updateCopyPoint=接口复制预览') && copyReadout.includes('commitPointCopy=ok') && copyReadout.includes('removePoints=ok') && apiView.getData().points.length === pointsBeforeAdvanced,
|
|
2541
|
+
copyReadout.slice(-180))
|
|
2542
|
+
document.querySelector('[data-demo=remove-apis]')?.click()
|
|
2543
|
+
await wait(120)
|
|
2544
|
+
const removeReadout = document.querySelector('[data-demo=advanced-readout]')?.textContent ?? ''
|
|
2545
|
+
check('接口能力 demo:requestRemove → getRemoveRequest → cancel,再走 removeSelected',
|
|
2546
|
+
removeReadout.includes('getRemoveRequest=pending') && removeReadout.includes('cancel=cleared') && removeReadout.includes('removeSelected=1 个节点') && !apiView.getData().points.some(point => String(point.id) === 'api-remove-node'),
|
|
2547
|
+
removeReadout.slice(-180))
|
|
2548
|
+
document.querySelector('[data-demo=once-api]')?.click()
|
|
2549
|
+
await wait(80)
|
|
2550
|
+
check('接口能力 demo:once 只触发一次', (document.querySelector('[data-demo=advanced-readout]')?.textContent ?? '').includes('只触发 1 次'), document.querySelector('[data-demo=advanced-readout]')?.textContent ?? '')
|
|
2551
|
+
document.querySelector('[data-demo=webgl-apis]')?.click()
|
|
2552
|
+
await wait(80)
|
|
2553
|
+
check('接口能力 demo:clearWebGLLayers 清空临时层', apiView.getWebGLLayers().length === 0 && (document.querySelector('[data-demo=advanced-readout]')?.textContent ?? '').includes('剩余 0 层'), document.querySelector('[data-demo=advanced-readout]')?.textContent ?? '')
|
|
2554
|
+
document.querySelector('[data-demo=registry-apis]')?.click()
|
|
2555
|
+
await wait(80)
|
|
2556
|
+
const registryReadout = document.querySelector('[data-demo=advanced-readout]')?.textContent ?? ''
|
|
2557
|
+
check('接口能力 demo:unregisterSymbol / unregisterOperator 注销临时注册项',
|
|
2558
|
+
registryReadout.includes('unregisterSymbol=true') && registryReadout.includes('unregisterOperator=true') && !apiView.listSymbols().includes('api-ring') && !apiView.listOperators().includes('api-even'),
|
|
2559
|
+
registryReadout.slice(-180))
|
|
2560
|
+
document.querySelector('[data-demo=initial-options]')?.click()
|
|
2561
|
+
await wait(60)
|
|
2562
|
+
const optionsReadout = document.querySelector('[data-demo=advanced-readout]')?.textContent ?? ''
|
|
2563
|
+
check('接口能力 demo:初始化 options 的 ariaLabel / labelRender / gridSize / fitOnInit 等能读回',
|
|
2564
|
+
optionsReadout.includes('ariaLabel=接口能力演示画布') && optionsReadout.includes('labelRender=dom') && optionsReadout.includes('gridSize=96') && optionsReadout.includes('fitOnInit=true') && optionsReadout.includes('progressiveInit=true'),
|
|
2565
|
+
optionsReadout.slice(-220))
|
|
2566
|
+
}
|
|
2567
|
+
else {
|
|
2568
|
+
check('接口能力 demo:画布与 view 都起来了', false, ready ? '没拿到 view' : '没等到 canvas')
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
else {
|
|
2572
|
+
check('接口能力 demo:页签在', false, '没找到「接口能力」页签')
|
|
2573
|
+
}
|
|
2574
|
+
// 2.10 假后端契约:头 / body 必须一致,同 opId 换 body 必须报幂等冲突
|
|
2050
2575
|
{
|
|
2051
2576
|
const { createFakeBackend } = await import('../demos/shared/fakeBackend.js')
|
|
2052
2577
|
const backend = createFakeBackend({ initialVersion: 12, latency: 0, prefix: 'contract' })
|