cardbuddy 0.6.0 → 0.8.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.
Files changed (36) hide show
  1. package/README.md +21 -0
  2. package/dist/preview-editor/assets/index-BQKzMPBq.js +79 -0
  3. package/dist/preview-editor/assets/index-D3zp47Dg.css +1 -0
  4. package/dist/preview-editor/index.html +2 -2
  5. package/dist/server-core/cardFiles.js +9 -4
  6. package/dist/server-core/codeBridge.js +359 -0
  7. package/dist/server-core/index.js +14 -2
  8. package/dist/server-core/sliceUpload.js +97 -0
  9. package/dist/standalone.js +18 -3
  10. package/dist/vendored/App.vue +268 -45
  11. package/dist/vendored/api.js +102 -32
  12. package/dist/vendored/assets/svgIcons/icons.svg +5 -0
  13. package/dist/vendored/components/ApiCallPanel.vue +127 -0
  14. package/dist/vendored/components/ApiPayloadTree.vue +34 -1
  15. package/dist/vendored/components/CardGallery.vue +2 -0
  16. package/dist/vendored/components/CardSelect.vue +6 -0
  17. package/dist/vendored/components/ComponentPalette.vue +45 -38
  18. package/dist/vendored/components/ConfirmDialog.vue +20 -5
  19. package/dist/vendored/components/DebugPanel.vue +168 -127
  20. package/dist/vendored/components/EventEditor.vue +27 -3
  21. package/dist/vendored/components/JsonTree.vue +35 -4
  22. package/dist/vendored/components/LogValue.vue +2 -1
  23. package/dist/vendored/components/PropControl.vue +6 -2
  24. package/dist/vendored/components/StructureTree.vue +53 -2
  25. package/dist/vendored/components/setters/SetCode.vue +111 -3
  26. package/dist/vendored/styles/app.css +507 -132
  27. package/dist/vendored/styles/card-gallery.css +23 -2
  28. package/dist/vendored/styles/card-select.css +15 -0
  29. package/dist/vendored/styles/dropdown.css +3 -0
  30. package/dist/vendored/styles/json-tree.css +1 -1
  31. package/dist/vendored/styles/node-inspector.css +176 -119
  32. package/dist/vendored/styles/setters.css +91 -2
  33. package/dist/vendored/styles/structure-tree.css +158 -15
  34. package/package.json +1 -1
  35. package/dist/preview-editor/assets/index-BYB0hngG.js +0 -72
  36. package/dist/preview-editor/assets/index-CpBZEQDy.css +0 -1
@@ -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 === 'api'">
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">
@@ -77,7 +43,9 @@
77
43
  <p v-if="rawError" class="debug__raw-err">{{ rawError }}</p>
78
44
  </div>
79
45
  <div class="debug__scope-foot">
80
- <button class="debug__copy" type="button" @click="saveRaw">应用</button>
46
+ <button class="debug__copy debug__copy--primary" type="button" @click="saveRaw">
47
+ <svg class="debug__copy-icn" viewBox="0 0 16 16" aria-hidden="true"><use href="#icon-check" /></svg>应用
48
+ </button>
81
49
  <button class="debug__copy" type="button" @click="exitRaw">取消</button>
82
50
  </div>
83
51
  </template>
@@ -88,11 +56,19 @@
88
56
  </div>
89
57
  <div class="debug__scope-foot">
90
58
  <span v-if="copied" class="debug__copied">已复制 ✓</span>
91
- <button class="debug__copy" type="button" @click="enterRaw">Raw</button>
92
- <button class="debug__copy" type="button" @click="copyScope">复制</button>
59
+ <button class="debug__copy" type="button" title="切换为 Raw JSON 编辑" @click="enterRaw">
60
+ <svg class="debug__copy-icn" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="16 18 22 12 16 6" /><polyline points="8 6 2 12 8 18" /></svg>Raw
61
+ </button>
62
+ <button class="debug__copy" type="button" @click="copyScope">
63
+ <svg class="debug__copy-icn" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-copy-sm" /></svg>复制
64
+ </button>
93
65
  <span class="debug__sep"></span>
