cardbuddy 0.6.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 +211 -0
- package/dist/cli.js +76 -0
- package/dist/config.js +63 -0
- package/dist/core.js +2 -0
- package/dist/preview-editor/assets/apple-touch-icon-BQcES9QK.png +0 -0
- package/dist/preview-editor/assets/default-image-wzZYQ86K.png +0 -0
- package/dist/preview-editor/assets/favicon-16-Cg6Nrosk.png +0 -0
- package/dist/preview-editor/assets/favicon-32-B_1744sp.png +0 -0
- package/dist/preview-editor/assets/index-BYB0hngG.js +72 -0
- package/dist/preview-editor/assets/index-CpBZEQDy.css +1 -0
- package/dist/preview-editor/assets/site-icon-CFMif8z4.png +0 -0
- package/dist/preview-editor/assets/svg-symbols-B2exBrAr.svg +118 -0
- package/dist/preview-editor/index.html +17 -0
- package/dist/server-core/_vendor/finmall-env.mjs +96 -0
- package/dist/server-core/assetProxy.js +154 -0
- package/dist/server-core/bbox.js +153 -0
- package/dist/server-core/browser.js +108 -0
- package/dist/server-core/buildDataFromSchema.js +50 -0
- package/dist/server-core/cardFiles.js +188 -0
- package/dist/server-core/cardHistory.js +156 -0
- package/dist/server-core/cardImage.js +85 -0
- package/dist/server-core/cardImportMeta.js +139 -0
- package/dist/server-core/cardListCache.js +68 -0
- package/dist/server-core/cardOpenFolder.js +135 -0
- package/dist/server-core/cardSave.js +127 -0
- package/dist/server-core/cardSidecar.js +72 -0
- package/dist/server-core/cardSync.js +259 -0
- package/dist/server-core/cardValidate.js +134 -0
- package/dist/server-core/index.js +82 -0
- package/dist/server-core/logger.js +241 -0
- package/dist/server-core/nodeModels.js +52 -0
- package/dist/server-core/renderStatus.js +207 -0
- package/dist/server-core/screenshot.js +123 -0
- package/dist/server-core/sliceStatic.js +44 -0
- package/dist/standalone.js +447 -0
- package/dist/vendored/App.vue +2330 -0
- package/dist/vendored/api.js +142 -0
- package/dist/vendored/assets/apple-touch-icon.png +0 -0
- package/dist/vendored/assets/default-image.png +0 -0
- package/dist/vendored/assets/favicon-16.png +0 -0
- package/dist/vendored/assets/favicon-32.png +0 -0
- package/dist/vendored/assets/logo.png +0 -0
- package/dist/vendored/assets/site-icon.png +0 -0
- package/dist/vendored/assets/svg-symbols.svg +118 -0
- package/dist/vendored/assets/svgIcons/icons.svg +54 -0
- package/dist/vendored/assets/svgIcons/index.js +39 -0
- package/dist/vendored/components/AbsoluteLayout.vue +42 -0
- package/dist/vendored/components/AlertCardInstance.vue +58 -0
- package/dist/vendored/components/AlertCardLayer.vue +59 -0
- package/dist/vendored/components/ApiPayloadTree.vue +168 -0
- package/dist/vendored/components/AppToast.vue +37 -0
- package/dist/vendored/components/Button.vue +37 -0
- package/dist/vendored/components/Canvas.vue +65 -0
- package/dist/vendored/components/CardGallery.vue +243 -0
- package/dist/vendored/components/CardPreview.vue +72 -0
- package/dist/vendored/components/CardSelect.vue +200 -0
- package/dist/vendored/components/CheckGroup.vue +131 -0
- package/dist/vendored/components/Column.vue +25 -0
- package/dist/vendored/components/ComponentPalette.vue +294 -0
- package/dist/vendored/components/ConfirmDialog.vue +39 -0
- package/dist/vendored/components/CustomPlaceholder.vue +33 -0
- package/dist/vendored/components/DatePickerOverlay.vue +345 -0
- package/dist/vendored/components/DebugPanel.vue +806 -0
- package/dist/vendored/components/DeviceOverlay.vue +70 -0
- package/dist/vendored/components/Divider.vue +22 -0
- package/dist/vendored/components/Dropdown.vue +191 -0
- package/dist/vendored/components/EventEditor.vue +247 -0
- package/dist/vendored/components/GridLayout.vue +67 -0
- package/dist/vendored/components/HoverGuides.vue +122 -0
- package/dist/vendored/components/Icon.vue +34 -0
- package/dist/vendored/components/Image.vue +34 -0
- package/dist/vendored/components/Input.vue +90 -0
- package/dist/vendored/components/JsonTree.vue +516 -0
- package/dist/vendored/components/ListLayout.vue +68 -0
- package/dist/vendored/components/LogValue.vue +51 -0
- package/dist/vendored/components/Map.vue +110 -0
- package/dist/vendored/components/MultiColumn.vue +49 -0
- package/dist/vendored/components/MultiText.vue +58 -0
- package/dist/vendored/components/NodeInspector.vue +295 -0
- package/dist/vendored/components/PickerOverlay.vue +131 -0
- package/dist/vendored/components/ProgressSlider.vue +167 -0
- package/dist/vendored/components/PropControl.vue +241 -0
- package/dist/vendored/components/PropTree.vue +128 -0
- package/dist/vendored/components/RadioButton.vue +74 -0
- package/dist/vendored/components/RemotePreview.vue +171 -0
- package/dist/vendored/components/RenderTree.vue +437 -0
- package/dist/vendored/components/Root.vue +30 -0
- package/dist/vendored/components/Row.vue +25 -0
- package/dist/vendored/components/SelectionOverlay.vue +105 -0
- package/dist/vendored/components/StructureTree.vue +949 -0
- package/dist/vendored/components/SvgCode.vue +19 -0
- package/dist/vendored/components/SvgSymbols.vue +30 -0
- package/dist/vendored/components/Switch.vue +38 -0
- package/dist/vendored/components/Text.vue +32 -0
- package/dist/vendored/components/Video.vue +70 -0
- package/dist/vendored/components/ViewContainer.vue +52 -0
- package/dist/vendored/components/base.js +12 -0
- package/dist/vendored/components/icons.js +47 -0
- package/dist/vendored/components/index.js +58 -0
- package/dist/vendored/components/setters/SetBgPosition.vue +64 -0
- package/dist/vendored/components/setters/SetBgSize.vue +90 -0
- package/dist/vendored/components/setters/SetCode.vue +90 -0
- package/dist/vendored/components/setters/SetColor.vue +244 -0
- package/dist/vendored/components/setters/SetCustomSelect.vue +58 -0
- package/dist/vendored/components/setters/SetExpr.vue +107 -0
- package/dist/vendored/components/setters/SetGradient.vue +125 -0
- package/dist/vendored/components/setters/SetGrid9.vue +68 -0
- package/dist/vendored/components/setters/SetIconSelect.vue +67 -0
- package/dist/vendored/components/setters/SetIfForm.vue +61 -0
- package/dist/vendored/components/setters/SetImage.vue +43 -0
- package/dist/vendored/components/setters/SetList.vue +139 -0
- package/dist/vendored/components/setters/SetNumber.vue +118 -0
- package/dist/vendored/components/setters/SetNumberStyle.vue +113 -0
- package/dist/vendored/components/setters/SetPadding.vue +77 -0
- package/dist/vendored/components/setters/SetSegment.vue +160 -0
- package/dist/vendored/components/setters/SetSelect.vue +51 -0
- package/dist/vendored/components/setters/SetSwitch.vue +49 -0
- package/dist/vendored/components/setters/SetText.vue +52 -0
- package/dist/vendored/components/setters/props.js +23 -0
- package/dist/vendored/fidelity/baseline-messageOverview.json +87 -0
- package/dist/vendored/fidelity/calibration.json +29 -0
- package/dist/vendored/fidelity/fidelity-messageOverview-after.html +114 -0
- package/dist/vendored/fidelity/fidelity-messageOverview-after.json +272 -0
- package/dist/vendored/fidelity/fidelity-messageOverview.html +185 -0
- package/dist/vendored/fidelity/fidelity-messageOverview.json +336 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v2.html +262 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v2.json +502 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v3.html +201 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v3.json +417 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_visual.html +125 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_visual.json +338 -0
- package/dist/vendored/index.html +16 -0
- package/dist/vendored/main.js +36 -0
- package/dist/vendored/meta/action-meta.json +141 -0
- package/dist/vendored/meta/component-meta.json +2714 -0
- package/dist/vendored/meta/directive-meta.json +40 -0
- package/dist/vendored/meta/style-meta.json +1086 -0
- package/dist/vendored/runtime/StyleParser.js +179 -0
- package/dist/vendored/runtime/adapter.js +166 -0
- package/dist/vendored/runtime/alert-card.js +125 -0
- package/dist/vendored/runtime/app-toast.js +33 -0
- package/dist/vendored/runtime/assets.js +16 -0
- package/dist/vendored/runtime/canvas-context.js +141 -0
- package/dist/vendored/runtime/card-runtime.js +289 -0
- package/dist/vendored/runtime/clipboard-node.js +83 -0
- package/dist/vendored/runtime/clipboard.js +29 -0
- package/dist/vendored/runtime/evaluate.js +152 -0
- package/dist/vendored/runtime/event-model.js +300 -0
- package/dist/vendored/runtime/hoverGuide.js +31 -0
- package/dist/vendored/runtime/jsapi-mock.js +256 -0
- package/dist/vendored/runtime/node-model.js +217 -0
- package/dist/vendored/runtime/property.js +364 -0
- package/dist/vendored/runtime/selectionOverlay.js +53 -0
- package/dist/vendored/runtime/toPreview.js +648 -0
- package/dist/vendored/standalone-main.js +36 -0
- package/dist/vendored/standalone.html +12 -0
- package/dist/vendored/styles/app.css +2565 -0
- package/dist/vendored/styles/card-gallery.css +234 -0
- package/dist/vendored/styles/card-select.css +281 -0
- package/dist/vendored/styles/components.css +383 -0
- package/dist/vendored/styles/dropdown.css +337 -0
- package/dist/vendored/styles/json-tree.css +308 -0
- package/dist/vendored/styles/node-inspector.css +1003 -0
- package/dist/vendored/styles/setters.css +962 -0
- package/dist/vendored/styles/standalone.css +27 -0
- package/dist/vendored/styles/structure-tree.css +320 -0
- package/package.json +42 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 卡片交互运行时。
|
|
3
|
+
*
|
|
4
|
+
* 通过 Vue 的 provide/inject 下发给每个 RenderTree 节点(inject key: `cardRuntime`)。
|
|
5
|
+
*
|
|
6
|
+
* 与官方渲染模块 2130 里 `h` 给出的上下文对齐:customAction 脚本里的 `this`
|
|
7
|
+
* 拥有 `state` / `data` / `setData` / `setRootData` / `addEventListener` /
|
|
8
|
+
* `postMessage`,并挂载 `window.ynet.callJSAPI` 桩。
|
|
9
|
+
*
|
|
10
|
+
* 关键差异(用户选择了「支持交互」):官方的 `setData` 是空函数,这里**真实生效**——
|
|
11
|
+
* 它把补丁合并进当前数据绑定,并自增 `revision` 强制整树重渲染。因此脚本里
|
|
12
|
+
* `this.setData({ activeTab: '2' })` 这类操作会真实改变后续渲染结果。
|
|
13
|
+
*/
|
|
14
|
+
import { reactive, watch, nextTick } from 'vue'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 由 data 生成 state 的初始种子:浅拷贝顶层自有可枚举键(基础类型与引用)。
|
|
18
|
+
* 仅用于让「初始 state」镜像 data,避免脚本在赋值前读取 state.x 得到 undefined;
|
|
19
|
+
* 运行期 setData / `this.state.x = ...` 只改 stateScope,不会回写 rootScope。
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* state 需要「结构性」地与 rootScope 彻底分离:不仅顶层键独立,嵌套对象/数组
|
|
23
|
+
* 也必须是各自的副本。否则(如 assetOverview 的 onReady 里 `this.state.listData.forEach(e =>
|
|
24
|
+
* e.ratio = ...)`)会顺着共享引用直接改到 rootScope 的持久化数据,进而被保存脏标记
|
|
25
|
+
* 误判为「已编辑」。data.json 是纯 JSON 数据,深拷贝安全。
|
|
26
|
+
*/
|
|
27
|
+
function deepCloneState(v) {
|
|
28
|
+
if (Array.isArray(v)) return v.map(deepCloneState)
|
|
29
|
+
if (v && typeof v === 'object') {
|
|
30
|
+
const out = {}
|
|
31
|
+
for (const k of Object.keys(v)) out[k] = deepCloneState(v[k])
|
|
32
|
+
return out
|
|
33
|
+
}
|
|
34
|
+
return v
|
|
35
|
+
}
|
|
36
|
+
function seedState(raw) {
|
|
37
|
+
return deepCloneState(raw || {})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 把一个日志实参格式化为可读字符串:字符串原样、基础类型 String()、
|
|
42
|
+
* 对象/数组 JSON 序列化(失败回退 String)。供事件面板展示 console.* 输出。
|
|
43
|
+
*/
|
|
44
|
+
function formatLogArg(a) {
|
|
45
|
+
if (a === null) return 'null'
|
|
46
|
+
if (a === undefined) return 'undefined'
|
|
47
|
+
const t = typeof a
|
|
48
|
+
if (t === 'string') return a
|
|
49
|
+
if (t === 'number' || t === 'boolean' || t === 'bigint') return String(a)
|
|
50
|
+
if (t === 'function') return '[Function ' + (a.name || 'anonymous') + ']'
|
|
51
|
+
try {
|
|
52
|
+
return JSON.stringify(a)
|
|
53
|
+
} catch {
|
|
54
|
+
return String(a)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 卡片事件执行产生的错误类型(事件代码 / 监听器)。
|
|
60
|
+
* 与绑定类错误(bind / vshow / render / refresh)的区别:
|
|
61
|
+
* - 绑定错误随每次渲染重新收集(渲染期产生,重渲染即重新计算);
|
|
62
|
+
* - 事件错误由用户交互触发,必须跨渲染保留,直到切换卡片才清空。
|
|
63
|
+
*/
|
|
64
|
+
// 事件执行错误:用户交互/用户触发操作产生的错误,跨渲染保留(直到切卡才清空)。
|
|
65
|
+
// 包括卡片内 customAction/listener,以及预览顶栏的保存/同步/刷新等操作失败。
|
|
66
|
+
export const EVENT_ERROR_KINDS = ['customAction', 'listener', 'syncRemote', 'saveCard', 'refresh']
|
|
67
|
+
|
|
68
|
+
export function createCardRuntime(rootScopeRaw, adapter) {
|
|
69
|
+
// rootScope:数据绑定(持久化),做成响应式,applyVars 取值时能建立细粒度依赖
|
|
70
|
+
let rootScope = reactive(rootScopeRaw || {})
|
|
71
|
+
// state:组件运行期本地状态,与 rootScope 完全分离。
|
|
72
|
+
// 关键修复:此前 this.state 被别名成 rootScope,导致运行时交互(如点击触发
|
|
73
|
+
// `this.state.x = ...`)直接污染持久化数据绑定,进而被 save 脏标记误判为「已编辑」。
|
|
74
|
+
// 现在 state 是独立对象,运行时状态写入只影响 state,绝不进入 rootScope/落盘;
|
|
75
|
+
// 渲染层经 makeRenderScope 按「state 优先于 data」合并,绑定仍可读到运行时状态。
|
|
76
|
+
let stateScope = reactive(seedState(rootScopeRaw))
|
|
77
|
+
// revision:强制整树重渲染的版本号(setData / 切换卡片时自增)
|
|
78
|
+
const revision = reactive({ value: 0 })
|
|
79
|
+
// errorRev:错误计数版本号。errors 本身是非响应式数组,调试面板状态栏
|
|
80
|
+
// 的 computed 依赖此值,保证错误变化后指示器刷新。注意:自增一律延迟到
|
|
81
|
+
// nextTick(渲染期之外的微任务),因为 pushError 可能在渲染函数内被调用
|
|
82
|
+
// (如 vshow 求值失败),若在渲染中读写 observable 会让 RenderTree 订阅
|
|
83
|
+
// errorRev 并自我触发,形成无限更新循环。
|
|
84
|
+
const errorRev = reactive({ value: 0 })
|
|
85
|
+
let bumpScheduled = false
|
|
86
|
+
// apiRev:接口调用流水版本号。apiCalls 本身是非响应式数组,调试面板
|
|
87
|
+
// 「接口调用」tab 的 computed 依赖此值,保证新调用/清空后实时刷新。
|
|
88
|
+
// pushApiCall 只在事件处理器中调用(postMessage / callJSAPI),不在
|
|
89
|
+
// 渲染函数内,直接自增安全,无需 nextTick 延迟。
|
|
90
|
+
const apiRev = reactive({ value: 0 })
|
|
91
|
+
// logRev:事件代码主动日志(console.*)流水版本号。与 apiRev 同理,仅在事件
|
|
92
|
+
// 处理器(用户交互/生命周期动作)中被 pushLog 自增,不在渲染函数内,直接自增安全。
|
|
93
|
+
const logRev = reactive({ value: 0 })
|
|
94
|
+
// 绑定错误的渲染期收集缓冲区:闭包内普通数组,不进入 Vue 响应式系统,
|
|
95
|
+
// 渲染中读写它不会建立任何依赖。渲染结束后由 commitBindingPass 一次性
|
|
96
|
+
// 替换进 rt.errors(绑定错误列表)。
|
|
97
|
+
let bindingBuffer = []
|
|
98
|
+
|
|
99
|
+
const rt = {
|
|
100
|
+
adapter,
|
|
101
|
+
/** 当前数据绑定(响应式,持久化) */
|
|
102
|
+
get rootScope() {
|
|
103
|
+
return rootScope
|
|
104
|
+
},
|
|
105
|
+
/** 组件运行期本地状态(响应式,独立于 rootScope,不落盘) */
|
|
106
|
+
get state() {
|
|
107
|
+
return stateScope
|
|
108
|
+
},
|
|
109
|
+
/**
|
|
110
|
+
* 切换卡片 / 绑定数据整体替换:**原地改写**现有响应式 rootScope,保持引用不变,
|
|
111
|
+
* 使 App.vue 的 `:scope="rt.rootScope"` 绑定、所有 `rt.rootScope` 读取方与画布
|
|
112
|
+
* 实时看到新数据。
|
|
113
|
+
*
|
|
114
|
+
* 早期实现把 rootScope 重赋成一个「新 reactive 对象」,导致 RenderTree 的 scope
|
|
115
|
+
* prop(在 App.vue 渲染时绑定、且 App.vue 不追踪该 getter 的内部闭包变量)仍指向
|
|
116
|
+
* 旧对象——于是「绑定数据 → 应用」保存后画布不刷新(典型复现:DebugPanel 绑定
|
|
117
|
+
* 数据 Raw 模式点「应用」)。见 App.vue 的 `:scope` 绑定与设计注释(rootScope
|
|
118
|
+
* 可经 setRootScope 整体替换而引用不变)。
|
|
119
|
+
*/
|
|
120
|
+
setRootScope(raw) {
|
|
121
|
+
const src = raw || {}
|
|
122
|
+
// 清空并就地写入,保留 rootScope 这个响应式引用
|
|
123
|
+
for (const k of Object.keys(rootScope)) delete rootScope[k]
|
|
124
|
+
Object.assign(rootScope, src)
|
|
125
|
+
// state 重置为新基线的深拷贝(同样保持 stateScope 引用不变)
|
|
126
|
+
const seeded = seedState(src)
|
|
127
|
+
for (const k of Object.keys(stateScope)) delete stateScope[k]
|
|
128
|
+
Object.assign(stateScope, seeded)
|
|
129
|
+
this.markDirty()
|
|
130
|
+
},
|
|
131
|
+
/**
|
|
132
|
+
* 把运行态 state 重置为「当前绑定数据」的深拷贝。
|
|
133
|
+
* 用于:绑定数据(rootScope)发生变更后,让调试工作态回到最新基线——
|
|
134
|
+
* 调试交互只写 state、绝不触碰 rootScope,所以本重置不会清掉持久化数据,
|
|
135
|
+
* 只会把「运行态面板」里的临时调试值刷新为最新的绑定数据。
|
|
136
|
+
* 同样原地改写 stateScope,保持引用稳定(rt.state 经 getter 实时读取)。
|
|
137
|
+
*/
|
|
138
|
+
reseedState() {
|
|
139
|
+
const seeded = seedState(rootScope)
|
|
140
|
+
for (const k of Object.keys(stateScope)) delete stateScope[k]
|
|
141
|
+
Object.assign(stateScope, seeded)
|
|
142
|
+
this.markDirty()
|
|
143
|
+
},
|
|
144
|
+
/** 重渲染版本号(RenderTree 在 render 中读取它建立依赖) */
|
|
145
|
+
get revision() {
|
|
146
|
+
return revision.value
|
|
147
|
+
},
|
|
148
|
+
/** 错误计数版本号(调试面板状态栏读取它建立响应式依赖) */
|
|
149
|
+
get errorRev() {
|
|
150
|
+
return errorRev.value
|
|
151
|
+
},
|
|
152
|
+
/** 接口调用流水版本号(调试面板「接口调用」tab 读取它建立响应式依赖) */
|
|
153
|
+
get apiRev() {
|
|
154
|
+
return apiRev.value
|
|
155
|
+
},
|
|
156
|
+
/** 事件主动日志(console.*)流水版本号(事件面板读取它建立响应式依赖) */
|
|
157
|
+
get logRev() {
|
|
158
|
+
return logRev.value
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// 以下为非响应式簿记数据,调试面板直接读取
|
|
162
|
+
/** 绑定类错误(bind / vshow / render / refresh),每次渲染重新收集 */
|
|
163
|
+
errors: [],
|
|
164
|
+
/** 事件执行错误(customAction / listener),跨渲染保留 */
|
|
165
|
+
eventErrors: [],
|
|
166
|
+
/** 事件代码主动日志(console.log/warn/error 等),跨渲染保留,切卡清空 */
|
|
167
|
+
eventLogs: [],
|
|
168
|
+
apiCalls: [],
|
|
169
|
+
listeners: {},
|
|
170
|
+
markDirty() {
|
|
171
|
+
revision.value++
|
|
172
|
+
},
|
|
173
|
+
clearErrors() {
|
|
174
|
+
this.errors.length = 0
|
|
175
|
+
this.eventErrors.length = 0
|
|
176
|
+
this.eventLogs.length = 0
|
|
177
|
+
logRev.value++
|
|
178
|
+
this.scheduleErrorBump()
|
|
179
|
+
},
|
|
180
|
+
/** 只清绑定类错误(刷新/重建预览时用,事件错误应保留) */
|
|
181
|
+
clearBindingErrors() {
|
|
182
|
+
this.errors.length = 0
|
|
183
|
+
this.scheduleErrorBump()
|
|
184
|
+
},
|
|
185
|
+
clearApiCalls() {
|
|
186
|
+
this.apiCalls.length = 0
|
|
187
|
+
apiRev.value++
|
|
188
|
+
},
|
|
189
|
+
/**
|
|
190
|
+
* 捕获事件代码里的 console.* 主动日志。level 取 'log'|'info'|'warn'|'error'|'debug',
|
|
191
|
+
* args 为原始实参数组,统一格式化为单行文本后入队(同时仍由调用方打到真实控制台)。
|
|
192
|
+
* 仅由用户事件/生命周期动作触发,直接自增 logRev 安全(不在渲染函数内)。
|
|
193
|
+
*/
|
|
194
|
+
pushLog(level, args) {
|
|
195
|
+
const arr = args || []
|
|
196
|
+
const text = arr.map((a) => formatLogArg(a)).join(' ')
|
|
197
|
+
// 同时保留原始 args:面板据此做类型着色与 JSON 折叠渲染(text 仅作兜底)。
|
|
198
|
+
this.eventLogs.push({ level, text, args: arr, t: Date.now() })
|
|
199
|
+
if (this.eventLogs.length > 500) this.eventLogs.shift()
|
|
200
|
+
logRev.value++
|
|
201
|
+
},
|
|
202
|
+
/** 手动清空事件主动日志(事件面板「清空日志」按钮) */
|
|
203
|
+
clearEventLogs() {
|
|
204
|
+
this.eventLogs.length = 0
|
|
205
|
+
logRev.value++
|
|
206
|
+
},
|
|
207
|
+
pushError(kind, msg) {
|
|
208
|
+
// 事件执行错误单独存放,不随渲染重置
|
|
209
|
+
const list = EVENT_ERROR_KINDS.includes(kind) ? this.eventErrors : this.errors
|
|
210
|
+
list.push({ kind, msg, t: Date.now() })
|
|
211
|
+
if (list.length > 500) list.shift()
|
|
212
|
+
this.scheduleErrorBump()
|
|
213
|
+
},
|
|
214
|
+
/** 延迟自增 errorRev:确保不在渲染函数内读写 observable(防无限循环) */
|
|
215
|
+
scheduleErrorBump() {
|
|
216
|
+
if (bumpScheduled) return
|
|
217
|
+
bumpScheduled = true
|
|
218
|
+
nextTick(() => {
|
|
219
|
+
bumpScheduled = false
|
|
220
|
+
errorRev.value++
|
|
221
|
+
})
|
|
222
|
+
},
|
|
223
|
+
/** 渲染期绑定错误收集缓冲区(普通数组,非响应式) */
|
|
224
|
+
getBindingBuffer() {
|
|
225
|
+
return bindingBuffer
|
|
226
|
+
},
|
|
227
|
+
/** 根节点渲染开始时重置缓冲区,本轮渲染重新收集绑定错误 */
|
|
228
|
+
startBindingPass() {
|
|
229
|
+
bindingBuffer = []
|
|
230
|
+
},
|
|
231
|
+
/**
|
|
232
|
+
* 渲染结束后(mounted/updated 钩子)提交本轮绑定错误:
|
|
233
|
+
* 去重后整体替换 rt.errors。事件错误不受影响。
|
|
234
|
+
*/
|
|
235
|
+
commitBindingPass() {
|
|
236
|
+
const seen = new Set()
|
|
237
|
+
const next = []
|
|
238
|
+
for (const e of bindingBuffer) {
|
|
239
|
+
if (seen.has(e.msg)) continue
|
|
240
|
+
seen.add(e.msg)
|
|
241
|
+
next.push(e)
|
|
242
|
+
}
|
|
243
|
+
this.errors.length = 0
|
|
244
|
+
for (const e of next) this.errors.push(e)
|
|
245
|
+
this.scheduleErrorBump()
|
|
246
|
+
},
|
|
247
|
+
pushApiCall(api, payload) {
|
|
248
|
+
this.apiCalls.push({ api, payload, t: Date.now() })
|
|
249
|
+
if (this.apiCalls.length > 500) this.apiCalls.shift()
|
|
250
|
+
apiRev.value++
|
|
251
|
+
},
|
|
252
|
+
addEventListener(name, fn) {
|
|
253
|
+
if (!this.listeners[name]) this.listeners[name] = []
|
|
254
|
+
this.listeners[name].push(fn)
|
|
255
|
+
},
|
|
256
|
+
/**
|
|
257
|
+
* 内部通讯:组件经 postMessage 通知「外部」(通常是根节点注册的监听器)
|
|
258
|
+
* 修改数据状态。对齐官方平台语义:派发时把 payload 包成 { data: payload },
|
|
259
|
+
* 因此监听器统一以 `event.data` 读取内容(与卡片脚本里的
|
|
260
|
+
* `this.addEventListener('globalSet', e => self.$set(e.data.key, e.data.value))`
|
|
261
|
+
* 写法一致)。
|
|
262
|
+
*/
|
|
263
|
+
postMessage(name, payload) {
|
|
264
|
+
const ls = this.listeners[name] || []
|
|
265
|
+
const event = { data: payload, type: name }
|
|
266
|
+
ls.forEach((fn) => {
|
|
267
|
+
try {
|
|
268
|
+
fn(event)
|
|
269
|
+
} catch (e) {
|
|
270
|
+
// 不再静默吞掉:监听器脚本出错时记录到调试面板,便于排查
|
|
271
|
+
this.pushError('listener', `${name}: ${e && e.message}`)
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
this.pushApiCall('postMessage:' + name, payload)
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 绑定数据(rootScope)一旦变更——就地编辑(绑定数据 tab 增删改)、换卡、
|
|
279
|
+
// Raw 应用、重置导入镜像等——即把运行态 state 重置为最新绑定数据的深拷贝,
|
|
280
|
+
// 使「运行态」面板回到干净基线。调试交互只写 state、不碰 rootScope,故不会
|
|
281
|
+
// 触发本 watch;watch 仅在绑定数据真正变化时回调(懒触发,不重渲染初始 seed)。
|
|
282
|
+
watch(
|
|
283
|
+
() => rootScope,
|
|
284
|
+
() => rt.reseedState(),
|
|
285
|
+
{ deep: true }
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
return rt
|
|
289
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 节点树剪贴板(编辑器内的复制/粘贴/副本)。
|
|
3
|
+
*
|
|
4
|
+
* 存储的是 schema 节点子树的深拷贝(纯数据,已剥离运行时 _schemaNode 引用),
|
|
5
|
+
* 粘贴/副本时再递归重写 componentId,避免与卡片内现有节点冲突。
|
|
6
|
+
*
|
|
7
|
+
* 设计要点:
|
|
8
|
+
* - 用 Vue reactive 承载,便于结构树「粘贴」按钮实时反映剪贴板是否为空(disabled 态);
|
|
9
|
+
* - 实际插入 schema 时,会对存储的克隆再深拷贝一份并重新生成 id,保证重复粘贴互不影响;
|
|
10
|
+
* - 记录来源卡片 id(sourceCardId),用于跨卡片粘贴时的来源标注与兼容性校验。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { reactive } from 'vue'
|
|
14
|
+
import { genComponentId } from '@/runtime/toPreview.js'
|
|
15
|
+
|
|
16
|
+
/** 剪贴板状态:node 为 null 表示空;sourceCardId 记录复制时所在卡片 id */
|
|
17
|
+
export const nodeClipboard = reactive({ node: null, sourceCardId: null })
|
|
18
|
+
|
|
19
|
+
/** 取出剪贴板当前节点(只读引用,勿就地改写) */
|
|
20
|
+
export function getClipboardNode() {
|
|
21
|
+
return nodeClipboard.node
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 取出剪贴板来源卡片 id(可能为 null,表示未知来源) */
|
|
25
|
+
export function getClipboardSourceCardId() {
|
|
26
|
+
return nodeClipboard.sourceCardId
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 写入剪贴板:深拷贝 schema 节点,剥离 _schemaNode 循环引用,并记录来源卡片 id */
|
|
30
|
+
export function setClipboardNode(schemaNode, sourceCardId = null) {
|
|
31
|
+
if (!schemaNode) {
|
|
32
|
+
nodeClipboard.node = null
|
|
33
|
+
nodeClipboard.sourceCardId = null
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
const clone = JSON.parse(
|
|
37
|
+
JSON.stringify(schemaNode, (k, v) => (k === '_schemaNode' ? undefined : v)),
|
|
38
|
+
)
|
|
39
|
+
nodeClipboard.node = clone
|
|
40
|
+
nodeClipboard.sourceCardId = sourceCardId || null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function clearClipboard() {
|
|
44
|
+
nodeClipboard.node = null
|
|
45
|
+
nodeClipboard.sourceCardId = null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 递归收集子树中所有节点的 className(去重),用于跨卡片粘贴前的兼容性校验。
|
|
50
|
+
* @param {Object} node schema 节点
|
|
51
|
+
* @param {Set<string>} [acc] 累加集合(内部递归用)
|
|
52
|
+
* @returns {string[]} 去重后的 className 列表
|
|
53
|
+
*/
|
|
54
|
+
export function collectClassNames(node, acc = new Set()) {
|
|
55
|
+
if (!node || typeof node !== 'object') return [...acc]
|
|
56
|
+
if (typeof node.className === 'string' && node.className) acc.add(node.className)
|
|
57
|
+
if (Array.isArray(node.slots)) {
|
|
58
|
+
for (const s of node.slots) {
|
|
59
|
+
if (s && Array.isArray(s.components)) {
|
|
60
|
+
for (const c of s.components) collectClassNames(c, acc)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return [...acc]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 递归重写子树内所有节点的 componentId,避免与卡片内现有节点冲突。
|
|
69
|
+
* @param {Object} node schema 节点(会被就地修改)
|
|
70
|
+
*/
|
|
71
|
+
export function regenerateComponentIds(node) {
|
|
72
|
+
if (!node || typeof node !== 'object') return
|
|
73
|
+
if (node.componentId != null && node.className != null) {
|
|
74
|
+
node.componentId = genComponentId(node.className)
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(node.slots)) {
|
|
77
|
+
for (const s of node.slots) {
|
|
78
|
+
if (s && Array.isArray(s.components)) {
|
|
79
|
+
for (const c of s.components) regenerateComponentIds(c)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 复制文本到剪贴板。优先 navigator.clipboard,失败时降级到
|
|
3
|
+
* textarea + execCommand(兼容非安全上下文 / 旧浏览器)。
|
|
4
|
+
* @param {string} text 待复制文本
|
|
5
|
+
* @returns {Promise<boolean>} 是否复制成功
|
|
6
|
+
*/
|
|
7
|
+
export async function copyText(text) {
|
|
8
|
+
try {
|
|
9
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
10
|
+
await navigator.clipboard.writeText(text)
|
|
11
|
+
return true
|
|
12
|
+
}
|
|
13
|
+
} catch (e) {
|
|
14
|
+
/* 降级到 execCommand */
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const ta = document.createElement('textarea')
|
|
18
|
+
ta.value = text
|
|
19
|
+
ta.style.position = 'fixed'
|
|
20
|
+
ta.style.opacity = '0'
|
|
21
|
+
document.body.appendChild(ta)
|
|
22
|
+
ta.select()
|
|
23
|
+
const ok = document.execCommand('copy')
|
|
24
|
+
document.body.removeChild(ta)
|
|
25
|
+
return ok
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表达式求值 / 变量回填
|
|
3
|
+
*
|
|
4
|
+
* 移植自官方渲染模块 2130 里的 `d`(evalExpr)、`f`(setByPath)、`p`(applyVars)。
|
|
5
|
+
*
|
|
6
|
+
* 卡片 DSL 的数据绑定不是 `{{ }}` 模板,而是 **`$` 前缀的 JS 表达式**:
|
|
7
|
+
* 作用域里的 `foo` 会被改名成 `$foo` 注入,再用 `with(vars){ return (expr) }` 求值。
|
|
8
|
+
* 唯一的例外是 `$$`(作用域插槽的当前 item),它保持原名。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 把作用域对象转成 `$` 前缀形式。
|
|
13
|
+
* @example { list: [], _index: 0 } -> { $list: [], $_index: 0 }
|
|
14
|
+
*/
|
|
15
|
+
export function toDollarScope(scope) {
|
|
16
|
+
const out = {};
|
|
17
|
+
const src = scope || {};
|
|
18
|
+
for (const k in src) {
|
|
19
|
+
if (k === '$$') out[k] = src[k];
|
|
20
|
+
else out['$' + k] = src[k];
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 求值单个表达式。引用未定义变量时会抛 ReferenceError,由调用方决定如何降级。
|
|
27
|
+
* @param {string} expr 例如 "$rate > 0 ? '#f00' : '#0a0'"
|
|
28
|
+
* @param {Object} scope 原始作用域(不带 $ 前缀)
|
|
29
|
+
*/
|
|
30
|
+
export function evalExpr(expr, scope) {
|
|
31
|
+
const vars = toDollarScope(scope);
|
|
32
|
+
// 用 Proxy 包裹作用域:未定义的 `$` 变量解析为 undefined(而非抛 ReferenceError),
|
|
33
|
+
// 这样生产环境里 `$foo || 默认值` 这类兜底能正常生效。真正的语法错误仍会抛错并被上层捕获。
|
|
34
|
+
const proxy = new Proxy(vars, {
|
|
35
|
+
has(t, k) {
|
|
36
|
+
if (typeof k === 'string' && (k[0] === '$' || k === '$$')) return true
|
|
37
|
+
return k in t;
|
|
38
|
+
},
|
|
39
|
+
get(t, k) {
|
|
40
|
+
if (k in t) return t[k];
|
|
41
|
+
if (typeof k === 'string' && (k[0] === '$' || k === '$$')) return undefined;
|
|
42
|
+
return t[k];
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
// eslint-disable-next-line no-new-func
|
|
46
|
+
const fn = new Function('vars', `\n\t\twith(vars){\n\t\t\treturn (${expr})\n\t\t}\n\t`);
|
|
47
|
+
return fn(proxy);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 按点号路径写值。中间路径不存在时会抛错(与官方一致,交给上层 try/catch)。 */
|
|
51
|
+
export function setByPath(obj, path, value) {
|
|
52
|
+
const seg = String(path).split('.');
|
|
53
|
+
const last = seg.pop();
|
|
54
|
+
let cur = obj;
|
|
55
|
+
for (let i = 0; i < seg.length; i++) cur = cur[seg[i]];
|
|
56
|
+
cur[last] = value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 把 preview 对象里登记的变量表达式求值后回填。
|
|
61
|
+
*
|
|
62
|
+
* 注意:**逐项 try/catch**。某一项求值失败时该项保持 meta 默认值(而不是变成
|
|
63
|
+
* undefined)—— 这正是官方行为,也是 billListDialog 这类引用了未定义变量的卡
|
|
64
|
+
* 还能正常渲染的原因。
|
|
65
|
+
*
|
|
66
|
+
* @param {Object} previewObj 带 __vars__ 的 preview 产物
|
|
67
|
+
* @param {Object} scope 数据绑定
|
|
68
|
+
* @param {Object} [errors] 可选,收集失败信息用于调试面板
|
|
69
|
+
*/
|
|
70
|
+
export function applyVars(previewObj = {}, scope = {}, errors) {
|
|
71
|
+
const out = JSON.parse(JSON.stringify(previewObj || {}));
|
|
72
|
+
const vars = previewObj && previewObj.__vars__;
|
|
73
|
+
if (vars) {
|
|
74
|
+
for (const path in vars) {
|
|
75
|
+
try {
|
|
76
|
+
setByPath(out, path, evalExpr(vars[path], scope));
|
|
77
|
+
} catch (e) {
|
|
78
|
+
if (errors)
|
|
79
|
+
errors.push({
|
|
80
|
+
kind: 'bind',
|
|
81
|
+
msg: `${path} <- ${vars[path]}: ${e && e.message}`,
|
|
82
|
+
path,
|
|
83
|
+
expr: vars[path],
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
delete out.__vars__;
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 构造作用域插槽的子作用域。
|
|
94
|
+
* 与官方一致:外层作用域 + item 对象**展开** + `$$`(整个 item) + `_index`(索引)。
|
|
95
|
+
* 所以列表项内部既能写 `$fieldName`,也能写 `$$` 和 `$_index`。
|
|
96
|
+
*/
|
|
97
|
+
export function makeSlotScope(parentScope, item, index) {
|
|
98
|
+
return Object.assign(
|
|
99
|
+
{},
|
|
100
|
+
parentScope,
|
|
101
|
+
(typeof item === 'object' && item) || {},
|
|
102
|
+
{ $$: item, _index: index },
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 构造「渲染作用域」:base(局部作用域)优先于 state(rt.state 运行期写入)合并后的只读视图。
|
|
108
|
+
*
|
|
109
|
+
* 关键语义(修复列表项字段被全局字段遮蔽的 bug):
|
|
110
|
+
* - 列表项(ListLayout/GridLayout 的插槽子节点)的 base = itemScope(含 item.title 等),
|
|
111
|
+
* 必须优先于卡片全局的 rt.state(deep-copy 了整份卡片数据,也含顶层 title)——
|
|
112
|
+
* 否则列表项内 `$title` 会取到「卡片外层 greeting」而非「列表项自身 title」。
|
|
113
|
+
* - 根节点 base 取 rt.state(见 App.vue 的 :scope="rt.state"),而 rt.state 本身已是
|
|
114
|
+
* rootScope 的运行期副本,故根层级「运行期写入优先生效」语义不变(state 即 base)。
|
|
115
|
+
* - state 与 base 均只读,state 写入经 runCustomAction 的 set 陷阱落 rt.state,
|
|
116
|
+
* 绝不反向污染 rootScope(持久化数据绑定)—— 修复「点击交互触发 setData 把卡片误判为已编辑」。
|
|
117
|
+
*
|
|
118
|
+
* 用 Proxy + ownKeys/getOwnPropertyDescriptor 陷阱,保证 evalExpr 内部的
|
|
119
|
+
* toDollarScope 用 `for...in` 枚举、以及 `scope[k]` 取值都能正确遍历合并后的键,
|
|
120
|
+
* 且读取底层 reactive(state / rootScope)以建立响应式依赖。
|
|
121
|
+
*
|
|
122
|
+
* 从 RenderTree.vue 提升至此,供回归测试直接测真实实现(L0-7 渲染作用域语义)。
|
|
123
|
+
*/
|
|
124
|
+
export function makeRenderScope(stateScope, baseScope) {
|
|
125
|
+
return new Proxy(
|
|
126
|
+
{},
|
|
127
|
+
{
|
|
128
|
+
get(_, k) {
|
|
129
|
+
if (k in baseScope) return baseScope[k]
|
|
130
|
+
return stateScope[k]
|
|
131
|
+
},
|
|
132
|
+
has(_, k) {
|
|
133
|
+
return k in baseScope || k in stateScope
|
|
134
|
+
},
|
|
135
|
+
ownKeys() {
|
|
136
|
+
const seen = new Set()
|
|
137
|
+
for (const k of Object.keys(baseScope)) seen.add(k)
|
|
138
|
+
for (const k of Object.keys(stateScope)) seen.add(k)
|
|
139
|
+
return [...seen]
|
|
140
|
+
},
|
|
141
|
+
getOwnPropertyDescriptor(_, k) {
|
|
142
|
+
if (k in baseScope)
|
|
143
|
+
return { enumerable: true, configurable: true, value: baseScope[k] }
|
|
144
|
+
if (k in stateScope)
|
|
145
|
+
return { enumerable: true, configurable: true, value: stateScope[k] }
|
|
146
|
+
return undefined
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default applyVars;
|