cardbuddy 0.6.0 → 0.7.0
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/dist/preview-editor/assets/{index-BYB0hngG.js → index-BlscexDE.js} +18 -11
- package/dist/preview-editor/assets/index-dlbSPb5W.css +1 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/server-core/cardFiles.js +9 -4
- package/dist/server-core/index.js +6 -0
- package/dist/server-core/sliceUpload.js +97 -0
- package/dist/vendored/App.vue +263 -44
- package/dist/vendored/assets/svgIcons/icons.svg +5 -0
- package/dist/vendored/components/ApiCallPanel.vue +115 -0
- package/dist/vendored/components/CardGallery.vue +2 -0
- package/dist/vendored/components/CardSelect.vue +6 -0
- package/dist/vendored/components/ConfirmDialog.vue +20 -5
- package/dist/vendored/components/DebugPanel.vue +22 -88
- package/dist/vendored/components/JsonTree.vue +35 -4
- package/dist/vendored/components/StructureTree.vue +53 -2
- package/dist/vendored/styles/app.css +142 -51
- package/dist/vendored/styles/card-gallery.css +20 -0
- package/dist/vendored/styles/card-select.css +15 -0
- package/dist/vendored/styles/json-tree.css +1 -1
- package/dist/vendored/styles/structure-tree.css +135 -5
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-CpBZEQDy.css +0 -1
|
@@ -43,6 +43,12 @@
|
|
|
43
43
|
@mousemove="highlighted = i"
|
|
44
44
|
@click="choose(opt)"
|
|
45
45
|
>
|
|
46
|
+
<span
|
|
47
|
+
class="csel__dot"
|
|
48
|
+
:class="{ 'csel__dot--imported': opt.imported }"
|
|
49
|
+
:title="opt.imported ? '导入卡片(含 .import-meta.json 来源信息)' : '本地卡片(无导入来源信息)'"
|
|
50
|
+
aria-hidden="true"
|
|
51
|
+
></span>
|
|
46
52
|
<span class="csel__item-id">{{ opt.id }}</span>
|
|
47
53
|
<span class="csel__item-name">{{ opt.name }}</span>
|
|
48
54
|
<svg v-if="opt.id === value" class="csel__check" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><use href="#icon-check" /></svg>
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
<transition name="confirm-fade">
|
|
3
3
|
<div v-if="visible" class="confirm-mask" @click.self="$emit('cancel')">
|
|
4
4
|
<div class="confirm-modal" role="alertdialog" aria-modal="true" :aria-label="title">
|
|
5
|
-
<div class="confirm-modal__title" :class="{ 'is-danger': danger }">
|
|
6
|
-
<svg v-if="danger" class="confirm-modal__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-no-entry" /></svg>
|
|
5
|
+
<div class="confirm-modal__title" :class="{ 'is-danger': severity === 'danger', 'is-warn': severity === 'warn' }">
|
|
6
|
+
<svg v-if="severity === 'danger'" class="confirm-modal__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-no-entry" /></svg>
|
|
7
|
+
<svg v-else-if="severity === 'warn'" class="confirm-modal__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-alert-triangle" /></svg>
|
|
7
8
|
<svg v-else class="confirm-modal__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-gear" /></svg>
|
|
8
9
|
{{ title }}
|
|
9
10
|
</div>
|
|
10
11
|
<div class="confirm-modal__desc">{{ message }}</div>
|
|
11
12
|
<div class="confirm-modal__actions">
|
|
12
13
|
<button class="confirm-modal__btn confirm-modal__btn--ghost" type="button" @click="$emit('cancel')">{{ cancelText }}</button>
|
|
13
|
-
<button class="confirm-modal__btn" :class="
|
|
14
|
+
<button class="confirm-modal__btn" :class="confirmBtnClass" type="button" @click="$emit('confirm')">{{ confirmText }}</button>
|
|
14
15
|
</div>
|
|
15
16
|
</div>
|
|
16
17
|
</div>
|
|
@@ -31,9 +32,23 @@ export default {
|
|
|
31
32
|
confirmText: { type: String, default: '继续' },
|
|
32
33
|
/** 取消按钮文案,默认「取消」 */
|
|
33
34
|
cancelText: { type: String, default: '取消' },
|
|
34
|
-
/**
|
|
35
|
-
|
|
35
|
+
/** 严重级:primary=常规确认(蓝)/ warn=警告操作(橙黄+警告三角图标,如版本推送等覆盖推送类)/
|
|
36
|
+
* danger=明确负面结果(红+禁止图标,如全量同步覆盖本地卡等不可逆数据丢失)。
|
|
37
|
+
* 分级与配色见 design/深浅主题配色规范手册.md §2.7 / §3.4。 */
|
|
38
|
+
severity: {
|
|
39
|
+
type: String,
|
|
40
|
+
default: 'primary',
|
|
41
|
+
validator: (v) => ['primary', 'warn', 'danger'].includes(v),
|
|
42
|
+
},
|
|
36
43
|
},
|
|
37
44
|
emits: ['confirm', 'cancel'],
|
|
45
|
+
computed: {
|
|
46
|
+
/** 确认按钮配色档:与 severity 一一对应 */
|
|
47
|
+
confirmBtnClass() {
|
|
48
|
+
if (this.severity === 'danger') return 'confirm-modal__btn--danger'
|
|
49
|
+
if (this.severity === 'warn') return 'confirm-modal__btn--warn'
|
|
50
|
+
return 'confirm-modal__btn--primary'
|
|
51
|
+
},
|
|
52
|
+
},
|
|
38
53
|
}
|
|
39
54
|
</script>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
|
|
3
|
-
- 接口调用:ynet.callJSAPI / postMessage 的调用流水
|
|
2
|
+
调试面板:三个标签页。
|
|
4
3
|
- 数据绑定:当前 rootScope 的实时 JSON(初始数据 / 落盘 / 保存高亮比对源)。
|
|
5
4
|
本地调试的所有交互只写「运行态」,不会改这里,故调试中此 tab 保持不变、保存不亮。
|
|
6
5
|
- 组件结构:当前卡片的组件层级树(参考编辑器「结构」面板)
|
|
7
6
|
- 卡片来源:导入来源元数据
|
|
8
7
|
|
|
8
|
+
注:「接口调用」tab 已拆出为右侧常驻独立面板(ApiCallPanel.vue),
|
|
9
|
+
由 App 顶栏按钮控制展开/收起。
|
|
10
|
+
|
|
9
11
|
运行态(rt.state)已从标签页移出:底部状态栏新增「运行态」指示器,点击在面板内
|
|
10
12
|
弹出可拖拽高度的只读面板,默认收起。rt.state 是运行期交互态(Input 输入 /
|
|
11
13
|
RadioButton·CheckGroup 选择 / globalSet 等只写这里),不污染 rootScope、不触发保存
|
|
@@ -24,47 +26,11 @@
|
|
|
24
26
|
@click="$emit('update:activeTab', t.key)"
|
|
25
27
|
>
|
|
26
28
|
{{ t.label }}
|
|
27
|
-
<span v-if="t.key === 'api' && apiCalls.length" class="debug__count">{{ apiCalls.length }}</span>
|
|
28
29
|
</button>
|
|
29
30
|
</div>
|
|
30
31
|
|
|
31
32
|
<div class="debug__body">
|
|
32
|
-
<template v-if="activeTab === '
|
|
33
|
-
<button
|
|
34
|
-
class="debug__clear"
|
|
35
|
-
type="button"
|
|
36
|
-
:disabled="!apiCalls.length"
|
|
37
|
-
title="清空日志"
|
|
38
|
-
@click="clearApiLog"
|
|
39
|
-
>
|
|
40
|
-
<!-- 垃圾桶图标:清空日志 -->
|
|
41
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none"
|
|
42
|
-
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#icon-trash" /></svg>
|
|
43
|
-
</button>
|
|
44
|
-
<div class="debug__placeholder"></div>
|
|
45
|
-
<p v-if="!apiCalls.length" class="debug__empty">暂无接口调用</p>
|
|
46
|
-
<ul class="debug__list debug__list--api">
|
|
47
|
-
<li v-for="(c, i) in apiCalls" :key="i" class="debug__item debug__item--api">
|
|
48
|
-
<div class="debug__api-row">
|
|
49
|
-
<span class="debug__kind">{{ c.api }}</span>
|
|
50
|
-
<button
|
|
51
|
-
class="debug__copy-ic"
|
|
52
|
-
type="button"
|
|
53
|
-
:title="copiedIndex === i ? '已复制 ✓' : '复制数据(不含方法名)'"
|
|
54
|
-
@click="copyApiPayload(c, i)"
|
|
55
|
-
>
|
|
56
|
-
<svg v-if="copiedIndex !== i" width="15" height="15" viewBox="0 0 24 24" fill="none"
|
|
57
|
-
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#icon-copy-sm" /></svg>
|
|
58
|
-
<span v-else class="debug__copy-ok">✓</span>
|
|
59
|
-
</button>
|
|
60
|
-
</div>
|
|
61
|
-
<!-- 返回数据:结构化折叠树(Chrome 控制台风格) -->
|
|
62
|
-
<ApiPayloadTree :value="c.payload" />
|
|
63
|
-
</li>
|
|
64
|
-
</ul>
|
|
65
|
-
</template>
|
|
66
|
-
|
|
67
|
-
<template v-else-if="activeTab === 'scope'">
|
|
33
|
+
<template v-if="activeTab === 'scope'">
|
|
68
34
|
<div class="debug__scope-wrap" :class="{ 'debug__scope-wrap--raw': rawMode }">
|
|
69
35
|
<!-- Raw 模式:直接编辑原始 JSON(CodeJar + Prism 语法高亮) -->
|
|
70
36
|
<template v-if="rawMode">
|
|
@@ -213,10 +179,14 @@
|
|
|
213
179
|
</section>
|
|
214
180
|
|
|
215
181
|
<!--
|
|
216
|
-
|
|
217
|
-
|
|
182
|
+
底部状态栏:Teleport 到 App 的 #app-workside-bar,作为「调试面板 + 接口调用面板」
|
|
183
|
+
中右两栏共同的底栏(贴近 VSCode 布局)。defer:目标与 Teleport 同属本应用组件树,
|
|
184
|
+
挂载时目标尚未插入 document,须等整个应用挂载完成后再解析(Vue 3.5+)。
|
|
185
|
+
chrome-off 截图模式下禁用 Teleport,回落到本面板内渲染(随面板一起隐藏)。
|
|
186
|
+
左侧「问题」指示器展示绑定错误数,点击弹出/收起错误面板;右侧展示当前卡片与卡片总数。
|
|
218
187
|
-->
|
|
219
|
-
<
|
|
188
|
+
<Teleport defer to="#app-workside-bar" :disabled="chromeOff">
|
|
189
|
+
<section class="statusbar">
|
|
220
190
|
<button
|
|
221
191
|
class="statusbar__item statusbar__item--prob"
|
|
222
192
|
:class="{ 'is-open': statePanelOpen }"
|
|
@@ -257,10 +227,11 @@
|
|
|
257
227
|
<svg class="statusbar__conn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-link" /></svg>
|
|
258
228
|
<span>{{ devSvcLabel }}</span>
|
|
259
229
|
</span>
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
230
|
+
<span class="statusbar__spacer"></span>
|
|
231
|
+
<span class="statusbar__item statusbar__item--right">{{ cardId }}</span>
|
|
232
|
+
<span class="statusbar__item statusbar__item--right">{{ cardCount }} 张卡片</span>
|
|
233
|
+
</section>
|
|
234
|
+
</Teleport>
|
|
264
235
|
|
|
265
236
|
<section class="errpanel" ref="errPanel" v-show="errorPanel">
|
|
266
237
|
<div class="errpanel__head" @mousedown.prevent="startResize">
|
|
@@ -321,7 +292,6 @@
|
|
|
321
292
|
|
|
322
293
|
<script>
|
|
323
294
|
import JsonTree from './JsonTree.vue'
|
|
324
|
-
import ApiPayloadTree from './ApiPayloadTree.vue'
|
|
325
295
|
import StructureTree from './StructureTree.vue'
|
|
326
296
|
import NodeInspector from './NodeInspector.vue'
|
|
327
297
|
import EventEditor from './EventEditor.vue'
|
|
@@ -355,7 +325,7 @@ function deepEqual(a, b) {
|
|
|
355
325
|
|
|
356
326
|
export default {
|
|
357
327
|
name: 'DebugPanel',
|
|
358
|
-
components: { JsonTree,
|
|
328
|
+
components: { JsonTree, StructureTree, NodeInspector, EventEditor, LogValue },
|
|
359
329
|
props: {
|
|
360
330
|
rt: { type: Object, required: true },
|
|
361
331
|
preview: { type: Object, default: null },
|
|
@@ -370,22 +340,21 @@ export default {
|
|
|
370
340
|
// 拖拽移动成功令牌:App 在节点拖拽移动成功后自增,透传给结构树,
|
|
371
341
|
// 触发「选中被拖节点 + 仅展开其祖先链、其余全部折叠」。
|
|
372
342
|
moveCollapseToken: { type: Number, default: 0 },
|
|
373
|
-
// 当前激活的调试面板 tab
|
|
343
|
+
// 当前激活的调试面板 tab(绑定数据/组件结构/卡片来源),由 App 统一管理,
|
|
374
344
|
// 便于画布 Ctrl/⌘ 点击定位在「组件结构」tab 激活时才生效。
|
|
375
|
-
activeTab: { type: String, default: '
|
|
345
|
+
activeTab: { type: String, default: 'scope' },
|
|
376
346
|
// 本地开发服务连接状态:复用 App 中顶栏「连接」按钮同款状态/配色(devSvcClass/devSvcTitle/devSvcLabel),
|
|
377
347
|
// 供底部状态栏展示「连接图标 + 状态文案」,与右上角按钮实时同步(颜色一致、状态一致)。
|
|
378
348
|
devSvcClass: { type: String, default: 'app__btn--warn' },
|
|
379
349
|
devSvcTitle: { type: String, default: '' },
|
|
380
350
|
devSvcLabel: { type: String, default: '' },
|
|
351
|
+
// 纯净截图模式(?chrome=off):禁用状态栏 Teleport,使其留在本面板内随面板隐藏
|
|
352
|
+
chromeOff: { type: Boolean, default: false },
|
|
381
353
|
},
|
|
382
354
|
data() {
|
|
383
355
|
return {
|
|
384
356
|
copied: false,
|
|
385
357
|
copyTimer: null,
|
|
386
|
-
// 接口调用 tab:刚复制过数据的条目下标(用于图标变 ✓ 反馈),null=无
|
|
387
|
-
copiedIndex: null,
|
|
388
|
-
apiCopyTimer: null,
|
|
389
358
|
// 底部错误面板模式:''(收起) | 'binding'(绑定错误) | 'event'(事件执行错误)
|
|
390
359
|
errorPanel: '',
|
|
391
360
|
// 运行态面板:默认收起;点击状态栏「运行态」指示器切换
|
|
@@ -409,24 +378,11 @@ export default {
|
|
|
409
378
|
tabs: [
|
|
410
379
|
{ key: 'scope', label: '绑定数据' },
|
|
411
380
|
{ key: 'structure', label: '组件结构' },
|
|
412
|
-
{ key: 'api', label: '接口调用' },
|
|
413
381
|
{ key: 'meta', label: '卡片来源' },
|
|
414
382
|
],
|
|
415
383
|
}
|
|
416
384
|
},
|
|
417
385
|
computed: {
|
|
418
|
-
// 依赖 apiRev,使接口调用流水在 pushApiCall / clearApiCalls 后实时刷新
|
|
419
|
-
// (apiCalls 本身是非响应式数组,模板直接读它不会触发重渲染)
|
|
420
|
-
//
|
|
421
|
-
// ⚠️ 必须 slice() 返回**新数组**,不能直接 return 原数组引用:
|
|
422
|
-
// Vue 3.4+ 引入了「computed 值稳定性」——重算后若新值与旧值 Object.is 相等,
|
|
423
|
-
// 就不再通知下游 effect。原数组是同一个引用,即使 apiRev 自增触发了重算,
|
|
424
|
-
// 渲染 effect 也收不到通知,界面纹丝不动;直到别的原因(如点 tab 改了 this.tab)
|
|
425
|
-
// 引起重渲染,才把积压的日志一次性刷出来——这正是「切 tab 才看得到」的成因。
|
|
426
|
-
apiCalls() {
|
|
427
|
-
void this.rt.apiRev
|
|
428
|
-
return this.rt.apiCalls.slice()
|
|
429
|
-
},
|
|
430
386
|
// 依赖 revision,使 setData 后数据绑定实时刷新
|
|
431
387
|
scopeText() {
|
|
432
388
|
void this.rt.revision
|
|
@@ -744,11 +700,6 @@ export default {
|
|
|
744
700
|
onHoverNode(payload) {
|
|
745
701
|
this.$emit('hover-node', payload)
|
|
746
702
|
},
|
|
747
|
-
/** 清空接口调用流水:调用运行时 clearApiCalls(清空数组 + 自增 apiRev),
|
|
748
|
-
* 因 apiCalls computed 返回 slice() 新引用,触发实时重渲染 */
|
|
749
|
-
clearApiLog() {
|
|
750
|
-
this.rt.clearApiCalls()
|
|
751
|
-
},
|
|
752
703
|
/** 清空事件主动日志(事件面板「清空日志」按钮);错误不清除 */
|
|
753
704
|
clearEventLogs() {
|
|
754
705
|
this.rt.clearEventLogs()
|
|
@@ -784,23 +735,6 @@ export default {
|
|
|
784
735
|
return iso
|
|
785
736
|
}
|
|
786
737
|
},
|
|
787
|
-
/** 复制某条接口调用的「返回数据」(即 payload 全文),不含方法名(api) */
|
|
788
|
-
async copyApiPayload(c, i) {
|
|
789
|
-
let text = ''
|
|
790
|
-
try {
|
|
791
|
-
text = JSON.stringify(c.payload, null, 2)
|
|
792
|
-
} catch {
|
|
793
|
-
text = String(c.payload)
|
|
794
|
-
}
|
|
795
|
-
const ok = await copyText(text)
|
|
796
|
-
if (ok) {
|
|
797
|
-
this.copiedIndex = i
|
|
798
|
-
if (this.apiCopyTimer) clearTimeout(this.apiCopyTimer)
|
|
799
|
-
this.apiCopyTimer = setTimeout(() => {
|
|
800
|
-
if (this.copiedIndex === i) this.copiedIndex = null
|
|
801
|
-
}, 1500)
|
|
802
|
-
}
|
|
803
|
-
},
|
|
804
738
|
},
|
|
805
739
|
}
|
|
806
740
|
</script>
|
|
@@ -135,6 +135,17 @@
|
|
|
135
135
|
<button v-if="editable" class="jt__ctxmenu-item jt__ctxmenu-item--del" type="button" @click="ctxDelete">删除节点</button>
|
|
136
136
|
</div>
|
|
137
137
|
<div v-if="ctxMenu.visible" class="jt__ctxmenu-mask" @click="hideContextMenu" @contextmenu.prevent="hideContextMenu"></div>
|
|
138
|
+
|
|
139
|
+
<!-- 删除被引用节点的二次确认:警告级(橙黄+警告三角),分级见 design/深浅主题配色规范手册.md §3.4 -->
|
|
140
|
+
<ConfirmDialog
|
|
141
|
+
:visible="delConfirm.show"
|
|
142
|
+
title="删除被引用节点"
|
|
143
|
+
:message="delConfirm.message"
|
|
144
|
+
confirm-text="删除"
|
|
145
|
+
severity="warn"
|
|
146
|
+
@confirm="onDelConfirm"
|
|
147
|
+
@cancel="onDelCancel"
|
|
148
|
+
/>
|
|
138
149
|
</div>
|
|
139
150
|
</template>
|
|
140
151
|
|
|
@@ -142,6 +153,7 @@
|
|
|
142
153
|
import { reactive } from 'vue'
|
|
143
154
|
import { copyText } from '@/runtime/clipboard.js'
|
|
144
155
|
import { showAppToast } from '@/runtime/app-toast.js'
|
|
156
|
+
import ConfirmDialog from './ConfirmDialog.vue'
|
|
145
157
|
|
|
146
158
|
// 模块级共享状态:驱动「全部展开 / 全部折叠」和「选中节点」。
|
|
147
159
|
// 所有 JsonTree 实例(含递归子实例)共享同一份,避免 provide/inject
|
|
@@ -187,6 +199,7 @@ function getByPath(obj, path) {
|
|
|
187
199
|
|
|
188
200
|
export default {
|
|
189
201
|
name: 'JsonTree',
|
|
202
|
+
components: { ConfirmDialog },
|
|
190
203
|
props: {
|
|
191
204
|
data: { type: [Object, Array], default: () => ({}) },
|
|
192
205
|
depth: { type: Number, default: 0 },
|
|
@@ -208,6 +221,11 @@ export default {
|
|
|
208
221
|
y: 0,
|
|
209
222
|
item: null,
|
|
210
223
|
},
|
|
224
|
+
delConfirm: {
|
|
225
|
+
show: false,
|
|
226
|
+
message: '',
|
|
227
|
+
item: null,
|
|
228
|
+
},
|
|
211
229
|
}
|
|
212
230
|
},
|
|
213
231
|
computed: {
|
|
@@ -479,16 +497,29 @@ export default {
|
|
|
479
497
|
const item = this.ctxMenu.item
|
|
480
498
|
this.hideContextMenu()
|
|
481
499
|
if (!item) return
|
|
482
|
-
//
|
|
500
|
+
// 检查是否被引用:被引用时走警告级二次确认(ConfirmDialog severity=warn),未引用直接删除
|
|
483
501
|
const referenced = this.isNodeReferenced(item.path)
|
|
484
502
|
if (referenced) {
|
|
485
503
|
const key = item.path.join('.')
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
504
|
+
this.delConfirm.message = `节点 “${key}” 在 schema 中被引用,删除后可能导致渲染异常。\n\n确定要删除吗?`
|
|
505
|
+
this.delConfirm.item = item
|
|
506
|
+
this.delConfirm.show = true
|
|
507
|
+
return
|
|
489
508
|
}
|
|
490
509
|
this.deleteNode(item)
|
|
491
510
|
},
|
|
511
|
+
/** 警告级二次确认点「删除」:执行挂起的删除 */
|
|
512
|
+
onDelConfirm() {
|
|
513
|
+
const item = this.delConfirm.item
|
|
514
|
+
this.delConfirm.show = false
|
|
515
|
+
this.delConfirm.item = null
|
|
516
|
+
if (item) this.deleteNode(item)
|
|
517
|
+
},
|
|
518
|
+
/** 警告级二次确认点「取消」/遮罩:丢弃挂起的删除 */
|
|
519
|
+
onDelCancel() {
|
|
520
|
+
this.delConfirm.show = false
|
|
521
|
+
this.delConfirm.item = null
|
|
522
|
+
},
|
|
492
523
|
/** 检测节点是否被 schema 引用(属性绑定 $key 或事件代码 state.key) */
|
|
493
524
|
isNodeReferenced(path) {
|
|
494
525
|
if (!this.schema) return false
|
|
@@ -51,8 +51,6 @@
|
|
|
51
51
|
@click="onAddFromToolbar"
|
|
52
52
|
>+ 添加组件</button>
|
|
53
53
|
</div>
|
|
54
|
-
<p class="st__tip">右键节点可 复制 / 粘贴 / 副本 / 复制节点ID / 删除,右键卡片根还可 复制卡片code(英文名 / 文件夹名);容器节点(含卡片根)还可 添加子节点、非根节点还可 前面/后面插入节点,也可在此 设置属性 / 事件;按住 Ctrl / ⌘ 点击画布元素,可在本树中定位并展开该节点</p>
|
|
55
|
-
|
|
56
54
|
<ul class="st__list" @contextmenu.prevent="closeContextMenu">
|
|
57
55
|
<!-- 根节点 -->
|
|
58
56
|
<li class="st__node st__node--root">
|
|
@@ -152,6 +150,46 @@
|
|
|
152
150
|
<div class="st__ctx-sep"></div>
|
|
153
151
|
<button class="st__ctx-item st__ctx-item--danger" type="button" :disabled="!selectedNodeId || selectedNodeId === '_root'" @click="onCtxDelete">删除</button>
|
|
154
152
|
</div>
|
|
153
|
+
|
|
154
|
+
<!--
|
|
155
|
+
帮助悬浮按钮:固定在面板左下角(不占用固定布局区域),点击弹出使用说明气泡;
|
|
156
|
+
气泡内 ✕ 关闭,再次点击按钮亦关闭。说明文本即原固定展示在面板顶部的 st__tip 内容。
|
|
157
|
+
实现:零高度的 sticky 容器吸附在滚动视口底部(滚动在外层 .debug__body),
|
|
158
|
+
按钮与气泡以绝对定位挂靠其上,内容长短均保持悬浮于面板左下角。
|
|
159
|
+
-->
|
|
160
|
+
<div class="st__help-wrap">
|
|
161
|
+
<button
|
|
162
|
+
class="st__help"
|
|
163
|
+
type="button"
|
|
164
|
+
:class="{ 'st__help--open': helpOpen }"
|
|
165
|
+
title="使用说明"
|
|
166
|
+
:aria-expanded="helpOpen"
|
|
167
|
+
@click.stop="helpOpen = !helpOpen"
|
|
168
|
+
>
|
|
169
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
170
|
+
<circle cx="12" cy="12" r="9.2" />
|
|
171
|
+
<path d="M9.4 9.2a2.6 2.6 0 0 1 5.1.7c0 1.7-2.5 2.2-2.5 3.7" />
|
|
172
|
+
<line x1="12" y1="17" x2="12" y2="17.01" />
|
|
173
|
+
</svg>
|
|
174
|
+
</button>
|
|
175
|
+
<div v-if="helpOpen" class="st__help-pop" @click.stop>
|
|
176
|
+
<div class="st__help-pop-head">
|
|
177
|
+
<span class="st__help-pop-title">快捷键</span>
|
|
178
|
+
<button class="st__help-pop-close" type="button" title="关闭" @click="helpOpen = false">✕</button>
|
|
179
|
+
</div>
|
|
180
|
+
<!-- 快捷键清单:左列按键示意(kbd 芯片),右列一句话说明;与 App onKeydown 保持一致 -->
|
|
181
|
+
<ul class="st__help-pop-body st__keys">
|
|
182
|
+
<li v-for="s in shortcuts" :key="s.desc" class="st__keys-row">
|
|
183
|
+
<span class="st__keys-combo">
|
|
184
|
+
<template v-for="(key, ki) in s.keys" :key="key">
|
|
185
|
+
<kbd class="st__kbd">{{ key }}</kbd><span v-if="ki < s.keys.length - 1" class="st__kbd-plus">+</span>
|
|
186
|
+
</template>
|
|
187
|
+
</span>
|
|
188
|
+
<span class="st__keys-desc">{{ s.desc }}</span>
|
|
189
|
+
</li>
|
|
190
|
+
</ul>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
155
193
|
</div>
|
|
156
194
|
</template>
|
|
157
195
|
|
|
@@ -518,6 +556,19 @@ export default {
|
|
|
518
556
|
locateEnabled: false,
|
|
519
557
|
// 右键上下文菜单状态:visible 显隐、x/y 为光标视口坐标(position:fixed)、nodeId 记录触发节点、node 记录触发节点对象(供「添加节点」定位容器)
|
|
520
558
|
contextMenu: { visible: false, x: 0, y: 0, nodeId: '', node: null },
|
|
559
|
+
// 帮助气泡:左下角悬浮按钮弹出的使用说明,默认收起(不占用固定布局区域)
|
|
560
|
+
helpOpen: false,
|
|
561
|
+
// 快捷键清单(帮助气泡内容,与 App onKeydown 的实现保持一致)
|
|
562
|
+
shortcuts: [
|
|
563
|
+
{ keys: ['⌘/Ctrl', 'C'], desc: '复制选中节点' },
|
|
564
|
+
{ keys: ['⌘/Ctrl', 'V'], desc: '粘贴节点' },
|
|
565
|
+
{ keys: ['⌘/Ctrl', 'D'], desc: '创建节点副本' },
|
|
566
|
+
{ keys: ['⌘/Ctrl', 'Z'], desc: '撤销' },
|
|
567
|
+
{ keys: ['⌘/Ctrl', '⇧', 'Z'], desc: '重做' },
|
|
568
|
+
{ keys: ['Delete'], desc: '删除选中节点' },
|
|
569
|
+
{ keys: ['Esc'], desc: '取消选中' },
|
|
570
|
+
{ keys: ['⌘/Ctrl', '点击'], desc: '点击画布元素,在本树中定位该节点' },
|
|
571
|
+
],
|
|
521
572
|
}
|
|
522
573
|
},
|
|
523
574
|
computed: {
|