94
- <button class="debug__copy debug__copy--primary" type="button" @click="onCommit">应用</button>
95
- <button class="debug__copy" type="button" @click="$emit('reset-import')" title="丢弃内存编辑,从磁盘重新载入(导入优先于编辑)">重置</button>
66
+ <button class="debug__copy debug__copy--primary" type="button" @click="onCommit">
67
+ <svg class="debug__copy-icn" viewBox="0 0 16 16" aria-hidden="true"><use href="#icon-check" /></svg>应用
68
+ </button>
69
+ <button class="debug__copy debug__copy--warn" type="button" @click="$emit('reset-import')" title="丢弃内存编辑,从磁盘重新载入(导入优先于编辑)">
70
+ <svg class="debug__copy-icn" viewBox="0 0 16 16" aria-hidden="true"><use href="#icon-reset" /></svg>重置
71
+ </button>
96
72
  </div>
97
73
  </template>
98
74
  </div>
@@ -104,7 +80,14 @@
104
80
 
105
81
  <template v-else-if="activeTab === 'meta'">
106
82
  <div class="debug__meta">
107
- <p v-if="!importMeta" class="debug__empty">该卡尚未经「同步远程」导入,无导入来源元数据。<br />本地新建卡 / 未同步卡均如此。</p>
83
+ <!-- 空状态占位:与 API 调用面板同款(虚线徽章 + 线性图标 + 文案 + 弱提示) -->
84
+ <div v-if="!importMeta" class="empty-state">
85
+ <span class="empty-state__badge" aria-hidden="true">
86
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#icon-cloud" /></svg>
87
+ </span>
88
+ <p class="debug__empty">暂无卡片来源</p>
89
+ <p class="empty-state__hint">该卡尚未经「同步远程」导入,无导入来源元数据;本地新建卡 / 未同步卡均如此</p>
90
+ </div>
108
91
  <dl v-else class="debug__meta-list">
109
92
  <div class="debug__meta-row">
110
93
  <dt>导入接口</dt>
@@ -198,25 +181,41 @@
198
181
  并持久化到 localStorage。展示 rt.state 实时 JSON(运行期交互态,只读),不污染绑定数据、
199
182
  不触发保存高亮、调试变更永不落盘。
200
183
  -->
184
+ <!--
185
+ 底部弹出面板(运行态 / 事件 / 绑定错误):Teleport 到 App 的 #app-workside-panels,
186
+ 宽度贯穿中右两栏、紧贴右缘(与共同底栏同一容器机制,贴近 VSCode 底部面板)。
187
+ defer:目标与 Teleport 同属本应用组件树,挂载时目标尚未插入 document,须等整个
188
+ 应用挂载完成后再解析(Vue 3.5+)。chrome-off 截图模式下禁用 Teleport,回落本面板内。
189
+ -->
190
+ <Teleport defer to="#app-workside-panels" :disabled="chromeOff">
201
191
  <section
202
192
  class="statepanel"
203
193
  ref="statePanel"
204
194
  v-show="statePanelOpen"
205
195
  >
206
- <div class="statepanel__head" @mousedown.prevent="startResize">
196
+ <!-- 顶缘拖拽条:与竖向分隔条同规格(命中 6px / 高亮 3px),仅分割边缘触发 -->
197
+ <div class="panelhead__edge" :class="{ 'is-dragging': panelDragging }" title="拖拽调整面板高度" @mousedown.prevent="startResize"></div>
198
+ <div class="statepanel__head">
207
199
  <span class="statepanel__title">运行态 <code>rt.state</code></span>
208
- <button class="statepanel__close" type="button" title="收起" @click="statePanelOpen = false">✕</button>
200
+ <button class="statepanel__close" type="button" title="收起" @click="statePanelOpen = false">
201
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="5" x2="19" y2="19" /><line x1="19" y1="5" x2="5" y2="19" /></svg>
202
+ </button>
209
203
  </div>
210
204
  <div class="statepanel__tree">
211
205
  <JsonTree :data="rt.state" :rt="rt" :editable="false" />
212
206
  </div>
213
207
  </section>
208
+ </Teleport>
214
209
 
215
210
  <!--
216
- 底部状态栏(仅右侧调试面板内、贴近 VSCode 布局):左侧「问题」指示器展示
217
- 绑定错误数,点击在面板底部弹出/收起错误面板;右侧展示当前卡片与卡片总数。
211
+ 底部状态栏:Teleport 到 App 的 #app-workside-bar,作为「调试面板 + 接口调用面板」
212
+ 中右两栏共同的底栏(贴近 VSCode 布局)。defer:目标与 Teleport 同属本应用组件树,
213
+ 挂载时目标尚未插入 document,须等整个应用挂载完成后再解析(Vue 3.5+)。
214
+ chrome-off 截图模式下禁用 Teleport,回落到本面板内渲染(随面板一起隐藏)。
215
+ 左侧「问题」指示器展示绑定错误数,点击弹出/收起错误面板;右侧展示当前卡片与卡片总数。
218
216
  -->
