gis-view-webgl2 0.1.6 → 0.1.8
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 +9 -8
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +316 -233
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +106 -0
- package/docs/GisView.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 +110 -0
- package/docs/demos/BasicDemo.vue +2 -2
- package/docs/demos/BatchAddDemo.vue +6 -6
- package/docs/demos/ConnectDemo.vue +164 -33
- package/docs/demos/CopyMergeDemo.vue +27 -14
- package/docs/demos/EventDemo.vue +17 -20
- package/docs/demos/LabelDemo.vue +7 -7
- package/docs/demos/StyleDemo.vue +1 -1
- package/docs/demos/api-index.js +34 -31
- 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 +33 -35
- 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/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +1 -1
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +49 -16
- 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 +60 -36
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +60 -4
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +17 -1
- 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 +2 -2
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +12 -13
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +26 -23
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +23 -20
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +132 -79
- 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 +2 -1
- 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 +4 -4
- package/docs/steps/README.md +9 -7
- package/docs/steps/bench-scale.html +1 -1
- package/docs/steps/index.html +6 -6
- package/docs/steps/selftest-actions.html +26 -25
- package/docs/steps/selftest-data.html +1 -1
- package/docs/steps/selftest-demos.html +102 -6
- package/docs/steps/selftest-docs.html +3 -3
- package/docs/steps/selftest-features.html +284 -52
- package/docs/steps/selftest-full.html +3 -3
- package/docs/steps/selftest-labels.html +1 -1
- package/docs/steps/selftest-perf.html +31 -4
- package/docs/steps/selftest-select.html +183 -1
- 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/package.json +1 -1
package/docs/steps/index.html
CHANGED
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
{ file: 'step-09-minimap.js', factory: 'createMinimapView', load: () => import('./step-09-minimap.js'), title: '鹰眼图', api: '小图 + 视口框', note: '小画布画全景、DOM 画视口框、点与拖都能联动主图(文档第 8 章)' },
|
|
266
266
|
{ file: 'step-10-full.js', factory: 'createFullView', load: () => import('./step-10-full.js'), title: '换成真组件', api: 'createGisView', note: '同一份数据换成 src/gis-view 的完整实现,API 与前面几步一一对应(文档 12.11)' },
|
|
267
267
|
{ 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
|
|
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)、命中的数据直接读载荷顶层(节点 / 线路 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
269
|
{ 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
270
|
]
|
|
271
271
|
|
|
@@ -359,12 +359,12 @@
|
|
|
359
359
|
},
|
|
360
360
|
onDblClick: payload => log('onDblClick', describeElement(payload)),
|
|
361
361
|
onSelect: payload => log('onSelect', payload ? `${payload.items.length} 个要素` : '清空选中'),
|
|
362
|
-
onPointerDown:
|
|
363
|
-
setReadout('pointer', `按下:${describeElement(payload)}(button ${
|
|
362
|
+
onPointerDown: payload => {
|
|
363
|
+
setReadout('pointer', `按下:${describeElement(payload)}(button ${payload.originalEvent?.button})`)
|
|
364
364
|
},
|
|
365
|
-
onPointerUp:
|
|
366
|
-
onContextMenu:
|
|
367
|
-
|
|
365
|
+
onPointerUp: payload => log('onPointerUp', `${describeElement(payload)}(button ${payload.originalEvent?.button})`),
|
|
366
|
+
onContextMenu: payload => {
|
|
367
|
+
payload.originalEvent?.preventDefault()
|
|
368
368
|
log('onContextMenu', describeElement(payload))
|
|
369
369
|
},
|
|
370
370
|
// 需求 1 的核心:草稿里每点一次都回调,第一次拿起点、第二次拿终点
|
|
@@ -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
|
])
|
|
@@ -559,6 +559,85 @@
|
|
|
559
559
|
const afterObjectChannel = view.getData()
|
|
560
560
|
const objectChannel = channelList().at(-1)
|
|
561
561
|
check('可视化连线:commitAddChannel 直接传通道对象即可按 lineIds / groups 落地', Boolean(objectChannelDraft) && channelList().length === 2 && afterObjectChannel.lines.length === beforeObjectChannel.lines.length + 2 && String(objectChannel?.id).startsWith('connect-object-channel-') && objectChannel?.lineIds?.length === 2 && objectChannel.lineIds.every(id => String(id).startsWith('connect-object-line-')), `${channelList().length} 通道 / ${beforeObjectChannel.lines.length} -> ${afterObjectChannel.lines.length} 线 / ${objectChannel?.id ?? '无'} / ${JSON.stringify(objectChannel?.lineIds ?? [])}`)
|
|
562
|
+
|
|
563
|
+
// 单一通道 / 已有分组:预置一条 node-0 ↔ node-1 的通道,再分别验证阻断、新分组、指定分组与 beginAddLine 挂回线
|
|
564
|
+
document.querySelector('[data-demo=connect-seed-existing]').click()
|
|
565
|
+
const seededExisting = await until(() => view.getChannelGroups('connect-existing-channel').length === 2, 3000)
|
|
566
|
+
check('可视化连线:预置已有通道带两个分组,面板读数同步', Boolean(seededExisting) && channelList().length === 3 && text('connect-existing-readout').includes('已有通道'), `${channelList().length} 通道 / ${text('connect-existing-readout')}`)
|
|
567
|
+
const singleChannelInput = document.querySelector('[data-demo=connect-single-channel]')
|
|
568
|
+
const existingModeSelect = document.querySelector('[data-demo=connect-existing-mode]')
|
|
569
|
+
const groupTargetSelect = document.querySelector('[data-demo=connect-group-target]')
|
|
570
|
+
const setExistingMode = async value => {
|
|
571
|
+
existingModeSelect.value = value
|
|
572
|
+
existingModeSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
573
|
+
await wait(60)
|
|
574
|
+
}
|
|
575
|
+
const beginPresetPair = async () => {
|
|
576
|
+
startPointSelect.value = 'node-0'
|
|
577
|
+
startPointSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
578
|
+
await wait(50)
|
|
579
|
+
document.querySelector('[data-demo=connect-start]').click()
|
|
580
|
+
await wait(70)
|
|
581
|
+
await clickNode('node-1')
|
|
582
|
+
return until(() => {
|
|
583
|
+
const current = view.getAddChannelDraft()
|
|
584
|
+
return current?.phase === 'ready' ? current : null
|
|
585
|
+
}, 2500)
|
|
586
|
+
}
|
|
587
|
+
await setExistingMode('none')
|
|
588
|
+
if (!singleChannelInput.checked) singleChannelInput.click()
|
|
589
|
+
await wait(50)
|
|
590
|
+
const blockedLinesBefore = view.getData().lines.length
|
|
591
|
+
const blockedDraft = await beginPresetPair()
|
|
592
|
+
document.querySelector('[data-demo=connect-default]').click()
|
|
593
|
+
const blockedEventReady = await until(() => text('connect-event-readout').includes('phase=blocked'), 2500)
|
|
594
|
+
check('可视化连线:singleChannel + 已有通道触发 ready blocked 与提交失败事件', Boolean(blockedDraft) && blockedDraft.blocked === true && blockedDraft.existingChannel === 'block' && Boolean(blockedEventReady) && text('connect-event-readout').includes('singleChannel=true') && text('connect-action-readout').includes('blocked'), `${text('connect-event-readout')} / ${view.getData().lines.length} 线`)
|
|
595
|
+
check('可视化连线:阻断失败不改线路 / 通道数据', view.getData().lines.length === blockedLinesBefore && channelList().length === 3, `${blockedLinesBefore} → ${view.getData().lines.length} 线 / ${channelList().length} 通道`)
|
|
596
|
+
|
|
597
|
+
await setExistingMode('newGroup')
|
|
598
|
+
const groupsBeforeNew = view.getChannelGroups('connect-existing-channel').length
|
|
599
|
+
const newGroupLinesBefore = view.getData().lines.length
|
|
600
|
+
const newGroupDraft = await beginPresetPair()
|
|
601
|
+
const newGroupCommitReady = newGroupDraft?.preferred === 'newGroup' && newGroupDraft?.blocked === false
|
|
602
|
+
document.querySelector('[data-demo=connect-default]').click()
|
|
603
|
+
await wait(160)
|
|
604
|
+
check('可视化连线:existingChannel: newGroup 把 2 回新线作为新分组加入已有通道', Boolean(newGroupCommitReady) && view.getChannelGroups('connect-existing-channel').length === groupsBeforeNew + 1 && view.getData().lines.length === newGroupLinesBefore + 2 && channelList().length === 3, `${groupsBeforeNew} → ${view.getChannelGroups('connect-existing-channel').length} 组 / ${newGroupLinesBefore} → ${view.getData().lines.length} 线 / ${text('connect-action-readout')}`)
|
|
605
|
+
|
|
606
|
+
await setExistingMode('joinGroup')
|
|
607
|
+
groupTargetSelect.value = 'connect-existing-group-b'
|
|
608
|
+
groupTargetSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
609
|
+
await wait(50)
|
|
610
|
+
const joinGroupBefore = view.getChannelGroups('connect-existing-channel').find(group => group.id === 'connect-existing-group-b')?.lineIds.length ?? 0
|
|
611
|
+
const joinGroupChannelsBefore = channelList().length
|
|
612
|
+
const joinGroupDraft = await beginPresetPair()
|
|
613
|
+
const joinGroupCommitReady = joinGroupDraft?.targetGroupId === 'connect-existing-group-b'
|
|
614
|
+
document.querySelector('[data-demo=connect-default]').click()
|
|
615
|
+
await wait(160)
|
|
616
|
+
const joinGroupAfter = view.getChannelGroups('connect-existing-channel').find(group => group.id === 'connect-existing-group-b')?.lineIds.length ?? 0
|
|
617
|
+
check('可视化连线:existingChannel: joinGroup + groupId 把 2 回新线并入指定分组', Boolean(joinGroupCommitReady) && joinGroupAfter === joinGroupBefore + 2 && channelList().length === joinGroupChannelsBefore, `group-b ${joinGroupBefore} → ${joinGroupAfter} 回 / ${channelList().length} 通道 / ${text('connect-action-readout')}`)
|
|
618
|
+
|
|
619
|
+
kindSelect.value = 'line'
|
|
620
|
+
kindSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
621
|
+
await wait(60)
|
|
622
|
+
groupTargetSelect.value = 'connect-existing-group-a'
|
|
623
|
+
groupTargetSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
624
|
+
await wait(50)
|
|
625
|
+
const attachGroupBefore = view.getChannelGroups('connect-existing-channel').find(group => group.id === 'connect-existing-group-a')?.lineIds.length ?? 0
|
|
626
|
+
const attachChannelsBefore = channelList().length
|
|
627
|
+
const attachDraft = await beginPresetPair()
|
|
628
|
+
const attachDraftReady = attachDraft?.kind === 'line' && attachDraft?.existingChannel === 'joinGroup' && attachDraft?.targetGroupId === 'connect-existing-group-a'
|
|
629
|
+
document.querySelector('[data-demo=connect-default]').click()
|
|
630
|
+
await wait(160)
|
|
631
|
+
const attachGroupAfter = view.getChannelGroups('connect-existing-channel').find(group => group.id === 'connect-existing-group-a')?.lineIds.length ?? 0
|
|
632
|
+
check('可视化连线:beginAddLine + existingChannel: joinGroup 也能把新回线挂进指定分组,不新建通道', Boolean(attachDraftReady) && attachGroupAfter === attachGroupBefore + 1 && channelList().length === attachChannelsBefore && text('connect-log').includes('beginAddLine'), `${text('connect-log')} / group-a ${attachGroupBefore} → ${attachGroupAfter} 回`)
|
|
633
|
+
|
|
634
|
+
kindSelect.value = 'channel-2'
|
|
635
|
+
kindSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
636
|
+
await setExistingMode('auto')
|
|
637
|
+
const autoDraft = await beginPresetPair()
|
|
638
|
+
check('可视化连线:existingChannel: auto 在分组有余量时推荐新分组', autoDraft?.blocked === false && autoDraft?.preferred === 'newGroup' && autoDraft?.existingChannel === 'auto', JSON.stringify({ preferred: autoDraft?.preferred, blocked: autoDraft?.blocked }))
|
|
639
|
+
view.cancelAddChannel()
|
|
640
|
+
await wait(60)
|
|
562
641
|
check('可视化连线:读数面板同步报出纯点 + 线 / 通道数量', text('connect-data-readout').includes('节点') && text('connect-data-readout').includes('通道'), text('connect-data-readout'))
|
|
563
642
|
}
|
|
564
643
|
else {
|
|
@@ -931,7 +1010,7 @@
|
|
|
931
1010
|
const click = key => document.querySelector(`[data-demo="${key}"]`)?.click()
|
|
932
1011
|
const text = key => document.querySelector(`[data-demo="${key}"]`)?.textContent.trim() ?? ''
|
|
933
1012
|
if (ready && view) {
|
|
934
|
-
const groupsOf = () => view.
|
|
1013
|
+
const groupsOf = () => (view.getOptions().channels ?? []).reduce((sum, channel) => sum + view.getChannelGroups(channel.id).length, 0)
|
|
935
1014
|
const lineIndex = view.getData().lines.findIndex(line => String(line.id) === 'line-0')
|
|
936
1015
|
const waypointCount = () => view.getWaypoints(lineIndex).length
|
|
937
1016
|
const pointsBefore = view.getData().points.length
|
|
@@ -1211,17 +1290,34 @@
|
|
|
1211
1290
|
check('批量复制与合并:409 后 rebase 生成新 opId 并成功落库',
|
|
1212
1291
|
Boolean(copyRebased) && setup.backendSnapshot?.version === conflictVersion + 2 && setup.backendSnapshot?.applied === conflictApplied + 1,
|
|
1213
1292
|
`v${conflictVersion} → v${setup.backendSnapshot?.version} / applied ${conflictApplied} → ${setup.backendSnapshot?.applied} / ${text('copy-backend-readout')}`)
|
|
1214
|
-
//
|
|
1293
|
+
// 解除合并:先选一组,再点演示按钮;节点 +1、autoLines 分配读数落到面板,撤销一步回到拆分前
|
|
1215
1294
|
click('copy-select-sources')
|
|
1216
1295
|
const splitSelectionReady = await until(() => view.getSelectionData().pointCount === 4, 5000)
|
|
1217
1296
|
const splitSource = view.getSelectionData().points?.[0] ?? view.getData().points[0]
|
|
1297
|
+
const splitChannelsBefore = view.getOptions().channels ?? []
|
|
1218
1298
|
const pointsBeforeSplit = view.getData().points.length
|
|
1219
1299
|
click('split-selected')
|
|
1220
1300
|
const splitLanded = await until(() => view.getData().points.length === pointsBeforeSplit + 1 && text('split-readout').includes('splitPoint'), 6000)
|
|
1221
1301
|
const splitReadout = text('split-readout')
|
|
1222
|
-
|
|
1223
|
-
|
|
1302
|
+
const splitResult = unwrap(setup.lastSplitResult)
|
|
1303
|
+
const splitSlotByLine = new Map()
|
|
1304
|
+
for (const assignment of splitResult?.lineAssignments ?? []) {
|
|
1305
|
+
for (const lineId of assignment.lineIds ?? []) splitSlotByLine.set(String(lineId), assignment.index)
|
|
1306
|
+
}
|
|
1307
|
+
const splitChannelsKept = splitChannelsBefore.every((channel) => {
|
|
1308
|
+
const slots = new Set()
|
|
1309
|
+
for (const lineId of channel.lineIds ?? []) {
|
|
1310
|
+
const slot = splitSlotByLine.get(String(lineId))
|
|
1311
|
+
if (slot !== undefined) slots.add(slot)
|
|
1312
|
+
}
|
|
1313
|
+
return slots.size <= 1
|
|
1314
|
+
})
|
|
1315
|
+
check('批量复制与合并:解除合并走 splitPoint,autoLines / 自动分线与改线 / 改通道读数落面板',
|
|
1316
|
+
Boolean(splitSelectionReady) && Boolean(splitLanded) && text('copy-action').includes('splitPoint') && splitReadout.includes('autoLines=alternate') && splitReadout.includes('自动分线') && splitReadout.includes('改线') && splitReadout.includes('改通道'),
|
|
1224
1317
|
`${pointsBeforeSplit} → ${view.getData().points.length} · ${splitReadout}`)
|
|
1318
|
+
check('批量复制与合并:解除合并按通道成组分配,同一条通道的回线不会落到两个节点',
|
|
1319
|
+
splitResult?.autoLines === 'alternate' && splitResult?.autoAssignedLines > 0 && splitResult.lineAssignments.reduce((sum, item) => sum + item.lineCount, 0) === splitResult.autoAssignedLines && splitChannelsKept && splitReadout.includes('通道成组保持完整'),
|
|
1320
|
+
JSON.stringify({ autoLines: splitResult?.autoLines, assigned: splitResult?.autoAssignedLines, channelsKept: splitChannelsKept, counts: splitResult?.lineAssignments?.map(item => item.lineCount) }))
|
|
1225
1321
|
view.undo()
|
|
1226
1322
|
const splitUndone = await until(() => view.getData().points.length === pointsBeforeSplit && view.getData().points.some(point => String(point.id) === String(splitSource?.id)), 5000)
|
|
1227
1323
|
check('批量复制与合并:拆分是一步历史,撤销后节点与连线恢复', Boolean(splitUndone), `${view.getData().points.length} 点 / ${splitSource?.id ?? '—'}`)
|
|
@@ -2493,8 +2589,8 @@
|
|
|
2493
2589
|
await wait(150)
|
|
2494
2590
|
const apiItems = () => [...document.querySelectorAll('.demo-api-list:not(.demo-api-list--options) .demo-api-item')]
|
|
2495
2591
|
const optionItems = () => [...document.querySelectorAll('.demo-api-list--options .demo-api-item')]
|
|
2496
|
-
check('实例方法速查:整份成员清单按分组列全(16 组 /
|
|
2497
|
-
Boolean(ready) && document.querySelectorAll('.demo-api-group').length === 16 && apiItems().length ===
|
|
2592
|
+
check('实例方法速查:整份成员清单按分组列全(16 组 / 267 个成员)',
|
|
2593
|
+
Boolean(ready) && document.querySelectorAll('.demo-api-group').length === 16 && apiItems().length === 267,
|
|
2498
2594
|
`${document.querySelectorAll('.demo-api-group').length} 组 / ${apiItems().length} 个成员`)
|
|
2499
2595
|
const apiNames = apiItems().map(node => node.querySelector('b')?.textContent)
|
|
2500
2596
|
const roundNames = ['registerSymbol', 'unregisterSymbol', 'listSymbols']
|
|
@@ -520,8 +520,8 @@
|
|
|
520
520
|
if (check('API 速查:挂上了 __gisApi 钩子(数据来自 api-index.js,没钩子多半是 file:// 打开的)', Boolean(apiHook))) {
|
|
521
521
|
const apiDoc = apiFrame.contentDocument
|
|
522
522
|
const summary = apiHook.summary
|
|
523
|
-
check('API 速查:清单来自 api-index(
|
|
524
|
-
summary.members ===
|
|
523
|
+
check('API 速查:清单来自 api-index(267 个成员 / 262 个方法 / 5 个属性 / 96 个 options / 16 个分组)',
|
|
524
|
+
summary.members === 267 && summary.methods === 262 && summary.properties === 5 && summary.options === 96 && summary.sections === 16,
|
|
525
525
|
JSON.stringify(summary))
|
|
526
526
|
const topJumps = [...apiDoc.querySelectorAll('.doc-jump')]
|
|
527
527
|
const jumpTargets = topJumps.map(node => `${node.dataset.docJump}:${node.getAttribute('href')}`)
|
|
@@ -559,7 +559,7 @@
|
|
|
559
559
|
`${apiDoc.querySelectorAll('.api-member').length} 张卡片 / 属性 ${apiDoc.querySelectorAll('.api-member[data-kind="property"]').length} 张`)
|
|
560
560
|
const noExample = [...apiDoc.querySelectorAll('.api-member')].filter(node => Number(node.dataset.examples) < 1)
|
|
561
561
|
check('API 速查:每个成员都带至少一段示例(不出现空卡片)', noExample.length === 0,
|
|
562
|
-
noExample.length ? noExample.map(node => node.dataset.name).join(' / ') : '
|
|
562
|
+
noExample.length ? noExample.map(node => node.dataset.name).join(' / ') : '267 张卡片都有示例')
|
|
563
563
|
check('API 速查:96 个 options 都列全(名字 / 类型 / 默认值 / 说明四件套)',
|
|
564
564
|
apiDoc.querySelectorAll('.api-option').length === summary.options && [...apiDoc.querySelectorAll('.api-option')].every(node => node.querySelector('.name code') && node.querySelector('.type') && node.querySelector('.def') && node.querySelector('.doc')),
|
|
565
565
|
`${apiDoc.querySelectorAll('.api-option').length} 个`)
|