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
package/docs/steps/index.html
CHANGED
|
@@ -171,6 +171,119 @@
|
|
|
171
171
|
.log b {
|
|
172
172
|
color: #2f6fd0;
|
|
173
173
|
}
|
|
174
|
+
.learn-route {
|
|
175
|
+
display: grid;
|
|
176
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
177
|
+
gap: 8px;
|
|
178
|
+
margin-bottom: 10px;
|
|
179
|
+
}
|
|
180
|
+
.learn-route a {
|
|
181
|
+
position: relative;
|
|
182
|
+
display: flex;
|
|
183
|
+
flex-direction: column;
|
|
184
|
+
gap: 3px;
|
|
185
|
+
min-height: 76px;
|
|
186
|
+
padding: 10px 12px;
|
|
187
|
+
border: 1px solid #d7e2ee;
|
|
188
|
+
border-radius: 10px;
|
|
189
|
+
background: rgba(255, 255, 255, 0.78);
|
|
190
|
+
color: #27476b;
|
|
191
|
+
text-decoration: none;
|
|
192
|
+
transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
|
|
193
|
+
}
|
|
194
|
+
.learn-route a:hover {
|
|
195
|
+
border-color: #8fb4dd;
|
|
196
|
+
box-shadow: 0 7px 20px rgba(47, 111, 208, 0.1);
|
|
197
|
+
transform: translateY(-1px);
|
|
198
|
+
}
|
|
199
|
+
.learn-route span {
|
|
200
|
+
color: #2f6fd0;
|
|
201
|
+
font-size: 11px;
|
|
202
|
+
font-weight: 700;
|
|
203
|
+
letter-spacing: 0.04em;
|
|
204
|
+
}
|
|
205
|
+
.learn-route b {
|
|
206
|
+
font-size: 13px;
|
|
207
|
+
}
|
|
208
|
+
.learn-route small {
|
|
209
|
+
color: #6b8099;
|
|
210
|
+
font-size: 11px;
|
|
211
|
+
line-height: 1.45;
|
|
212
|
+
}
|
|
213
|
+
.step-brief {
|
|
214
|
+
display: grid;
|
|
215
|
+
grid-template-columns: 1.1fr 1.1fr 0.8fr;
|
|
216
|
+
gap: 8px;
|
|
217
|
+
margin-bottom: 10px;
|
|
218
|
+
}
|
|
219
|
+
.step-brief__card {
|
|
220
|
+
min-width: 0;
|
|
221
|
+
padding: 10px 12px;
|
|
222
|
+
border: 1px solid #d7e2ee;
|
|
223
|
+
border-radius: 9px;
|
|
224
|
+
background: rgba(255, 255, 255, 0.8);
|
|
225
|
+
}
|
|
226
|
+
.step-brief__card > span {
|
|
227
|
+
display: block;
|
|
228
|
+
margin-bottom: 3px;
|
|
229
|
+
color: #2f6fd0;
|
|
230
|
+
font-size: 11px;
|
|
231
|
+
font-weight: 700;
|
|
232
|
+
}
|
|
233
|
+
.step-brief h2 {
|
|
234
|
+
margin: 0 0 3px;
|
|
235
|
+
color: #1f3550;
|
|
236
|
+
font-size: 14px;
|
|
237
|
+
}
|
|
238
|
+
.step-brief p {
|
|
239
|
+
margin: 0;
|
|
240
|
+
color: #526b8d;
|
|
241
|
+
font-size: 12px;
|
|
242
|
+
line-height: 1.55;
|
|
243
|
+
}
|
|
244
|
+
.step-brief code,
|
|
245
|
+
.step-brief small {
|
|
246
|
+
display: block;
|
|
247
|
+
overflow-wrap: anywhere;
|
|
248
|
+
}
|
|
249
|
+
.step-brief code {
|
|
250
|
+
margin-top: 3px;
|
|
251
|
+
}
|
|
252
|
+
.step-brief small {
|
|
253
|
+
margin-top: 5px;
|
|
254
|
+
color: #8191a8;
|
|
255
|
+
font-size: 11px;
|
|
256
|
+
}
|
|
257
|
+
.afterword {
|
|
258
|
+
margin-top: 10px;
|
|
259
|
+
border: 1px solid #d7e2ee;
|
|
260
|
+
border-radius: 10px;
|
|
261
|
+
background: rgba(255, 255, 255, 0.76);
|
|
262
|
+
}
|
|
263
|
+
.afterword summary {
|
|
264
|
+
padding: 10px 12px;
|
|
265
|
+
color: #27476b;
|
|
266
|
+
font-weight: 700;
|
|
267
|
+
cursor: pointer;
|
|
268
|
+
}
|
|
269
|
+
.afterword__body {
|
|
270
|
+
padding: 0 12px 12px;
|
|
271
|
+
color: #526b8d;
|
|
272
|
+
font-size: 12px;
|
|
273
|
+
line-height: 1.65;
|
|
274
|
+
}
|
|
275
|
+
.afterword__body h2 {
|
|
276
|
+
margin: 8px 0 3px;
|
|
277
|
+
color: #1f3550;
|
|
278
|
+
font-size: 13px;
|
|
279
|
+
}
|
|
280
|
+
.afterword__body p,
|
|
281
|
+
.afterword__body ol {
|
|
282
|
+
margin: 3px 0 0;
|
|
283
|
+
}
|
|
284
|
+
.afterword__body ol {
|
|
285
|
+
padding-left: 20px;
|
|
286
|
+
}
|
|
174
287
|
@media (max-width: 760px) {
|
|
175
288
|
body {
|
|
176
289
|
padding: 12px;
|
|
@@ -186,6 +299,10 @@
|
|
|
186
299
|
.readout {
|
|
187
300
|
grid-template-columns: 1fr;
|
|
188
301
|
}
|
|
302
|
+
.learn-route,
|
|
303
|
+
.step-brief {
|
|
304
|
+
grid-template-columns: 1fr;
|
|
305
|
+
}
|
|
189
306
|
}
|
|
190
307
|
</style>
|
|
191
308
|
</head>
|
|
@@ -208,9 +325,49 @@
|
|
|
208
325
|
<a href="../api/index.html">API 速查</a>
|
|
209
326
|
<a href="../playground/index.html">调试页</a>
|
|
210
327
|
<a href="./selftest.html">自测</a>
|
|
328
|
+
<a href="../index.html#从0到完整的实现.md">从零讲义</a>
|
|
329
|
+
<a href="../index.html#steps/README.md">步骤说明</a>
|
|
211
330
|
</nav>
|
|
212
331
|
</header>
|
|
332
|
+
<section class="learn-route" id="learn-route" aria-label="从零学习路线">
|
|
333
|
+
<a href="#1" data-route-step="0">
|
|
334
|
+
<span>第 1 段 · 第 1 ~ 3 步</span>
|
|
335
|
+
<b>先让画面成立</b>
|
|
336
|
+
<small>画布、点、线:先把 WebGL 主干跑通。</small>
|
|
337
|
+
</a>
|
|
338
|
+
<a href="#4" data-route-step="3">
|
|
339
|
+
<span>第 2 段 · 第 4 ~ 5 步</span>
|
|
340
|
+
<b>再看懂视角与命中</b>
|
|
341
|
+
<small>相机矩阵、适应视图、拾取与指针回调。</small>
|
|
342
|
+
</a>
|
|
343
|
+
<a href="#6" data-route-step="5">
|
|
344
|
+
<span>第 3 段 · 第 6 ~ 9 步</span>
|
|
345
|
+
<b>长成业务组件</b>
|
|
346
|
+
<small>样式规则、通道、覆盖层与鹰眼图。</small>
|
|
347
|
+
</a>
|
|
348
|
+
<a href="#10" data-route-step="9">
|
|
349
|
+
<span>第 4 段 · 第 10 ~ 13 步</span>
|
|
350
|
+
<b>接入真组件并上线</b>
|
|
351
|
+
<small>初始视图、动作判读、画质、批量与历史。</small>
|
|
352
|
+
</a>
|
|
353
|
+
</section>
|
|
213
354
|
<div class="steps" id="steps"></div>
|
|
355
|
+
<section class="step-brief" id="step-brief" aria-live="polite">
|
|
356
|
+
<div class="step-brief__card">
|
|
357
|
+
<span id="brief-stage">当前阶段</span>
|
|
358
|
+
<h2 id="brief-title">正在加载</h2>
|
|
359
|
+
<p id="brief-goal">每一步只解决一个问题;先读产出,再对照页面效果。</p>
|
|
360
|
+
</div>
|
|
361
|
+
<div class="step-brief__card">
|
|
362
|
+
<span>本步完成标准</span>
|
|
363
|
+
<p id="brief-accept">能说清输入、输出,并在页面上亲手验证。</p>
|
|
364
|
+
</div>
|
|
365
|
+
<div class="step-brief__card">
|
|
366
|
+
<span>本次调用的接口</span>
|
|
367
|
+
<code id="brief-api">—</code>
|
|
368
|
+
<small id="brief-source">对照源码:—</small>
|
|
369
|
+
</div>
|
|
370
|
+
</section>
|
|
214
371
|
<div id="map"></div>
|
|
215
372
|
<div class="bar">
|
|
216
373
|
<button type="button" id="btn-fit">适应视图</button>
|
|
@@ -248,6 +405,23 @@
|
|
|
248
405
|
<span id="rd-features">功能:—</span>
|
|
249
406
|
</div>
|
|
250
407
|
<div class="log" id="log"></div>
|
|
408
|
+
<details class="afterword" id="afterword">
|
|
409
|
+
<summary>通关之后:搭建思路、验收与优化顺序</summary>
|
|
410
|
+
<div class="afterword__body">
|
|
411
|
+
<h2>1. 先定契约,再写渲染</h2>
|
|
412
|
+
<p>每步开工前先回答四件事:输入是什么(世界坐标 / 业务字段)、输出是什么(像素 / DOM / 回调)、谁是唯一真相(数据副本还是 GPU 缓冲)、失败时怎么看得出来。把这四件事写清,后面加通道、标签和筛选才不会推倒重来。</p>
|
|
413
|
+
<h2>2. 沿一条数据流排查</h2>
|
|
414
|
+
<p>业务数据 → 解析成渲染实例 → 相机矩阵 → GPU / DOM 覆盖层 → 指针事件回查。画面不对时从最近一次「应该变但没变」的位置向前查,而不是同时改相机、着色器和事件。</p>
|
|
415
|
+
<h2>3. 优化只按瓶颈排队</h2>
|
|
416
|
+
<ol>
|
|
417
|
+
<li>先用 <code>getStats()</code>、性能和帧耗时确认瓶颈,再动代码。</li>
|
|
418
|
+
<li>减少每帧重排:批量更新、增量补丁、按需渲染优先于改写着色器。</li>
|
|
419
|
+
<li>再减可见工作:视口裁剪、标签上限与避让、聚合、线路批次。</li>
|
|
420
|
+
<li>最后才考虑 Worker、画质档或更换渲染后端;先把正确性和可读性守住。</li>
|
|
421
|
+
</ol>
|
|
422
|
+
<p>完整讲义见 <a href="../index.html#从0到完整的实现.md">《从 0 到完整的实现》</a>,每一步的验收表与源码对照见 <a href="../index.html#steps/README.md">docs/steps/README.md</a>。</p>
|
|
423
|
+
</div>
|
|
424
|
+
</details>
|
|
251
425
|
</main>
|
|
252
426
|
<script type="module">
|
|
253
427
|
import { buildParkData } from './data.js'
|
|
@@ -265,10 +439,51 @@
|
|
|
265
439
|
{ file: 'step-09-minimap.js', factory: 'createMinimapView', load: () => import('./step-09-minimap.js'), title: '鹰眼图', api: '小图 + 视口框', note: '小画布画全景、DOM 画视口框、点与拖都能联动主图(文档第 8 章)' },
|
|
266
440
|
{ file: 'step-10-full.js', factory: 'createFullView', load: () => import('./step-10-full.js'), title: '换成真组件', api: 'createGisView', note: '同一份数据换成 src/gis-view 的完整实现,API 与前面几步一一对应(文档 12.11)' },
|
|
267
441
|
{ file: 'step-11-background.js', factory: 'createBackgroundView', load: () => import('./step-11-background.js'), title: '背景层与初始视图', api: 'background / initialView / applyInitialView', note: '底图走 background.geoJson(几何与 demo 共用同一份生成器),最后一档换成 SVG 路径图片(background.image 只给 d 字符串);GeoJSON 还演示 aspectScale、逐区域配色(regions / itemStyle)与区域文字(label)。首屏落位走 initialView:默认聚焦数据中心放大 1.8 倍,不再一上来铺满整屏(GisView.md:背景层 / 初始视图)' },
|
|
268
|
-
{ file: 'step-12-events.js', factory: 'createActionView', load: () => import('./step-12-events.js'), title: '事件的动作判读与只读查询', api: 'action / pickAt / getFeaturesInRect / hoverAt', note: '每一条指针事件都带 action:type 是操作(按下时是意图、松开时是刚做完的那件事、点击与右键是真正发生的那一件)、target 是点到了什么(通道回线单独给 channel
|
|
442
|
+
{ file: 'step-12-events.js', factory: 'createActionView', load: () => import('./step-12-events.js'), title: '事件的动作判读与只读查询', api: 'action / pickAt / getFeaturesInRect / hoverAt', note: '每一条指针事件都带 action:type 是操作(按下时是意图、松开时是刚做完的那件事、点击与右键是真正发生的那一件)、target 是点到了什么(通道回线单独给 channel)、命中的数据直接读载荷顶层(节点 / 线路 item、通道 channel、标签 label),moved / double / press / duration / longPress 是同一份操作判读;按键、修饰键与 clientX / clientY 读 originalEvent(中键就是 originalEvent.button === 1,长按按 action.longPress 认),选中集读数用 getSelectionInfo();两个只读查询 pickAt(鼠标坐标问「这儿是什么」)与 getFeaturesInRect(画布矩形里有什么,与框选同一套判定),再加程序化悬停 hoverAt / clearHover / getHover(把鼠标停到「程序化悬停」按钮上试试)。点一下、框一下、右键一下,读数栏的「判读」都会跟着换(GisView.md:action:一次操作的判读 / 实例方法)' },
|
|
269
443
|
{ file: 'step-13-engineering.js', factory: 'createEngineeringView', load: () => import('./step-13-engineering.js'), title: '工程化四件套', api: 'quality / batch / history / features', note: '上线前要配的四项:画质四档(setQuality / getQuality,换档只影响绘制,不重排、不动数据)、batch(一批改动作一条 onDataChange 与一步历史,一千次 updatePoint 也只算一步)、history(undo / redo / getHistoryState,Ctrl+Z / Ctrl+Shift+Z 也接上;缺省是关的,这一步显式打开)、features(edit 全开 / view 只读 / static 静态图三个预设,一次切好交互口径)。工具条的「画质档」「批量移动」「撤销 / 重做」「功能预设」逐个点点,读数栏最后三行跟着换(GisView.md:画质 / 批量更新 / 历史 / 功能开关)' }
|
|
270
444
|
]
|
|
271
445
|
|
|
446
|
+
/** 每一步只回答三个问题:产出什么、怎么验收、回到库里看哪一段。 */
|
|
447
|
+
const STEP_GUIDES = [
|
|
448
|
+
{ goal: '让容器里出现一块跟随尺寸变化的 canvas,并建立“改数据 -> 请求一帧”的按需渲染循环。', accept: '缩放窗口后画布清晰、不拉伸;静止时没有持续 rAF 空转。', source: 'src/gis-view/engine.js · 上下文 / resize / requestRender' },
|
|
449
|
+
{ goal: '把点数组编成实例数据,用一次 draw call 画出所有节点。', accept: '改节点数后画面与缓冲同步更新;圆与方在片元里用距离场抗锯齿。', source: 'src/gis-view/point.js + shaders.js' },
|
|
450
|
+
{ goal: '把每段线展开成四边形状,在顶点着色器里按法线撑开线宽。', accept: '线宽不随缩放变糊,拐点处不断裂,颜色与宽度能逐条覆盖。', source: 'src/gis-view/line.js + shaders.js' },
|
|
451
|
+
{ goal: '把世界坐标经相机矩阵投到屏幕,并实现 fitView / 滚轮缩放 / 拖动平移。', accept: '滚轮以指针位置为锚点、拖动位移跟手,fitView 能完整包住数据范围。', source: 'src/gis-view/engine.js · 相机段' },
|
|
452
|
+
{ goal: '用屏幕距离做拾取,把 hover / click / dblClick / pointer / contextMenu 接成一套回调。', accept: '每个回调都带 type / index / item;点空白不误命中,拖动不会被当成点击。', source: 'src/gis-view/utils.js + engine.js' },
|
|
453
|
+
{ goal: '把业务字段翻译成样式,再按“点自身 > 状态 > 规则 > 全局”的优先级落地。', accept: '现场改规则只重算样式、不写业务数据;选中与悬停状态仍然优先。', source: 'src/gis-view/rules.js + condition.js' },
|
|
454
|
+
{ goal: '先算通道公共骨架,再按车道生成平行回线,并把两次点击变成可取消的草稿。', accept: '点两个节点能落成一条通道;回线间距稳定;取消 / 落地 / 连续新增都有回调。', source: 'src/gis-view/channel.js + engine.js' },
|
|
455
|
+
{ goal: '用 DOM 覆盖层承载标签和提示框,按视口、缩放级别与避让结果决定显示谁。', accept: '平移缩放后标签仍贴住要素;超过上限会隐藏或避让,悬停提示不阻塞画布。', source: 'src/gis-view/overlay.js' },
|
|
456
|
+
{ goal: '用两层小画布画全景与视口框,并把点击、拖动映射回主图相机。', accept: '主图缩放时视口框准确变化;点小图或拖框都能定位主图;销毁后不残留帧任务。', source: 'src/gis-view/minimap.js' },
|
|
457
|
+
{ goal: '把前面手写的实现替换成真实的 createGisView,对照同一份数据与同一套 API。', accept: '同样数据得到同样的点线关系;销毁后定时器、监听与 WebGL 资源都有归口。', source: 'src/gis-view/create.js' },
|
|
458
|
+
{ goal: '接入背景层与首屏落位,区分 initialView、顶层 center / scale 与 applyInitialView。', accept: 'GeoJSON / 图片 / 无底图都能切换;首屏中心与比例符合配置;显式 initialView 整份优先。', source: 'src/gis-view/background.js + geojson.js + engine.js' },
|
|
459
|
+
{ goal: '把原始指针事件压成稳定的 action 判读,并提供 pickAt / getFeaturesInRect / hoverAt 三组只读查询。', accept: '点击、框选、拖点与右键都能从 action.type / target 分清;查询结果与事件载荷同口径。', source: 'src/gis-view/engine.js + create.js' },
|
|
460
|
+
{ goal: '补齐上线前的工程开关:画质档、批量更新、历史与功能预设。', accept: '一批更新只发一次数据变更、只记一步历史;撤销 / 重做正确;只读与静态预设真的关掉交互。', source: 'src/gis-view/quality.js + history.js + features.js' }
|
|
461
|
+
]
|
|
462
|
+
const STEP_STAGES = [
|
|
463
|
+
'第 1 段 · 画面成立',
|
|
464
|
+
'第 1 段 · 画面成立',
|
|
465
|
+
'第 1 段 · 画面成立',
|
|
466
|
+
'第 2 段 · 视角与命中',
|
|
467
|
+
'第 2 段 · 视角与命中',
|
|
468
|
+
'第 3 段 · 业务组件',
|
|
469
|
+
'第 3 段 · 业务组件',
|
|
470
|
+
'第 3 段 · 业务组件',
|
|
471
|
+
'第 3 段 · 业务组件',
|
|
472
|
+
'第 4 段 · 接入与上线',
|
|
473
|
+
'第 4 段 · 接入与上线',
|
|
474
|
+
'第 4 段 · 接入与上线',
|
|
475
|
+
'第 4 段 · 接入与上线'
|
|
476
|
+
]
|
|
477
|
+
const updateStepBrief = (index, step) => {
|
|
478
|
+
const guide = STEP_GUIDES[index] ?? { goal: '', accept: '', source: '' }
|
|
479
|
+
document.querySelector('#brief-stage').textContent = STEP_STAGES[index] ?? ''
|
|
480
|
+
document.querySelector('#brief-title').textContent = `${index + 1}. ${step.title}`
|
|
481
|
+
document.querySelector('#brief-goal').textContent = guide.goal
|
|
482
|
+
document.querySelector('#brief-accept').textContent = guide.accept
|
|
483
|
+
document.querySelector('#brief-api').textContent = step.api
|
|
484
|
+
document.querySelector('#brief-source').textContent = `对照源码:${guide.source}`
|
|
485
|
+
}
|
|
486
|
+
|
|
272
487
|
const DATA = buildParkData(20240915)
|
|
273
488
|
/** 业务字段 -> 样式:第 6 步之后的数据都先过这两张表 */
|
|
274
489
|
const POINT_RULES = [
|
|
@@ -359,12 +574,12 @@
|
|
|
359
574
|
},
|
|
360
575
|
onDblClick: payload => log('onDblClick', describeElement(payload)),
|
|
361
576
|
onSelect: payload => log('onSelect', payload ? `${payload.items.length} 个要素` : '清空选中'),
|
|
362
|
-
onPointerDown:
|
|
363
|
-
setReadout('pointer', `按下:${describeElement(payload)}(button ${
|
|
577
|
+
onPointerDown: payload => {
|
|
578
|
+
setReadout('pointer', `按下:${describeElement(payload)}(button ${payload.originalEvent?.button})`)
|
|
364
579
|
},
|
|
365
|
-
onPointerUp:
|
|
366
|
-
onContextMenu:
|
|
367
|
-
|
|
580
|
+
onPointerUp: payload => log('onPointerUp', `${describeElement(payload)}(button ${payload.originalEvent?.button})`),
|
|
581
|
+
onContextMenu: payload => {
|
|
582
|
+
payload.originalEvent?.preventDefault()
|
|
368
583
|
log('onContextMenu', describeElement(payload))
|
|
369
584
|
},
|
|
370
585
|
// 需求 1 的核心:草稿里每点一次都回调,第一次拿起点、第二次拿终点
|
|
@@ -443,6 +658,7 @@
|
|
|
443
658
|
log('mount', `第 ${index + 1} 步 ${step.file}`)
|
|
444
659
|
syncCamera()
|
|
445
660
|
updateButtons()
|
|
661
|
+
updateStepBrief(index, step)
|
|
446
662
|
for (const button of stepsNode.children) {
|
|
447
663
|
button.classList.toggle('on', Number(button.dataset.index) === index)
|
|
448
664
|
}
|
|
@@ -145,12 +145,13 @@
|
|
|
145
145
|
const upP1 = last('up')
|
|
146
146
|
const clickP1 = last('click')
|
|
147
147
|
check('左键按下:action.type 是这一次按下的意图(节点上是 dragPoint)、target 是 point', downP1?.action?.type === 'dragPoint' && downP1?.action?.target === 'point', JSON.stringify(downP1?.action))
|
|
148
|
-
check('
|
|
149
|
-
check('
|
|
148
|
+
check('左键按下:命中的节点直接读 payload.item(与 getData() 同一份引用)', downP1?.item === view.getData().points[0] && downP1?.item?.id === 'p1' && downP1?.action?.data === undefined, String(downP1?.item?.id))
|
|
149
|
+
check('左键按下:按键 / 修饰键读 originalEvent,不再复制到 action', downP1?.originalEvent?.button === 0 && downP1?.originalEvent?.ctrlKey === false && downP1?.originalEvent?.shiftKey === false && downP1?.action?.button === undefined && downP1?.action?.modifiers === undefined, JSON.stringify({ button: downP1?.originalEvent?.button, ctrl: downP1?.originalEvent?.ctrlKey }))
|
|
150
150
|
check('左键松开:没拖动过 → action.type 是 click、moved 为 false', upP1?.action?.type === 'click' && upP1?.action?.moved === false && upP1?.action?.target === 'point', JSON.stringify(upP1?.action))
|
|
151
151
|
check('onClick:action.type 是 select(点中要素)、double 为 false', clickP1?.action?.type === 'select' && clickP1?.action?.double === false && clickP1?.action?.moved === false, JSON.stringify(clickP1?.action))
|
|
152
|
-
check('onClick
|
|
153
|
-
|
|
152
|
+
check('onClick:按键 / 修饰键同样从 originalEvent 读', clickP1?.originalEvent?.button === 0 && clickP1?.originalEvent?.ctrlKey === false && clickP1?.action?.button === undefined, String(clickP1?.originalEvent?.button))
|
|
153
|
+
const clickSelection = view.getSelectionInfo()
|
|
154
|
+
check('onClick:选中集读数改调 getSelectionInfo(),载荷不再重复挂 selection', clickP1?.selection === undefined && clickSelection.count === 1 && clickSelection.points === 1 && clickSelection.ids?.[0] === 'p1', JSON.stringify(clickSelection))
|
|
154
155
|
|
|
155
156
|
/* ---------- Ctrl 点击取反:不经过 onClick,要看 selectChange 的 action ---------- */
|
|
156
157
|
const togglesBefore = countOf('selectChange')
|
|
@@ -161,7 +162,7 @@
|
|
|
161
162
|
const togglePayload = last('selectChange')
|
|
162
163
|
check('Ctrl 点击取反:selectChange.action.type 是 toggleSelect(取反取没了也是它)', countOf('selectChange') === togglesBefore + 1 && togglePayload?.action?.type === 'toggleSelect', JSON.stringify(togglePayload?.action))
|
|
163
164
|
check('取反之后选中集空了:items / ids / count 都是空的一份、target 是 empty', togglePayload?.count === 0 && togglePayload?.ids?.length === 0 && togglePayload?.items?.length === 0 && togglePayload?.action?.target === 'empty', JSON.stringify({ ids: togglePayload?.ids, count: togglePayload?.count }))
|
|
164
|
-
check('
|
|
165
|
+
check('取反载荷:修饰键读 originalEvent、这一批数据直接读 items', togglePayload?.originalEvent?.ctrlKey === true && togglePayload?.items?.length === 0 && togglePayload?.action?.modifiers === undefined && togglePayload?.action?.data === undefined, JSON.stringify({ ctrl: togglePayload?.originalEvent?.ctrlKey, items: togglePayload?.items?.length }))
|
|
165
166
|
check('取反这一路不发 onClick:要按操作分支的界面认 selectChange.action', countOf('click') === 1, `${countOf('click')} 条 click`)
|
|
166
167
|
|
|
167
168
|
/* ---------- 程序化选中与点空白 ---------- */
|
|
@@ -204,8 +205,8 @@
|
|
|
204
205
|
fire(canvas, 'contextmenu', atP3, { button: 2, buttons: 2 })
|
|
205
206
|
await settle(40)
|
|
206
207
|
const menuP3 = last('contextmenu')
|
|
207
|
-
check('右键节点:action.type 是 contextMenu、target 是 point、button
|
|
208
|
-
check('
|
|
208
|
+
check('右键节点:action.type 是 contextMenu、target 是 point、button 读 originalEvent', menuP3?.action?.type === 'contextMenu' && menuP3?.action?.target === 'point' && menuP3?.originalEvent?.button === 2 && menuP3?.action?.button === undefined, JSON.stringify(menuP3?.action))
|
|
209
|
+
check('右键节点:节点读 item,selected 是命中时的快照,总读数走 getSelectionInfo()', menuP3?.item?.id === 'p3' && menuP3?.hit === true && typeof menuP3?.selected === 'boolean' && menuP3?.selection === undefined && typeof view.getSelectionInfo().count === 'number', JSON.stringify({ id: menuP3?.item?.id, selected: menuP3?.selected }))
|
|
209
210
|
const lineInfo = view.listLines({ inView: true }).find(entry => entry.index === 0)
|
|
210
211
|
const linePath = lineInfo?.path ?? []
|
|
211
212
|
const channelAt = linePath.length >= 2
|
|
@@ -218,8 +219,8 @@
|
|
|
218
219
|
await settle(40)
|
|
219
220
|
const menuChannel = last('contextmenu')
|
|
220
221
|
check('通道回线右键:同一份 onContextMenu 载荷的 action.type / target 是 contextMenu / channel', Boolean(channelAt) && menuChannel?.action?.type === 'contextMenu' && menuChannel?.action?.target === 'channel' && menuChannel?.channelContext?.channelId === 'c1', JSON.stringify({ action: menuChannel?.action, channel: menuChannel?.channelContext?.channelId }))
|
|
221
|
-
check('
|
|
222
|
-
check('
|
|
222
|
+
check('通道回线右键:通道定义读 payload.channel,不再打包 action.data', menuChannel?.channel?.id === 'c1' && Array.isArray(menuChannel?.channel?.lineIds) && menuChannel?.action?.data === undefined, String(menuChannel?.channel?.id))
|
|
223
|
+
check('通道回线右键:右键按键与通用右键同一口径', menuChannel?.originalEvent?.button === 2 && menuChannel?.action?.button === undefined, String(menuChannel?.originalEvent?.button))
|
|
223
224
|
|
|
224
225
|
/* ---------- 拐点:Ctrl 点击加点、点选、拖动 ---------- */
|
|
225
226
|
pointerId += 1
|
|
@@ -229,9 +230,9 @@
|
|
|
229
230
|
await settle(50)
|
|
230
231
|
const wpAdd = last('waypoint')
|
|
231
232
|
check('Ctrl 点击线路:拐点事件的 action.type 是 addWaypoint、target 是 waypoint', wpAdd?.phase === 'add' && wpAdd?.action?.type === 'addWaypoint' && wpAdd?.action?.target === 'waypoint', JSON.stringify(wpAdd?.action))
|
|
232
|
-
check('
|
|
233
|
-
check('
|
|
234
|
-
const wpCoordinate = wpAdd?.
|
|
233
|
+
check('拐点:哪条线、第几个顶点、坐标与改完的线直接读载荷顶层', wpAdd?.lineIndex === 1 && wpAdd?.pathIndex === 1 && Array.isArray(wpAdd?.coordinate) && wpAdd?.line?.id === 'l2' && wpAdd?.action?.data === undefined, JSON.stringify({ lineIndex: wpAdd?.lineIndex, pathIndex: wpAdd?.pathIndex, id: wpAdd?.line?.id }))
|
|
234
|
+
check('拐点事件:按键 / 修饰键读 originalEvent(左键 + Ctrl)', wpAdd?.originalEvent?.button === 0 && wpAdd?.originalEvent?.ctrlKey === true && wpAdd?.action?.button === undefined && wpAdd?.action?.modifiers === undefined, JSON.stringify({ button: wpAdd?.originalEvent?.button, ctrl: wpAdd?.originalEvent?.ctrlKey }))
|
|
235
|
+
const wpCoordinate = wpAdd?.coordinate ?? [180, 90]
|
|
235
236
|
pointerId += 1
|
|
236
237
|
const wpPoint = pointOf(wpCoordinate[0], wpCoordinate[1])
|
|
237
238
|
fire(canvas, 'pointerdown', wpPoint)
|
|
@@ -253,7 +254,7 @@
|
|
|
253
254
|
const wpEnd = lastOfPhase('waypoint', 'end')
|
|
254
255
|
check('拖动拐点:start / move / end 三条都带 action.type 是 dragWaypoint', wpStart?.action?.type === 'dragWaypoint' && wpMove?.action?.type === 'dragWaypoint' && wpEnd?.action?.type === 'dragWaypoint', JSON.stringify([wpStart?.action?.type, wpMove?.action?.type, wpEnd?.action?.type]))
|
|
255
256
|
check('拖动拐点:moved 依次是 false / true / true(起手不算拖过)', wpStart?.action?.moved === false && wpMove?.action?.moved === true && wpEnd?.action?.moved === true, JSON.stringify([wpStart?.action?.moved, wpMove?.action?.moved, wpEnd?.action?.moved]))
|
|
256
|
-
check('拖动拐点:end
|
|
257
|
+
check('拖动拐点:end 顶层是挪完的坐标与新的线路', wpEnd?.coordinate?.[1] > wpCoordinate[1] && wpEnd?.line?.id === 'l2', JSON.stringify(wpEnd?.coordinate))
|
|
257
258
|
|
|
258
259
|
/* ---------- 标签:点选 / 拖动 / 右键,同一套 action ---------- */
|
|
259
260
|
const labelNode = labelNodeOf(1)
|
|
@@ -265,9 +266,9 @@
|
|
|
265
266
|
fire(window, 'pointerup', at)
|
|
266
267
|
await settle(80)
|
|
267
268
|
const labelClick = last('labelClick')
|
|
268
|
-
check('标签点选:action.type 是 selectLabel、target 是 label
|
|
269
|
-
check('
|
|
270
|
-
check('
|
|
269
|
+
check('标签点选:action.type 是 selectLabel、target 是 label、按键读 originalEvent', labelClick?.action?.type === 'selectLabel' && labelClick?.action?.target === 'label' && labelClick?.originalEvent?.button === 0 && labelClick?.action?.button === undefined, JSON.stringify(labelClick?.action))
|
|
270
|
+
check('标签点选:标签与 parent 直接读载荷顶层', labelClick?.label?.id === labelClick?.labelId && labelClick?.parent?.id === 'p2' && labelClick?.parent?.type === 'point' && labelClick?.action?.data === undefined, JSON.stringify(labelClick?.parent))
|
|
271
|
+
check('标签点选:修饰键从 originalEvent 读', labelClick?.originalEvent instanceof Event && labelClick?.originalEvent?.ctrlKey === false && labelClick?.action?.modifiers === undefined, String(labelClick?.originalEvent?.type))
|
|
271
272
|
pointerId += 1
|
|
272
273
|
const dragFrom = centerOf(labelNode)
|
|
273
274
|
const dragTo = { clientX: dragFrom.clientX + 34, clientY: dragFrom.clientY + 24 }
|
|
@@ -278,13 +279,13 @@
|
|
|
278
279
|
const labelDragStart = lastOfPhase('labelDrag', 'start')
|
|
279
280
|
const labelDragEnd = lastOfPhase('labelDrag', 'end')
|
|
280
281
|
check('标签拖动:start / end 的 action.type 都是 dragLabel、moved 是 false / true', labelDragStart?.action?.type === 'dragLabel' && labelDragStart?.action?.moved === false && labelDragEnd?.action?.type === 'dragLabel' && labelDragEnd?.action?.moved === true, JSON.stringify([labelDragStart?.action?.moved, labelDragEnd?.action?.moved]))
|
|
281
|
-
check('标签拖动:end 带 originalEvent
|
|
282
|
+
check('标签拖动:end 带 originalEvent,标签与 parent 读顶层', Boolean(labelDragEnd?.originalEvent) && labelDragEnd?.label?.id === labelDragEnd?.labelId && labelDragEnd?.parent?.type === 'point' && labelDragEnd?.action?.data === undefined, String(labelDragEnd?.originalEvent?.type))
|
|
282
283
|
pointerId += 1
|
|
283
284
|
const menuAt = centerOf(labelNode)
|
|
284
285
|
fire(labelNode, 'contextmenu', menuAt, { button: 2, buttons: 2 })
|
|
285
286
|
await settle(60)
|
|
286
287
|
const labelMenu = last('labelMenu')
|
|
287
|
-
check('标签右键:action.type 是 contextMenu、target 是 label、button
|
|
288
|
+
check('标签右键:action.type 是 contextMenu、target 是 label、button 读 originalEvent', labelMenu?.action?.type === 'contextMenu' && labelMenu?.action?.target === 'label' && labelMenu?.originalEvent?.button === 2 && labelMenu?.clientX === undefined && labelMenu?.action?.button === undefined, JSON.stringify(labelMenu?.action))
|
|
288
289
|
}
|
|
289
290
|
|
|
290
291
|
/* ---------- pickAt / getFeaturesInRect / getChannelsInView ---------- */
|
|
@@ -334,7 +335,7 @@
|
|
|
334
335
|
const dbl = last('dblclick')
|
|
335
336
|
const dblClick = last('click')
|
|
336
337
|
check('双击:第二下的 onClick 带 double 标记、onDblClick 的 action.type 是 dblClick', dblClick?.action?.double === true && dbl?.action?.type === 'dblClick' && dbl?.action?.target === 'point', JSON.stringify({ click: dblClick?.action?.double, dbl: dbl?.action?.type }))
|
|
337
|
-
check('
|
|
338
|
+
check('双击:按键读 originalEvent、节点读 item、moved 仍在 action 上', dbl?.originalEvent?.button === 0 && dbl?.item?.id === 'p4' && dbl?.action?.moved === false && dbl?.action?.button === undefined && dbl?.action?.data === undefined, JSON.stringify({ button: dbl?.originalEvent?.button, id: dbl?.item?.id }))
|
|
338
339
|
|
|
339
340
|
/* ---------- 这一轮:左右键更细的判读(左 / 右键布尔、按下意图与时长、长按、中键平移、悬停)与更多实用方法 ---------- */
|
|
340
341
|
const holder2 = document.createElement('div')
|
|
@@ -392,13 +393,13 @@
|
|
|
392
393
|
const downA2 = last2('down')
|
|
393
394
|
const upA2 = last2('up')
|
|
394
395
|
const clickA2 = last2('click')
|
|
395
|
-
check('
|
|
396
|
+
check('左键:按键读 originalEvent(左键 button 为 0)', downA2?.originalEvent?.button === 0 && downA2?.action?.button === undefined && downA2?.action?.left === undefined && downA2?.action?.right === undefined, JSON.stringify({ button: downA2?.originalEvent?.button }))
|
|
396
397
|
check('松开:action.press 是按下时的意图、duration 读到毫秒、快速点击不算长按', upA2?.action?.press === downA2?.action?.type && typeof upA2?.action?.duration === 'number' && upA2.action.duration < 500 && upA2?.action?.longPress === false, JSON.stringify({ press: upA2?.action?.press, duration: upA2?.action?.duration, longPress: upA2?.action?.longPress }))
|
|
397
398
|
check('点击:press / duration / longPress 与松开那一份同口径', clickA2?.action?.press === downA2?.action?.type && clickA2?.action?.longPress === false && typeof clickA2?.action?.duration === 'number', JSON.stringify({ press: clickA2?.action?.press, duration: clickA2?.action?.duration }))
|
|
398
399
|
fire2('pointerdown', atA2, { button: 2, buttons: 2 })
|
|
399
400
|
fire2('contextmenu', atA2, { button: 2, buttons: 2 })
|
|
400
401
|
const menuA2 = last2('menu')
|
|
401
|
-
check('
|
|
402
|
+
check('右键:按键读 originalEvent(右键 button 为 2)', menuA2?.originalEvent?.button === 2 && menuA2?.action?.button === undefined && menuA2?.action?.left === undefined && menuA2?.action?.right === undefined, JSON.stringify({ button: menuA2?.originalEvent?.button }))
|
|
402
403
|
const beforeCenter = view2.getCamera().center
|
|
403
404
|
const clicksBeforeMiddle = count2('click')
|
|
404
405
|
fire2('pointerdown', { clientX: box2.left + 80, clientY: box2.top + 80 }, { button: 1, buttons: 4 })
|
|
@@ -407,7 +408,7 @@
|
|
|
407
408
|
const middleUp2 = last2('up')
|
|
408
409
|
const afterCenter = view2.getCamera().center
|
|
409
410
|
check('中键拖动:相机跟着平移(中键不再是「按下什么都不做」)', Math.abs(afterCenter[0] - beforeCenter[0]) > 1 && Math.abs(afterCenter[1] - beforeCenter[1]) > 1, JSON.stringify({ before: beforeCenter, after: afterCenter }))
|
|
410
|
-
check('中键:按下意图与松开动作都是 pan,中键那一下不算点击', middleUp2?.action?.type === 'pan' && middleUp2?.action?.button ===
|
|
411
|
+
check('中键:按下意图与松开动作都是 pan,中键那一下不算点击', middleUp2?.action?.type === 'pan' && middleUp2?.originalEvent?.button === 1 && middleUp2?.action?.button === undefined && count2('click') === clicksBeforeMiddle, JSON.stringify({ type: middleUp2?.action?.type, button: middleUp2?.originalEvent?.button }))
|
|
411
412
|
const clicksBeforeLong = count2('click')
|
|
412
413
|
fire2('pointerdown', atA2)
|
|
413
414
|
await settle(560)
|
|
@@ -421,8 +422,8 @@
|
|
|
421
422
|
const hoverBlank2 = view2.hoverAt(box2.left + 6, box2.top + 6)
|
|
422
423
|
check('hoverAt:悬停到空白处返回 null(没有命中就不是一条读数)', hoverBlank2 === null && view2.getHover() === null, JSON.stringify(hoverBlank2))
|
|
423
424
|
const hoveredA2 = view2.hoverAt(atHover2.clientX, atHover2.clientY)
|
|
424
|
-
check('hoverAt
|
|
425
|
-
check('hoverAt:读数里有光标位置(x / y / world
|
|
425
|
+
check('hoverAt:悬停载荷的 action 只留判读,命中数据读 item', hoveredA2?.hit === true && hoveredA2?.action?.type === 'hover' && hoveredA2?.action?.target === 'point' && hoveredA2?.item?.id === 'a' && hoveredA2?.action?.button === undefined && hoveredA2?.action?.data === undefined, JSON.stringify(hoveredA2 && { type: hoveredA2.action.type, target: hoveredA2.action.target, id: hoveredA2.item?.id }))
|
|
426
|
+
check('hoverAt:读数里有光标位置(x / y / world),选中集总读数走 getSelectionInfo()', typeof hoveredA2?.x === 'number' && typeof hoveredA2?.y === 'number' && Array.isArray(hoveredA2?.world) && hoveredA2.world.length === 2 && hoveredA2?.selection === undefined && view2.getSelectionInfo().count === view2.getSelections().length && view2.getHover() === hoveredA2, JSON.stringify({ x: hoveredA2?.x, world: hoveredA2?.world }))
|
|
426
427
|
check('onHover 回调与 hoverAt 读到的是同一份读数(那一次悬停也发了回调)', last2('hover')?.item?.id === 'a' && last2('hover')?.action?.type === 'hover' && last2('hover')?.x === hoveredA2?.x, JSON.stringify({ id: last2('hover')?.item?.id, x: last2('hover')?.x }))
|
|
427
428
|
check('clearHover:清掉程序化悬停并补发一条 onHover(null)', view2.clearHover() === null && view2.getHover() === null && last2('hover') === null, JSON.stringify(last2('hover')))
|
|
428
429
|
|
|
@@ -492,7 +493,7 @@
|
|
|
492
493
|
check('step-12:指针事件往 onAction 报一句判读(按下 / 松开 / 选中,且带着数据本身)', reports.some(text => text.startsWith('按下:')) && reports.some(text => text.startsWith('松开:')) && reports.some(text => text.startsWith('选中:')) && reports.some(text => text.includes('→ 节点') && text.includes(parkStation.name)), reports.slice(-3).join(' | '))
|
|
493
494
|
// 工具栏「程序化悬停」按钮走的那条路:probeHover 让指针「停」到视口里第一个节点上,再调一次撤掉
|
|
494
495
|
const hoverProbe = actionView.probeHover(true)
|
|
495
|
-
check('step-12:probeHover(程序化悬停) 停在节点上,读数与 onHover 同一份(带 action 与光标位置)', hoverProbe?.hit === true && hoverProbe?.type === 'point' && hoverProbe?.action?.type === 'hover' && hoverProbe?.action?.button ===
|
|
496
|
+
check('step-12:probeHover(程序化悬停) 停在节点上,读数与 onHover 同一份(带 action 与光标位置)', hoverProbe?.hit === true && hoverProbe?.type === 'point' && hoverProbe?.action?.type === 'hover' && hoverProbe?.action?.button === undefined && Array.isArray(hoverProbe?.world) && actionView.view.getHover() === hoverProbe, JSON.stringify(hoverProbe && { type: hoverProbe.type, action: hoverProbe.action.type, index: hoverProbe.index }))
|
|
496
497
|
check('step-12:probeHover(false) 撤掉悬停(getHover 回到 null)', actionView.probeHover(false) === null && actionView.view.getHover() === null, '')
|
|
497
498
|
actionView.destroy()
|
|
498
499
|
holder.remove()
|
|
@@ -305,7 +305,7 @@
|
|
|
305
305
|
// 先把两条新场站追加进来(addPoints 的增量路),再按它们新增通道
|
|
306
306
|
const addedPoints = sceneView.addPoints([{ id: 'c', x: 2, y: 0, name: '丙站' }, { id: 'd', x: 2, y: 2, name: '丁站' }])
|
|
307
307
|
const added = sceneView.addChannels([
|
|
308
|
-
{ from: 'a', to: 'c',
|
|
308
|
+
{ from: 'a', to: 'c', lineData: [{}, {}], name: '甲-丙' },
|
|
309
309
|
{ from: 'b', to: 'd', name: '乙-丁' },
|
|
310
310
|
{ from: 'a', to: 'nope', name: '坏的那条' }
|
|
311
311
|
])
|