219
- <section class="statusbar">
217
+ <Teleport defer to="#app-workside-bar" :disabled="chromeOff">
218
+ <section class="statusbar">
220
219
  <button
221
220
  class="statusbar__item statusbar__item--prob"
222
221
  :class="{ 'is-open': statePanelOpen }"
@@ -231,7 +230,7 @@
231
230
  class="statusbar__item statusbar__item--prob"
232
231
  :class="{ 'is-error': bindingErrors.length, 'is-ok': !bindingErrors.length }"
233
232
  type="button"
234
- @click="bindingErrors.length && toggleErrorPanel('binding')"
233
+ @click="toggleErrorPanel('binding')"
235
234
  :title="bindingErrors.length ? '查看绑定错误' : '无绑定错误'"
236
235
  >
237
236
  <span class="statusbar__icon">{{ bindingErrors.length ? '⚠' : '✓' }}</span>
@@ -241,8 +240,7 @@
241
240
  class="statusbar__item statusbar__item--prob"
242
241
  :class="eventStatusClass"
243
242
  type="button"
244
- :disabled="!eventCount"
245
- @click="eventCount && toggleErrorPanel('event')"
243
+ @click="toggleErrorPanel('event')"
246
244
  :title="eventStatusTitle"
247
245
  >
248
246
  <span class="statusbar__icon">{{ eventCount ? (eventErrors.length ? '⚠' : '☰') : '✓' }}</span>
@@ -257,17 +255,40 @@
257
255
  <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
256
  <span>{{ devSvcLabel }}</span>
259
257
  </span>
260
- <span class="statusbar__spacer"></span>
261
- <span class="statusbar__item statusbar__item--right">{{ cardId }}</span>
262
- <span class="statusbar__item statusbar__item--right">{{ cardCount }} 张卡片</span>
263
- </section>
258
+ <span class="statusbar__spacer"></span>
259
+ <span class="statusbar__item statusbar__item--right">{{ cardId }}</span>
260
+ <span class="statusbar__item statusbar__item--right">{{ cardCount }} 张卡片</span>
261
+ </section>
262
+ </Teleport>
264
263
 
264
+ <Teleport defer to="#app-workside-panels" :disabled="chromeOff">
265
265
  <section class="errpanel" ref="errPanel" v-show="errorPanel">
266
- <div class="errpanel__head" @mousedown.prevent="startResize">
266
+ <!-- 顶缘拖拽条:与竖向分隔条同规格(命中 6px / 高亮 3px),仅分割边缘触发 -->
267
+ <div class="panelhead__edge" :class="{ 'is-dragging': panelDragging }" title="拖拽调整面板高度" @mousedown.prevent="startResize"></div>
268
+ <div class="errpanel__head">
267
269
  <span class="errpanel__title">{{ errorPanel === 'event' ? '事件面板' : '绑定错误' }}</span>
270
+ <!-- 事件面板:日志 / 错误 tab 并入头部单行(一次只看一类);绑定错误面板无 tab -->
271
+ <div v-if="errorPanel === 'event'" class="errpanel__tabs">
272
+ <button
273
+ type="button"
274
+ class="errpanel__tab"
275
+ :class="{ 'errpanel__tab--active': eventTab === 'log' }"
276
+ @click="eventTab = 'log'"
277
+ >
278
+ 日志<span class="errpanel__tab-count">{{ logs.length }}</span>
279
+ </button>
280
+ <button
281
+ type="button"
282
+ class="errpanel__tab"
283
+ :class="{ 'errpanel__tab--active': eventTab === 'err' }"
284
+ @click="eventTab = 'err'"
285
+ >
286
+ 错误<span class="errpanel__tab-count" :class="{ 'is-hit': eventErrors.length }">{{ eventErrors.length }}</span>
287
+ </button>
288
+ </div>
268
289
  <span class="errpanel__actions">
269
290
  <button
270
- v-if="errorPanel === 'event'"
291
+ v-if="errorPanel === 'event' && eventTab === 'log'"
271
292
  class="errpanel__iconbtn errpanel__clear"
