cardbuddy 0.7.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.
- package/README.md +21 -0
- package/dist/preview-editor/assets/index-BQKzMPBq.js +79 -0
- package/dist/preview-editor/assets/index-D3zp47Dg.css +1 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/server-core/codeBridge.js +359 -0
- package/dist/server-core/index.js +8 -2
- package/dist/standalone.js +18 -3
- package/dist/vendored/App.vue +17 -13
- package/dist/vendored/api.js +102 -32
- package/dist/vendored/components/ApiCallPanel.vue +13 -1
- package/dist/vendored/components/ApiPayloadTree.vue +34 -1
- package/dist/vendored/components/ComponentPalette.vue +45 -38
- package/dist/vendored/components/DebugPanel.vue +146 -39
- package/dist/vendored/components/EventEditor.vue +27 -3
- package/dist/vendored/components/LogValue.vue +2 -1
- package/dist/vendored/components/PropControl.vue +6 -2
- package/dist/vendored/components/setters/SetCode.vue +111 -3
- package/dist/vendored/styles/app.css +366 -82
- package/dist/vendored/styles/card-gallery.css +3 -2
- package/dist/vendored/styles/dropdown.css +3 -0
- package/dist/vendored/styles/node-inspector.css +176 -119
- package/dist/vendored/styles/setters.css +91 -2
- package/dist/vendored/styles/structure-tree.css +25 -12
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-BlscexDE.js +0 -79
- package/dist/preview-editor/assets/index-dlbSPb5W.css +0 -1
|
@@ -43,7 +43,9 @@
|
|
|
43
43
|
<p v-if="rawError" class="debug__raw-err">{{ rawError }}</p>
|
|
44
44
|
</div>
|
|
45
45
|
<div class="debug__scope-foot">
|
|
46
|
-
<button class="debug__copy" type="button" @click="saveRaw"
|
|
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>
|
|
47
49
|
<button class="debug__copy" type="button" @click="exitRaw">取消</button>
|
|
48
50
|
</div>
|
|
49
51
|
</template>
|
|
@@ -54,11 +56,19 @@
|
|
|
54
56
|
</div>
|
|
55
57
|
<div class="debug__scope-foot">
|
|
56
58
|
<span v-if="copied" class="debug__copied">已复制 ✓</span>
|
|
57
|
-
<button class="debug__copy" type="button" @click="enterRaw">
|
|
58
|
-
|
|
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>
|
|
59
65
|
<span class="debug__sep"></span>
|
|
60
|
-
<button class="debug__copy debug__copy--primary" type="button" @click="onCommit"
|
|
61
|
-
|
|
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>
|
|
62
72
|
</div>
|
|
63
73
|
</template>
|
|
64
74
|
</div>
|
|
@@ -70,7 +80,14 @@
|
|
|
70
80
|
|
|
71
81
|
<template v-else-if="activeTab === 'meta'">
|
|
72
82
|
<div class="debug__meta">
|
|
73
|
-
|
|
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>
|
|
74
91
|
<dl v-else class="debug__meta-list">
|
|
75
92
|
<div class="debug__meta-row">
|
|
76
93
|
<dt>导入接口</dt>
|
|
@@ -164,19 +181,31 @@
|
|
|
164
181
|
并持久化到 localStorage。展示 rt.state 实时 JSON(运行期交互态,只读),不污染绑定数据、
|
|
165
182
|
不触发保存高亮、调试变更永不落盘。
|
|
166
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">
|
|
167
191
|
<section
|
|
168
192
|
class="statepanel"
|
|
169
193
|
ref="statePanel"
|
|
170
194
|
v-show="statePanelOpen"
|
|
171
195
|
>
|
|
172
|
-
|
|
196
|
+
<!-- 顶缘拖拽条:与竖向分隔条同规格(命中 6px / 高亮 3px),仅分割边缘触发 -->
|
|
197
|
+
<div class="panelhead__edge" :class="{ 'is-dragging': panelDragging }" title="拖拽调整面板高度" @mousedown.prevent="startResize"></div>
|
|
198
|
+
<div class="statepanel__head">
|
|
173
199
|
<span class="statepanel__title">运行态 <code>rt.state</code></span>
|
|
174
|
-
<button class="statepanel__close" type="button" title="收起" @click="statePanelOpen = false"
|
|
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>
|
|
175
203
|
</div>
|
|
176
204
|
<div class="statepanel__tree">
|
|
177
205
|
<JsonTree :data="rt.state" :rt="rt" :editable="false" />
|
|
178
206
|
</div>
|
|
179
207
|
</section>
|
|
208
|
+
</Teleport>
|
|
180
209
|
|
|
181
210
|
<!--
|
|
182
211
|
底部状态栏:Teleport 到 App 的 #app-workside-bar,作为「调试面板 + 接口调用面板」
|
|
@@ -201,7 +230,7 @@
|
|
|
201
230
|
class="statusbar__item statusbar__item--prob"
|
|
202
231
|
:class="{ 'is-error': bindingErrors.length, 'is-ok': !bindingErrors.length }"
|
|
203
232
|
type="button"
|
|
204
|
-
@click="
|
|
233
|
+
@click="toggleErrorPanel('binding')"
|
|
205
234
|
:title="bindingErrors.length ? '查看绑定错误' : '无绑定错误'"
|
|
206
235
|
>
|
|
207
236
|
<span class="statusbar__icon">{{ bindingErrors.length ? '⚠' : '✓' }}</span>
|
|
@@ -211,8 +240,7 @@
|
|
|
211
240
|
class="statusbar__item statusbar__item--prob"
|
|
212
241
|
:class="eventStatusClass"
|
|
213
242
|
type="button"
|
|
214
|
-
|
|
215
|
-
@click="eventCount && toggleErrorPanel('event')"
|
|
243
|
+
@click="toggleErrorPanel('event')"
|
|
216
244
|
:title="eventStatusTitle"
|
|
217
245
|
>
|
|
218
246
|
<span class="statusbar__icon">{{ eventCount ? (eventErrors.length ? '⚠' : '☰') : '✓' }}</span>
|
|
@@ -233,12 +261,34 @@
|
|
|
233
261
|
</section>
|
|
234
262
|
</Teleport>
|
|
235
263
|
|
|
264
|
+
<Teleport defer to="#app-workside-panels" :disabled="chromeOff">
|
|
236
265
|
<section class="errpanel" ref="errPanel" v-show="errorPanel">
|
|
237
|
-
|
|
266
|
+
<!-- 顶缘拖拽条:与竖向分隔条同规格(命中 6px / 高亮 3px),仅分割边缘触发 -->
|
|
267
|
+
<div class="panelhead__edge" :class="{ 'is-dragging': panelDragging }" title="拖拽调整面板高度" @mousedown.prevent="startResize"></div>
|
|
268
|
+
<div class="errpanel__head">
|
|
238
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>
|
|
239
289
|
<span class="errpanel__actions">
|
|
240
290
|
<button
|
|
241
|
-
v-if="errorPanel === 'event'"
|
|
291
|
+
v-if="errorPanel === 'event' && eventTab === 'log'"
|
|
242
292
|
class="errpanel__iconbtn errpanel__clear"
|
|
243
293
|
type="button"
|
|
244
294
|
:disabled="!logs.length"
|
|
@@ -246,47 +296,86 @@
|
|
|
246
296
|
@click="clearEventLogs"
|
|
247
297
|
>
|
|
248
298
|
<!-- 垃圾桶图标:清空日志 -->
|
|
249
|
-
<svg width="
|
|
299
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
|
|
250
300
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#icon-trash" /></svg>
|
|
251
301
|
</button>
|
|
252
|
-
<button class="errpanel__iconbtn errpanel__close" type="button" title="关闭" @click="errorPanel = ''"
|
|
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>
|
|
253
305
|
</span>
|
|
254
306
|
</div>
|
|
255
307
|
<div class="errpanel__body">
|
|
256
|
-
<!--
|
|
308
|
+
<!-- 事件面板:按当前 tab 只渲染日志或错误一类 -->
|
|
257
309
|
<template v-if="errorPanel === 'event'">
|
|
258
|
-
<
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
<
|
|
266
|
-
</
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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>
|
|
278
355
|
</template>
|
|
279
356
|
<!-- 绑定错误面板:仅错误列表 -->
|
|
280
357
|
<template v-else>
|
|
281
|
-
<
|
|
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>
|
|
282
368
|
<ul v-else class="debug__list">
|
|
283
369
|
<li v-for="(e, i) in panelErrors" :key="i" class="debug__item debug__item--err">
|
|
284
|
-
<span class="
|
|
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>
|
|
285
373
|
</li>
|
|
286
374
|
</ul>
|
|
287
375
|
</template>
|
|
288
376
|
</div>
|
|
289
377
|
</section>
|
|
378
|
+
</Teleport>
|
|
290
379
|
</aside>
|
|
291
380
|
</template>
|
|
292
381
|
|
|
@@ -357,11 +446,15 @@ export default {
|
|
|
357
446
|
copyTimer: null,
|
|
358
447
|
// 底部错误面板模式:''(收起) | 'binding'(绑定错误) | 'event'(事件执行错误)
|
|
359
448
|
errorPanel: '',
|
|
449
|
+
// 事件面板内部 tab:'log'(主动日志) | 'err'(执行错误),打开面板时按是否有错误自动定位
|
|
450
|
+
eventTab: 'log',
|
|
360
451
|
// 运行态面板:默认收起;点击状态栏「运行态」指示器切换
|
|
361
452
|
statePanelOpen: false,
|
|
362
453
|
// 运行态 / 绑定错误 / 事件 三个底部面板互斥,共用同一高度变量(px);
|
|
363
454
|
// 可被顶部抓手拖拽调整,并持久化到 localStorage(cardbuddy:debug-panel-height)
|
|
364
455
|
panelH: this.loadPanelH(),
|
|
456
|
+
// 底部面板头部拖拽进行中:头部高亮与竖向分隔条 .is-dragging 同一语言
|
|
457
|
+
panelDragging: false,
|
|
365
458
|
// 双击结构树节点后弹出的属性面板目标节点
|
|
366
459
|
inspectingComp: null,
|
|
367
460
|
// 换节点时强制重建面板(内部含 CodeJar 这类托管 DOM 的控件)
|
|
@@ -506,7 +599,11 @@ export default {
|
|
|
506
599
|
// 再次点击同一指示器则收起;点击另一指示器则切换列表
|
|
507
600
|
this.errorPanel = this.errorPanel === kind ? '' : kind
|
|
508
601
|
// 与运行态面板互斥:点开错误/事件面板即关闭运行态
|
|
509
|
-
if (this.errorPanel)
|
|
602
|
+
if (this.errorPanel) {
|
|
603
|
+
this.statePanelOpen = false
|
|
604
|
+
// 事件面板展开时自动落到更值得关注的一侧:有执行错误看错误,否则看日志
|
|
605
|
+
if (kind === 'event') this.eventTab = this.eventErrors.length ? 'err' : 'log'
|
|
606
|
+
}
|
|
510
607
|
},
|
|
511
608
|
/**
|
|
512
609
|
* 拖拽调整底部面板(运行态 / 绑定错误 / 事件)高度:挂在面板头部(已有 .statepanel__head /
|
|
@@ -518,6 +615,7 @@ export default {
|
|
|
518
615
|
*/
|
|
519
616
|
startResize(e) {
|
|
520
617
|
if (e.target.closest('button')) return
|
|
618
|
+
this.panelDragging = true
|
|
521
619
|
const startY = e.clientY
|
|
522
620
|
const startH = this.panelH
|
|
523
621
|
const minH = 120
|
|
@@ -537,6 +635,7 @@ export default {
|
|
|
537
635
|
document.removeEventListener('mousemove', onMove)
|
|
538
636
|
document.removeEventListener('mouseup', onUp)
|
|
539
637
|
this._resizeMove = this._resizeUp = null
|
|
638
|
+
this.panelDragging = false
|
|
540
639
|
this.savePanelH()
|
|
541
640
|
}
|
|
542
641
|
this._resizeMove = onMove
|
|
@@ -551,6 +650,7 @@ export default {
|
|
|
551
650
|
if (this._resizeMove) document.removeEventListener('mousemove', this._resizeMove)
|
|
552
651
|
if (this._resizeUp) document.removeEventListener('mouseup', this._resizeUp)
|
|
553
652
|
this._resizeMove = this._resizeUp = null
|
|
653
|
+
this.panelDragging = false
|
|
554
654
|
document.body.style.cursor = ''
|
|
555
655
|
document.body.style.userSelect = ''
|
|
556
656
|
},
|
|
@@ -704,6 +804,13 @@ export default {
|
|
|
704
804
|
clearEventLogs() {
|
|
705
805
|
this.rt.clearEventLogs()
|
|
706
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
|
+
},
|
|
707
814
|
/** 拉取当前卡片的导入来源元数据(Phase E 来源 tab) */
|
|
708
815
|
maybeFetchMeta() {
|
|
709
816
|
if (this.activeTab === 'meta') this.fetchImportMeta()
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
- 编辑即时(防抖)重建预览(emit apply),「应用」把改动暂存进内存工作副本(currentSchema),落盘由顶栏「保存到本地」负责;不再 emit write-back。
|
|
12
12
|
-->
|
|
13
13
|
<template>
|
|
14
|
-
<div class="ee-overlay"
|
|
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
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
-->
|
|
8
8
|
<template>
|
|
9
9
|
<span v-if="!complex" class="log__val" :class="'jt__val--' + typeClass">{{ display }}</span>
|
|
10
|
-
|
|
10
|
+
<!-- 日志里的对象/数组参数:根节点默认一行摘要、点击展开,保证日志行密度 -->
|
|
11
|
+
<div v-else class="log__json"><ApiPayloadTree :value="value" :max-depth="14" root-collapsed /></div>
|
|
11
12
|
</template>
|
|
12
13
|
|
|
13
14
|
<script>
|
|
@@ -233,9 +233,13 @@ export default {
|
|
|
233
233
|
.pc-body { line-height: 0; }
|
|
234
234
|
.pc-srcs { display: flex; gap: 4px; line-height: 1.5; }
|
|
235
235
|
.pc-src {
|
|
236
|
-
flex: 1; font-size: 12px; padding:
|
|
236
|
+
flex: 1; height: 24px; font-size: 12px; padding: 0 10px; border: 1px solid var(--border);
|
|
237
237
|
background: transparent; color: var(--text-dim); border-radius: 6px; cursor: pointer;
|
|
238
|
+
transition: background 0.12s, color 0.12s, border-color 0.12s;
|
|
238
239
|
}
|
|
239
|
-
.pc-src
|
|
240
|
+
.pc-src:hover { background: var(--bg-hover); color: var(--text); }
|
|
241
|
+
/* 选中态与分段控件 .st-seg 同语言:浅底 + 同色文字 + 发丝描边,不做实心填充 */
|
|
242
|
+
.pc-src.is-active { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-border); }
|
|
243
|
+
.pc-src.is-active:hover { background: var(--accent-soft); color: var(--accent); }
|
|
240
244
|
.pc--readonly .pc-src { opacity: .6; cursor: not-allowed; }
|
|
241
245
|
</style>
|
|
@@ -12,9 +12,40 @@
|
|
|
12
12
|
-->
|
|
13
13
|
<template>
|
|
14
14
|
<div class="st-code">
|
|
15
|
-
<div
|
|
15
|
+
<div
|
|
16
|
+
ref="editor"
|
|
17
|
+
class="st-code__editor"
|
|
18
|
+
:class="{ 'is-disabled': disabled, 'is-bridged': !!bridge }"
|
|
19
|
+
></div>
|
|
20
|
+
<!-- 右上角语言标签:弱化的只读标识,不参与交互 -->
|
|
21
|
+
<span class="st-code__lang" aria-hidden="true">JS</span>
|
|
16
22
|
<div class="st-code__foot">
|
|
17
23
|
<span class="st-code__hint">{{ lines }} 行 · {{ chars }} 字符</span>
|
|
24
|
+
<!-- VSCode 代码桥:生成临时 js 用外部编辑器编辑,保存实时回写;断开即删临时文件 -->
|
|
25
|
+
<span class="st-code__foot-actions">
|
|
26
|
+
<span v-if="bridge" class="st-code__bridge-tag" :title="bridge.file">VSCode 同步中</span>
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
class="st-code__vscode"
|
|
30
|
+
:class="{ 'is-on': !!bridge }"
|
|
31
|
+
:title="
|
|
32
|
+
bridge
|
|
33
|
+
? '断开 VSCode 桥接并删除临时文件'
|
|
34
|
+
: '在 VSCode 中编辑:保存实时同步回编辑区,断开后清除临时文件'
|
|
35
|
+
"
|
|
36
|
+
:disabled="disabled"
|
|
37
|
+
@click="toggleBridge"
|
|
38
|
+
>
|
|
39
|
+
<!-- 图标左侧小字:未桥接时是动作说明;桥接后由状态徽标接替,避免两段文字挤一行 -->
|
|
40
|
+
<span v-if="!bridge" class="st-code__vscode-label">在编辑器中打开</span>
|
|
41
|
+
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
|
|
42
|
+
<path
|
|
43
|
+
fill="currentColor"
|
|
44
|
+
d="M23.15 2.587 18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z"
|
|
45
|
+
/>
|
|
46
|
+
</svg>
|
|
47
|
+
</button>
|
|
48
|
+
</span>
|
|
18
49
|
</div>
|
|
19
50
|
</div>
|
|
20
51
|
</template>
|
|
@@ -24,13 +55,20 @@ import { CodeJar } from 'codejar'
|
|
|
24
55
|
import Prism from 'prismjs'
|
|
25
56
|
import 'prismjs/components/prism-javascript'
|
|
26
57
|
import { setterProps, setterEmits } from './props.js'
|
|
58
|
+
import {
|
|
59
|
+
codeBridgeOpen,
|
|
60
|
+
codeBridgeFetch,
|
|
61
|
+
codeBridgeHeartbeat,
|
|
62
|
+
codeBridgeClose,
|
|
63
|
+
subscribeCodeBridge,
|
|
64
|
+
} from '../../api.js'
|
|
27
65
|
|
|
28
66
|
export default {
|
|
29
67
|
name: 'SetCode',
|
|
30
68
|
props: setterProps,
|
|
31
69
|
emits: setterEmits,
|
|
32
70
|
data() {
|
|
33
|
-
return { code: '' }
|
|
71
|
+
return { code: '', bridge: null }
|
|
34
72
|
},
|
|
35
73
|
computed: {
|
|
36
74
|
codeKey() {
|
|
@@ -49,6 +87,8 @@ export default {
|
|
|
49
87
|
},
|
|
50
88
|
beforeUnmount() {
|
|
51
89
|
if (this._t) clearTimeout(this._t)
|
|
90
|
+
// 组件卸载(抽屉关闭 / 切节点 / key 重建)即结束桥接会话,服务端删除临时文件
|
|
91
|
+
this.teardownBridge(true)
|
|
52
92
|
if (this._jar && this._jar.destroy) this._jar.destroy()
|
|
53
93
|
},
|
|
54
94
|
methods: {
|
|
@@ -75,7 +115,7 @@ export default {
|
|
|
75
115
|
this._jar.onUpdate((code) => this.onCode(code))
|
|
76
116
|
},
|
|
77
117
|
onCode(code) {
|
|
78
|
-
if (this.disabled) return
|
|
118
|
+
if (this.disabled || this.bridge) return
|
|
79
119
|
this.code = code
|
|
80
120
|
// 保留 compiled 等未知键,只覆写 codeKey
|
|
81
121
|
const base = this.value && typeof this.value === 'object' ? Object.assign({}, this.value) : {}
|
|
@@ -85,6 +125,74 @@ export default {
|
|
|
85
125
|
if (this._t) clearTimeout(this._t)
|
|
86
126
|
this._t = setTimeout(() => this.$emit('change'), 400)
|
|
87
127
|
},
|
|
128
|
+
/* ---------------- VSCode 代码桥 ---------------- */
|
|
129
|
+
toggleBridge() {
|
|
130
|
+
if (this.bridge) this.teardownBridge(true)
|
|
131
|
+
else this.setupBridge()
|
|
132
|
+
},
|
|
133
|
+
async setupBridge() {
|
|
134
|
+
if (this.disabled || this.bridge) return
|
|
135
|
+
const label = this.itemInfo && this.itemInfo.itemKey ? String(this.itemInfo.itemKey) : ''
|
|
136
|
+
try {
|
|
137
|
+
const info = await codeBridgeOpen(this.code, label)
|
|
138
|
+
this.bridge = { id: info.id, file: info.file }
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error('[VSCode 桥接建立失败]', e)
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
// 桥接期间 Web 编辑区只读:外部编辑器为唯一写入源,避免双向冲突
|
|
144
|
+
this.setEditorEditable(false)
|
|
145
|
+
this._hb = setInterval(() => {
|
|
146
|
+
if (!this.bridge) return
|
|
147
|
+
codeBridgeHeartbeat(this.bridge.id).catch(() => {})
|
|
148
|
+
}, 5000)
|
|
149
|
+
this._bridgeUnsub = subscribeCodeBridge((payload) => {
|
|
150
|
+
if (!this.bridge || !payload || payload.id !== this.bridge.id) return
|
|
151
|
+
// code-bridge-closed 带 reason(ttl / client):服务端已清理,本地同步收 UI
|
|
152
|
+
if (payload.reason) return this.teardownBridge(false)
|
|
153
|
+
this.pullBridgeCode()
|
|
154
|
+
})
|
|
155
|
+
},
|
|
156
|
+
async pullBridgeCode() {
|
|
157
|
+
if (!this.bridge) return
|
|
158
|
+
let r
|
|
159
|
+
try {
|
|
160
|
+
r = await codeBridgeFetch(this.bridge.id)
|
|
161
|
+
} catch (e) {
|
|
162
|
+
return // 会话已失效:等 closed 事件或 TTL 收尾
|
|
163
|
+
}
|
|
164
|
+
const code = String(r.code == null ? '' : r.code)
|
|
165
|
+
if (code === this.code) return
|
|
166
|
+
this.applyExternalCode(code)
|
|
167
|
+
},
|
|
168
|
+
applyExternalCode(code) {
|
|
169
|
+
this.code = code
|
|
170
|
+
// callOnUpdate=false:不触发 onUpdate,切断「回写 → input → 再写临时文件」回声环
|
|
171
|
+
if (this._jar) this._jar.updateCode(code, false)
|
|
172
|
+
const base = this.value && typeof this.value === 'object' ? Object.assign({}, this.value) : {}
|
|
173
|
+
base[this.codeKey] = code
|
|
174
|
+
this.$emit('input', base)
|
|
175
|
+
if (this._t) clearTimeout(this._t)
|
|
176
|
+
this._t = setTimeout(() => this.$emit('change'), 400)
|
|
177
|
+
},
|
|
178
|
+
teardownBridge(notifyServer) {
|
|
179
|
+
const b = this.bridge
|
|
180
|
+
this.bridge = null
|
|
181
|
+
if (this._hb) {
|
|
182
|
+
clearInterval(this._hb)
|
|
183
|
+
this._hb = null
|
|
184
|
+
}
|
|
185
|
+
if (this._bridgeUnsub) {
|
|
186
|
+
this._bridgeUnsub()
|
|
187
|
+
this._bridgeUnsub = null
|
|
188
|
+
}
|
|
189
|
+
this.setEditorEditable(true)
|
|
190
|
+
if (b && notifyServer) codeBridgeClose(b.id).catch(() => {})
|
|
191
|
+
},
|
|
192
|
+
setEditorEditable(on) {
|
|
193
|
+
const el = this.$refs.editor
|
|
194
|
+
if (el) el.setAttribute('contenteditable', on ? 'true' : 'false')
|
|
195
|
+
},
|
|
88
196
|
},
|
|
89
197
|
}
|
|
90
198
|
</script>
|