272
293
  type="button"
273
294
  :disabled="!logs.length"
@@ -275,53 +296,91 @@
275
296
  @click="clearEventLogs"
276
297
  >
277
298
  <!-- 垃圾桶图标:清空日志 -->
278
- <svg width="15" height="15" viewBox="0 0 24 24" fill="none"
299
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none"
279
300
  stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#icon-trash" /></svg>
280
301
  </button>
281
- <button class="errpanel__iconbtn errpanel__close" type="button" title="关闭" @click="errorPanel = ''">✕</button>
302
+ <button class="errpanel__iconbtn errpanel__close" type="button" title="关闭" @click="errorPanel = ''">
303
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="5" x2="19" y2="19" /><line x1="19" y1="5" x2="5" y2="19" /></svg>
304
+ </button>
282
305
  </span>
283
306
  </div>
284
307
  <div class="errpanel__body">
285
- <!-- 事件面板:日志 + 错误 两区块,用于捕获主动日志与执行错误 -->
308
+ <!-- 事件面板:按当前 tab 只渲染日志或错误一类 -->
286
309
  <template v-if="errorPanel === 'event'">
287
- <div class="ep__sec-title">日志 ({{ logs.length }})</div>
288
- <p v-if="!logs.length" class="debug__empty">无日志 ✓</p>
289
- <ul v-else class="debug__list debug__list--log">
290
- <li v-for="(l, i) in logs" :key="'l' + i" class="log__item" :class="'log--' + l.level">
291
- <span class="log__tag" :class="'log__tag--' + l.level">{{ l.level }}</span>
292
- <div class="log__content">
293
- <template v-if="l.args && l.args.length">
294
- <LogValue v-for="(a, ai) in l.args" :key="ai" :value="a" />
295
- </template>
296
- <span v-else class="log__empty">—</span>
297
- </div>
298
- </li>
299
- </ul>
300
- <div class="ep__sec-title">错误 ({{ eventErrors.length }})</div>
301
- <p v-if="!eventErrors.length" class="debug__empty">无错误 ✓</p>
302
- <ul v-else class="debug__list">
303
- <li v-for="(e, i) in eventErrors" :key="'e' + i" class="debug__item debug__item--err">
304
- <span class="debug__kind">[{{ e.kind }}]</span> {{ e.msg }}
305
- </li>
306
- </ul>
310
+ <template v-if="eventTab === 'log'">
311
+ <!-- 空状态占位:与 API 调用面板同款(虚线徽章 + 线性图标 + 文案 + 弱提示) -->
312
+ <div v-if="!logs.length" class="empty-state">
313
+ <span class="empty-state__badge" aria-hidden="true">
314
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
315
+ <line x1="21" y1="6" x2="3" y2="6" />
316
+ <line x1="21" y1="12" x2="3" y2="12" />
317
+ <line x1="21" y1="18" x2="3" y2="18" />
318
+ </svg>
319
+ </span>
320
+ <p class="debug__empty">暂无日志</p>
321
+ <p class="empty-state__hint">卡片事件内 console.* 主动打印的日志会实时显示在这里</p>
322
+ </div>
323
+ <ul v-else class="debug__list debug__list--log">
324
+ <li v-for="(l, i) in logs" :key="'l' + i" class="log__item" :class="'log--' + l.level">
325
+ <span class="log__time">{{ fmtTime(l.t) }}</span>
326
+ <span class="log__tag" :class="'log__tag--' + l.level">{{ l.level }}</span>
327
+ <div class="log__content">
328
+ <template v-if="l.args && l.args.length">
329
+ <LogValue v-for="(a, ai) in l.args" :key="ai" :value="a" />
330
+ </template>
331
+ <span v-else class="log__empty">—</span>
332
+ </div>
333
+ </li>
334
+ </ul>
335
+ </template>
336
+ <template v-else>
337
+ <div v-if="!eventErrors.length" class="empty-state">
338
+ <span class="empty-state__badge" aria-hidden="true">
339
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
340
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
341
+ <polyline points="22 4 12 14.01 9 11.01" />
342
+ </svg>
343
+ </span>
344
+ <p class="debug__empty">无事件错误</p>
345
+ <p class="empty-state__hint">事件动作(customAction / 监听器)执行报错时会记录在这里</p>
346
+ </div>
347
+ <ul v-else class="debug__list">
348
+ <li v-for="(e, i) in eventErrors" :key="'e' + i" class="debug__item debug__item--err">
349
+ <span class="err__time">{{ fmtTime(e.t) }}</span>
350
+ <span class="debug__kind">{{ e.kind }}</span>
351
+ <span class="err__msg">{{ e.msg }}</span>
352
+ </li>
353
+ </ul>
354
+ </template>
307
355
  </template>
308
356
  <!-- 绑定错误面板:仅错误列表 -->
309
357
  <template v-else>
310
- <p v-if="!panelErrors.length" class="debug__empty">无错误 ✓</p>
358
+ <div v-if="!panelErrors.length" class="empty-state">
359
+ <span class="empty-state__badge" aria-hidden="true">
360
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
361
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
362
+ <polyline points="22 4 12 14.01 9 11.01" />
363
+ </svg>
364
+ </span>
365
+ <p class="debug__empty">无绑定错误</p>
366
+ <p class="empty-state__hint">绑定 / 表达式(bind、vshow、render、refresh)求值失败时会记录在这里</p>
367
+ </div>
311
368
  <ul v-else class="debug__list">
312
369
  <li v-for="(e, i) in panelErrors" :key="i" class="debug__item debug__item--err">
313
- <span class="debug__kind">[{{ e.kind }}]</span> {{ e.msg }}
370
+ <span class="err__time">{{ fmtTime(e.t) }}</span>
371
+ <span class="debug__kind">{{ e.kind }}</span>
372
+ <span class="err__msg">{{ e.msg }}</span>
314
373
  </li>
315
374
  </ul>
316
375
  </template>
317
376
  </div>
318
377
  </section>
378
+ </Teleport>
319
379
  </aside>
320
380
  </template>
321
381
 
322
382
  <script>
323
383
  import JsonTree from './JsonTree.vue'
324
- import ApiPayloadTree from './ApiPayloadTree.vue'
325
384
  import StructureTree from './StructureTree.vue'
326
385
  import NodeInspector from './NodeInspector.vue'
327
386
  import EventEditor from './EventEditor.vue'
@@ -355,7 +414,7 @@ function deepEqual(a, b) {
355
414
 
356
415
  export default {
357
416
  name: 'DebugPanel',
358
- components: { JsonTree, ApiPayloadTree, StructureTree, NodeInspector, EventEditor, LogValue },
417
+ components: { JsonTree, StructureTree, NodeInspector, EventEditor, LogValue },
359
418
  props: {
360
419
  rt: { type: Object, required: true },
361
420
  preview: { type: Object, default: null },
@@ -370,29 +429,32 @@ export default {
370
429
  // 拖拽移动成功令牌:App 在节点拖拽移动成功后自增,透传给结构树,
371
430
  // 触发「选中被拖节点 + 仅展开其祖先链、其余全部折叠」。
372
431
  moveCollapseToken: { type: Number, default: 0 },
373
- // 当前激活的调试面板 tab(接口调用/绑定数据/组件结构/卡片来源),由 App 统一管理,
432
+ // 当前激活的调试面板 tab(绑定数据/组件结构/卡片来源),由 App 统一管理,
374
433
  // 便于画布 Ctrl/⌘ 点击定位在「组件结构」tab 激活时才生效。
375
- activeTab: { type: String, default: 'api' },
434
+ activeTab: { type: String, default: 'scope' },
376
435
  // 本地开发服务连接状态:复用 App 中顶栏「连接」按钮同款状态/配色(devSvcClass/devSvcTitle/devSvcLabel),
377
436
  // 供底部状态栏展示「连接图标 + 状态文案」,与右上角按钮实时同步(颜色一致、状态一致)。
378
437
  devSvcClass: { type: String, default: 'app__btn--warn' },
379
438
  devSvcTitle: { type: String, default: '' },
380
439
  devSvcLabel: { type: String, default: '' },
440
+ // 纯净截图模式(?chrome=off):禁用状态栏 Teleport,使其留在本面板内随面板隐藏
441
+ chromeOff: { type: Boolean, default: false },
381
442
  },
382
443
  data() {
383
444
  return {
384
445
  copied: false,
385
446
  copyTimer: null,
386
- // 接口调用 tab:刚复制过数据的条目下标(用于图标变 ✓ 反馈),null=无
387
- copiedIndex: null,
388
- apiCopyTimer: null,
389
447
  // 底部错误面板模式:''(收起) | 'binding'(绑定错误) | 'event'(事件执行错误)
390
448
  errorPanel: '',
449
+ // 事件面板内部 tab:'log'(主动日志) | 'err'(执行错误),打开面板时按是否有错误自动定位
450
+ eventTab: 'log',
391
451
  // 运行态面板:默认收起;点击状态栏「运行态」指示器切换
392
452
  statePanelOpen: false,
393
453
  // 运行态 / 绑定错误 / 事件 三个底部面板互斥,共用同一高度变量(px);
394
454
  // 可被顶部抓手拖拽调整,并持久化到 localStorage(cardbuddy:debug-panel-height)
395
455
  panelH: this.loadPanelH(),
456
+ // 底部面板头部拖拽进行中:头部高亮与竖向分隔条 .is-dragging 同一语言
457
+ panelDragging: false,
396
458
  // 双击结构树节点后弹出的属性面板目标节点
397
459
  inspectingComp: null,
398
460
  // 换节点时强制重建面板(内部含 CodeJar 这类托管 DOM 的控件)
@@ -409,24 +471,11 @@ export default {
409
471
  tabs: [
410
472
  { key: 'scope', label: '绑定数据' },
411
473
  { key: 'structure', label: '组件结构' },
412
- { key: 'api', label: '接口调用' },
413
474
  { key: 'meta', label: '卡片来源' },
414
475
  ],
415
476
  }
416
477
  },
417
478
  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
479
  // 依赖 revision,使 setData 后数据绑定实时刷新
431
480
  scopeText() {
432
481
  void this.rt.revision
@@ -550,7 +599,11 @@ export default {
550
599
  // 再次点击同一指示器则收起;点击另一指示器则切换列表
551
600
  this.errorPanel = this.errorPanel === kind ? '' : kind
552
601
  // 与运行态面板互斥:点开错误/事件面板即关闭运行态
553
- if (this.errorPanel) this.statePanelOpen = false
602
+ if (this.errorPanel) {
603
+ this.statePanelOpen = false
604
+ // 事件面板展开时自动落到更值得关注的一侧:有执行错误看错误,否则看日志
605
+ if (kind === 'event') this.eventTab = this.eventErrors.length ? 'err' : 'log'
606
+ }
554
607
  },
555
608
  /**
556
609
  * 拖拽调整底部面板(运行态 / 绑定错误 / 事件)高度:挂在面板头部(已有 .statepanel__head /
@@ -562,6 +615,7 @@ export default {
562
615
  */
563
616
  startResize(e) {
564
617
  if (e.target.closest('button')) return
618
+ this.panelDragging = true
565
619
  const startY = e.clientY
566
620
  const startH = this.panelH
567
621
  const minH = 120
@@ -581,6 +635,7 @@ export default {
581
635
  document.removeEventListener('mousemove', onMove)
582
636
  document.removeEventListener('mouseup', onUp)
583
637
  this._resizeMove = this._resizeUp = null
638
+ this.panelDragging = false
584
639
  this.savePanelH()
585
640
  }
586
641
  this._resizeMove = onMove
@@ -595,6 +650,7 @@ export default {
595
650
  if (this._resizeMove) document.removeEventListener('mousemove', this._resizeMove)
596
651
  if (this._resizeUp) document.removeEventListener('mouseup', this._resizeUp)
597
652
  this._resizeMove = this._resizeUp = null
653
+ this.panelDragging = false
598
654
  document.body.style.cursor = ''
599
655
  document.body.style.userSelect = ''
600
656
  },
@@ -744,15 +800,17 @@ export default {
744
800
  onHoverNode(payload) {
745
801
  this.$emit('hover-node', payload)
746
802
  },
747
- /** 清空接口调用流水:调用运行时 clearApiCalls(清空数组 + 自增 apiRev),
748
- * 因 apiCalls computed 返回 slice() 新引用,触发实时重渲染 */
749
- clearApiLog() {
750
- this.rt.clearApiCalls()
751
- },
752
803
  /** 清空事件主动日志(事件面板「清空日志」按钮);错误不清除 */
753
804
  clearEventLogs() {
754
805
  this.rt.clearEventLogs()
755
806
  },
807
+ /** 日志 / 错误行首时间戳:HH:MM:SS 等宽对齐,单行多带一份定位信息 */
808
+ fmtTime(t) {
809
+ if (!t) return '--:--:--'
810
+ const d = new Date(t)
811
+ const p = (n) => String(n).padStart(2, '0')
812
+ return p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds())
813
+ },
756
814
  /** 拉取当前卡片的导入来源元数据(Phase E 来源 tab) */
757
815
  maybeFetchMeta() {
758
816
  if (this.activeTab === 'meta') this.fetchImportMeta()
@@ -784,23 +842,6 @@ export default {
784
842
  return iso
785
843
  }
786
844
  },
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
845
  },
805
846
  }
806
847
  </script>
@@ -11,7 +11,7 @@
11
11
  - 编辑即时(防抖)重建预览(emit apply),「应用」把改动暂存进内存工作副本(currentSchema),落盘由顶栏「保存到本地」负责;不再 emit write-back。
12
12
  -->
13
13
  <template>
14
- <div class="ee-overlay" @click.self="onClose">
14
+ <div class="ee-overlay">
15
15
  <div class="ee-panel">
16
16
  <div class="ee-head">
17
17
  <span class="ee-title">
@@ -29,7 +29,7 @@
29
29
  v-for="evt in eventModel.eventOptions"
30
30
  :key="evt.eventKey"
31
31
  class="ee-evt"
32
- :class="{ 'is-on': isOn(evt) }"
32
+ :class="{ 'is-on': isOn(evt), 'is-sel': activeKey === evt.eventKey }"
33
33
  @click="onSelectEvent(evt)"
34
34
  >
35
35
  <span class="ee-evt-name" :title="evt.eventDesc || evt.eventName || evt.eventKey">{{ evt.eventName || evt.eventKey }}</span>
@@ -43,6 +43,24 @@
43
43
  ><span class="ee-switch__dot"></span></button>
44
44
  </div>
45
45
  </div>
46
+ <!-- 帮助图标:与事件项左右靠边、垂直居中;点击弹出当前动作的使用说明 -->
47
+ <button
48
+ class="ee-help"
49
+ type="button"
50
+ :title="helpOpen ? '关闭使用说明' : '使用说明'"
51
+ @click.stop="helpOpen = !helpOpen"
52
+ >
53
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
54
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
55
+ <circle cx="12" cy="12" r="10"></circle>
56
+ <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
57
+ <line x1="12" y1="17" x2="12.01" y2="17"></line>
58
+ </svg>
59
+ </button>
60
+ <div v-if="helpOpen" class="ee-help__mask" @click="helpOpen = false"></div>
61
+ <div v-if="helpOpen" class="ee-help__pop" @click.stop>
62
+ <p class="ee-help__text">{{ helpText }}</p>
63
+ </div>
46
64
  </aside>
47
65
 
48
66
  <section class="ee-detail">
@@ -58,7 +76,6 @@
58
76
  @change="onActionType(activeRow, $event)"
59
77
  />
60
78
  </div>
61
- <p v-if="actionDesc(activeRow.actionType)" class="ee-evt-desc">{{ actionDesc(activeRow.actionType) }}</p>
62
79
  </div>
63
80
 
64
81
  <div class="ee-detail-body">
@@ -108,6 +125,7 @@ export default {
108
125
  return {
109
126
  eventModel: null,
110
127
  activeKey: '',
128
+ helpOpen: false,
111
129
  _t: null,
112
130
  _snapshot: null,
113
131
  _dirty: false,
@@ -127,6 +145,11 @@ export default {
127
145
  if (!this.eventModel || !this.activeKey) return null
128
146
  return this.eventModel.rowForEvent(this.activeKey) || null
129
147
  },
148
+ /** 帮助弹层文案:当前动作类型的使用说明;无选中动作时给通用引导 */
149
+ helpText() {
150
+ const d = this.activeRow ? this.actionDesc(this.activeRow.actionType) : ''
151
+ return d || '先在上方开启事件,再选择动作类型并填写参数;编辑即时预览,「应用」写入历史。'
152
+ },
130
153
  },
131
154
  watch: {
132
155
  comp: {
@@ -141,6 +164,7 @@ export default {
141
164
  const sn = this.schemaNode
142
165
  this.eventModel = sn ? reactive(new EventEditModel(sn)) : null
143
166
  this.activeKey = ''
167
+ this.helpOpen = false
144
168
  this._snapshot = sn ? this.snapshotEventActions(sn) : null
145
169
  this._dirty = false
146
170
  this._written = false