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,648 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* schema.json → 预览数据
|
|
3
|
+
*
|
|
4
|
+
* 复刻编辑器 chunk c9 中 View / Node / Component / Slot / Style 的
|
|
5
|
+
* `onRestore` + `onPreview` 组合流程:
|
|
6
|
+
*
|
|
7
|
+
* 1. 按组件 meta 造一棵**带默认值**的属性树;
|
|
8
|
+
* 2. 用 schema 导出的 item 覆盖;
|
|
9
|
+
* 3. 依据 itemCondition 重算可见性;
|
|
10
|
+
* 4. 摊平成普通对象,变量表达式收集进 __vars__。
|
|
11
|
+
*
|
|
12
|
+
* 易漏点:schema 里 `properties: []` 的组件**不是没有属性**,
|
|
13
|
+
* 而是"全部沿用 meta 默认值"(restoreProperty 在 items 为空时直接返回)。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import Property, { PropertyList, RestoreMode } from './property.js';
|
|
17
|
+
import componentMetaJson from '../meta/component-meta.json';
|
|
18
|
+
import styleMetaJson from '../meta/style-meta.json';
|
|
19
|
+
import directiveMetaJson from '../meta/directive-meta.json';
|
|
20
|
+
|
|
21
|
+
const clone = (v) => (v === undefined ? v : JSON.parse(JSON.stringify(v)));
|
|
22
|
+
|
|
23
|
+
let __seq = 0;
|
|
24
|
+
const nextVal = (p) => `${p}_${(++__seq).toString(36)}`;
|
|
25
|
+
|
|
26
|
+
/* ------------------------------ meta 索引 ------------------------------ */
|
|
27
|
+
|
|
28
|
+
export const COMPONENT_META = componentMetaJson.components || {};
|
|
29
|
+
|
|
30
|
+
/** 样式分组 meta:按 itemKey / itemId 以及 `id1|id2`、`key1.key2` 路径索引 */
|
|
31
|
+
export const STYLE_META_LIST = styleMetaJson.properties || [];
|
|
32
|
+
export const STYLE_META_MAP = {};
|
|
33
|
+
(function indexStyleMeta(list, idPath = [], keyPath = []) {
|
|
34
|
+
list.forEach((m) => {
|
|
35
|
+
const ids = idPath.concat(m.itemId);
|
|
36
|
+
const keys = keyPath.concat(m.itemKey);
|
|
37
|
+
STYLE_META_MAP[ids.join('|')] = m;
|
|
38
|
+
STYLE_META_MAP[keys.join('.')] = m;
|
|
39
|
+
if (m.itemChildren) indexStyleMeta(m.itemChildren, ids, keys);
|
|
40
|
+
});
|
|
41
|
+
})(STYLE_META_LIST);
|
|
42
|
+
|
|
43
|
+
export const DIRECTIVE_META = directiveMetaJson.properties || [];
|
|
44
|
+
|
|
45
|
+
/* -------------------------------- Style -------------------------------- */
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 组件样式。
|
|
49
|
+
* 默认属性树**只包含 metaInfo.style.state 中显式列出的分组与字段**
|
|
50
|
+
* (parseStateProperties 会把 state 未列出的子属性 splice 掉)。
|
|
51
|
+
*/
|
|
52
|
+
export class Style {
|
|
53
|
+
constructor(meta = {}) {
|
|
54
|
+
this.id = nextVal('style');
|
|
55
|
+
this.state = Object.assign({}, meta.state || {});
|
|
56
|
+
this.visible = meta.visible || {};
|
|
57
|
+
this.meta = meta.meta || {};
|
|
58
|
+
this.properties = [];
|
|
59
|
+
this.parseStateProperties(this.state, this.properties);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
parseStateProperties(state, target = []) {
|
|
63
|
+
const snapshot = clone(state) || {};
|
|
64
|
+
for (const groupKey in snapshot) {
|
|
65
|
+
const meta = STYLE_META_MAP[groupKey];
|
|
66
|
+
if (!meta) continue;
|
|
67
|
+
|
|
68
|
+
const prop = new Property(meta);
|
|
69
|
+
target.push(prop);
|
|
70
|
+
|
|
71
|
+
// 裁掉 state 未列出的子属性
|
|
72
|
+
const allowed = Object.keys(snapshot[groupKey] || {});
|
|
73
|
+
const children = prop.itemChildren?.properties || [];
|
|
74
|
+
// flexShrink 例外:styleVisible 声明了「空间不足时压缩节点」时保留该字段。
|
|
75
|
+
// 否则实例树缺失 flexShrink → 样式输出无 flex-shrink → CSS 回落浏览器默认 1
|
|
76
|
+
// (压缩),而面板按 meta 默认显示 0(关闭),两者脱节。
|
|
77
|
+
const flexVisible = this.visible && this.visible.flex
|
|
78
|
+
if (
|
|
79
|
+
groupKey === 'flex' &&
|
|
80
|
+
flexVisible &&
|
|
81
|
+
typeof flexVisible === 'object' &&
|
|
82
|
+
flexVisible.flexShrink
|
|
83
|
+
) {
|
|
84
|
+
allowed.push('flexShrink');
|
|
85
|
+
}
|
|
86
|
+
for (let i = 0; i < children.length; i++) {
|
|
87
|
+
if (!allowed.includes(children[i].itemKey)) children.splice(i--, 1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 写入 state 里的初值
|
|
91
|
+
PropertyList.setPropertyValue(children, snapshot[groupKey]);
|
|
92
|
+
prop.itemValueFrom = '0';
|
|
93
|
+
prop.itemUpdated = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
onRestore(schemaStyle = {}) {
|
|
98
|
+
this.properties.length = 0;
|
|
99
|
+
this.parseStateProperties(this.state, this.properties);
|
|
100
|
+
|
|
101
|
+
const byId = this.properties.reduce((acc, p) => {
|
|
102
|
+
acc[p.itemId] = p;
|
|
103
|
+
return acc;
|
|
104
|
+
}, {});
|
|
105
|
+
|
|
106
|
+
// schema 里出现的分组:默认树里已有 → Append 合并;没有 → Delete 只保留 schema 列出的字段
|
|
107
|
+
const appendHook = {
|
|
108
|
+
append(item, targetProps, paths = []) {
|
|
109
|
+
const ids = [].concat(paths);
|
|
110
|
+
ids.push(item.itemId);
|
|
111
|
+
const meta = STYLE_META_MAP[ids.join('|')];
|
|
112
|
+
const np = new Property(meta || {});
|
|
113
|
+
targetProps.push(np);
|
|
114
|
+
np.onRestore(clone(item), RestoreMode.Delete);
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
(schemaStyle.properties || []).forEach((item) => {
|
|
119
|
+
const meta = STYLE_META_MAP[item.itemId];
|
|
120
|
+
if (!meta) {
|
|
121
|
+
// meta 未知的样式分组:仍按 schema 原始结构建一个 Property 保留下来,避免写回时丢字段
|
|
122
|
+
const np = new Property(item);
|
|
123
|
+
this.properties.push(np);
|
|
124
|
+
byId[item.itemId] = np;
|
|
125
|
+
np.onRestore(item, RestoreMode.Delete);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const existing = byId[item.itemId];
|
|
129
|
+
if (existing) {
|
|
130
|
+
existing.onRestore(item, RestoreMode.Append, appendHook);
|
|
131
|
+
} else {
|
|
132
|
+
const np = new Property(meta);
|
|
133
|
+
this.properties.push(np);
|
|
134
|
+
byId[item.itemId] = np;
|
|
135
|
+
np.onRestore(item, RestoreMode.Delete);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
Property.updatePropertyVisible(this.properties);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
onPreview() {
|
|
143
|
+
const properties = this.properties.reduce((acc, p) => {
|
|
144
|
+
const r = p.onPreview();
|
|
145
|
+
if (r) Property.mergePreview(acc, r);
|
|
146
|
+
return acc;
|
|
147
|
+
}, {});
|
|
148
|
+
return { id: this.id, properties };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** 序列化回 schema 的 style 结构 `{properties:[...]}` */
|
|
152
|
+
onExport() {
|
|
153
|
+
const properties = this.properties.reduce((ret, p) => {
|
|
154
|
+
const e = p.onExport();
|
|
155
|
+
if (e) ret.push(e);
|
|
156
|
+
return ret;
|
|
157
|
+
}, []);
|
|
158
|
+
return { properties };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 定点写入一个样式字段(移植自编辑器 ModelStyle.onPropertyChange)。
|
|
163
|
+
* 只把用户真正改过的字段并进实例树,避免把整棵样式默认树写进 schema。
|
|
164
|
+
* @param {Property} formGroup 表单树上的顶层分组属性(已含新值)
|
|
165
|
+
* @param {{itemId:string, child?:Object}} childChain 从分组往下指向被改字段的链
|
|
166
|
+
*/
|
|
167
|
+
applyChange(formGroup, childChain) {
|
|
168
|
+
const status = formGroup.onExport(childChain);
|
|
169
|
+
if (!status) return;
|
|
170
|
+
|
|
171
|
+
const itemId = formGroup.itemId;
|
|
172
|
+
let target = this.properties.filter((p) => p.itemId === itemId)[0];
|
|
173
|
+
|
|
174
|
+
if (!target) {
|
|
175
|
+
target = new Property(STYLE_META_MAP[itemId] || {});
|
|
176
|
+
this.properties.push(target);
|
|
177
|
+
target.onRestore(clone(status), RestoreMode.Delete);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const ids = [itemId];
|
|
182
|
+
for (let c = childChain; c; c = c.child) ids.push(c.itemId);
|
|
183
|
+
|
|
184
|
+
target.onRestore(clone(status), RestoreMode.Append, {
|
|
185
|
+
append(item, targetProps) {
|
|
186
|
+
const meta = STYLE_META_MAP[ids.join('|')];
|
|
187
|
+
const np = new Property(meta || {});
|
|
188
|
+
targetProps.push(np);
|
|
189
|
+
np.onRestore(clone(item), RestoreMode.Delete);
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* -------------------------------- Slot --------------------------------- */
|
|
196
|
+
|
|
197
|
+
class Slot {
|
|
198
|
+
constructor(meta = {}) {
|
|
199
|
+
this.id = nextVal('slot');
|
|
200
|
+
this.slotKey = meta.slotKey;
|
|
201
|
+
this.slotName = meta.slotName;
|
|
202
|
+
this.slotDesc = meta.slotDesc || '';
|
|
203
|
+
this.slotScope = meta.slotScope === true || meta.slotScope === 'true';
|
|
204
|
+
this.slotWidth = meta.slotWidth || '';
|
|
205
|
+
this.slotHeight = meta.slotHeight || '';
|
|
206
|
+
this.slotSwitch = meta.slotSwitch === undefined ? '1' : meta.slotSwitch;
|
|
207
|
+
this.components = [];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
onRestore(schemaSlot = {}) {
|
|
211
|
+
if (schemaSlot.slotSwitch !== undefined) this.slotSwitch = schemaSlot.slotSwitch;
|
|
212
|
+
this.components = (schemaSlot.components || []).map((c) => makeComponent(c));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
onPreview() {
|
|
216
|
+
return {
|
|
217
|
+
id: this.id,
|
|
218
|
+
slotKey: this.slotKey,
|
|
219
|
+
slotScope: this.slotScope,
|
|
220
|
+
slotDesc: this.slotDesc,
|
|
221
|
+
slotSwitch: this.slotSwitch,
|
|
222
|
+
slotWidth: this.slotWidth,
|
|
223
|
+
slotHeight: this.slotHeight,
|
|
224
|
+
components: this.components.reduce((acc, c) => {
|
|
225
|
+
const r = c.onPreview();
|
|
226
|
+
if (r) acc.push(r);
|
|
227
|
+
return acc;
|
|
228
|
+
}, []),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** 序列化回 schema 的 slot 结构(onRestore 的逆运算)。components 此时已是 schema 节点数组。 */
|
|
233
|
+
onExport() {
|
|
234
|
+
return {
|
|
235
|
+
slotKey: this.slotKey,
|
|
236
|
+
slotScope: this.slotScope,
|
|
237
|
+
slotSwitch: this.slotSwitch,
|
|
238
|
+
components: (this.components || []).map((c) => (c && typeof c.onExport === 'function' ? c.onExport() : c)),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* ------------------------------ EventAction ----------------------------- */
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 事件动作。
|
|
247
|
+
* action 的属性 meta 不在组件包里(由动作类型决定),而 schema 已完整导出了
|
|
248
|
+
* itemKey/itemValueFrom/itemValue,所以这里直接按 onPreview 的语义摊平即可。
|
|
249
|
+
*/
|
|
250
|
+
function flattenSchemaItems(items = [], paths = [], vars = {}) {
|
|
251
|
+
const out = {};
|
|
252
|
+
const isNested = paths.length > 0;
|
|
253
|
+
(items || []).forEach((it) => {
|
|
254
|
+
if (!it || !it.itemKey) return;
|
|
255
|
+
const cur = paths.concat(it.itemKey);
|
|
256
|
+
if (it.itemValueFrom === '1') {
|
|
257
|
+
out[it.itemKey] = it.itemDefaultValue;
|
|
258
|
+
if (it.itemValue) vars[cur.join('.')] = it.itemValue;
|
|
259
|
+
} else if (it.itemValueFrom === '2') {
|
|
260
|
+
out[it.itemKey] = it.itemValue;
|
|
261
|
+
} else if (it.itemChildren && it.itemChildren.properties) {
|
|
262
|
+
out[it.itemKey] = flattenSchemaItems(it.itemChildren.properties, cur, vars);
|
|
263
|
+
} else if (it.itemList) {
|
|
264
|
+
out[it.itemKey] = it.itemList.map((li, idx) =>
|
|
265
|
+
flattenSchemaItems((li && li.properties) || [], cur.concat(String(idx)), vars),
|
|
266
|
+
);
|
|
267
|
+
} else {
|
|
268
|
+
out[it.itemKey] = it.itemValue;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
if (!isNested) out.__vars__ = vars;
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function makeEventAction(schemaEA = {}, componentEvents = []) {
|
|
276
|
+
const se = schemaEA.event || {};
|
|
277
|
+
// 与官方 getMatchEvent 一致:按 eventKey 在组件 meta 的事件表里找,找不到就是空事件
|
|
278
|
+
const matched = (componentEvents || []).filter((e) => e.eventKey === se.eventKey)[0] || {};
|
|
279
|
+
const action = schemaEA.action || {};
|
|
280
|
+
return {
|
|
281
|
+
event: { eventName: matched.eventName, eventKey: matched.eventKey },
|
|
282
|
+
action: {
|
|
283
|
+
actionType: action.actionType,
|
|
284
|
+
properties: flattenSchemaItems(action.properties || []),
|
|
285
|
+
},
|
|
286
|
+
// 在 schema.eventactions 中的原始下标:回写代码时按下标精确定位,
|
|
287
|
+
// 避免按 eventKey 匹配时 undefined === undefined 误写进空条目
|
|
288
|
+
schemaIndex: -1,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* ------------------------------- Component ------------------------------ */
|
|
293
|
+
|
|
294
|
+
class Component {
|
|
295
|
+
constructor(schemaNode = {}) {
|
|
296
|
+
const className = schemaNode.className;
|
|
297
|
+
const meta = COMPONENT_META[className] || {};
|
|
298
|
+
|
|
299
|
+
this.id = schemaNode.id || nextVal('comp');
|
|
300
|
+
this.componentId = schemaNode.componentId || nextVal(String(className).toLowerCase());
|
|
301
|
+
this.name = schemaNode.name || meta.name || className;
|
|
302
|
+
this.className = className;
|
|
303
|
+
this.version = schemaNode.version || meta.version;
|
|
304
|
+
this.visible = true;
|
|
305
|
+
this.metaMissing = !COMPONENT_META[className];
|
|
306
|
+
|
|
307
|
+
this.style = new Style({ state: meta.styleState, visible: meta.styleVisible });
|
|
308
|
+
this.directives = DIRECTIVE_META.map((m) => new Property(m));
|
|
309
|
+
this.properties = (meta.properties || []).map((m) => new Property(m));
|
|
310
|
+
this.events = meta.events || [];
|
|
311
|
+
this.eventactions = [];
|
|
312
|
+
this.slots = (meta.slots || []).map((s) => new Slot(s));
|
|
313
|
+
// 保存原始 schema 节点引用,供事件编辑器回写
|
|
314
|
+
this._schemaNode = schemaNode;
|
|
315
|
+
|
|
316
|
+
this.onRestore(schemaNode);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
onRestore(node = {}) {
|
|
320
|
+
this.style.onRestore(node.style || {});
|
|
321
|
+
|
|
322
|
+
// 注意:不传 mode —— 既不删除未匹配的默认属性,也不追加 schema 中的多余项
|
|
323
|
+
Property.restoreProperty(node.directives || [], this.directives);
|
|
324
|
+
Property.updatePropertyVisible(this.directives);
|
|
325
|
+
|
|
326
|
+
Property.restoreProperty(node.properties || [], this.properties);
|
|
327
|
+
Property.updatePropertyVisible(this.properties);
|
|
328
|
+
|
|
329
|
+
this.eventactions = (node.eventactions || []).map((ea, i) => {
|
|
330
|
+
const p = makeEventAction(ea, this.events);
|
|
331
|
+
p.schemaIndex = i;
|
|
332
|
+
return p;
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const bySlotKey = (node.slots || []).reduce((acc, s) => {
|
|
336
|
+
acc[s.slotKey] = s;
|
|
337
|
+
return acc;
|
|
338
|
+
}, {});
|
|
339
|
+
this.slots.forEach((s) => {
|
|
340
|
+
if (bySlotKey[s.slotKey]) s.onRestore(bySlotKey[s.slotKey]);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// meta 缺失时(未知组件)兜底:直接把 schema 的子节点挂到 default 插槽,避免整棵子树丢失
|
|
344
|
+
if (this.metaMissing && !this.slots.length && (node.slots || []).length) {
|
|
345
|
+
(node.slots || []).forEach((s) => {
|
|
346
|
+
const slot = new Slot({ slotKey: s.slotKey, slotScope: s.slotScope });
|
|
347
|
+
slot.onRestore(s);
|
|
348
|
+
this.slots.push(slot);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
onPreview() {
|
|
354
|
+
const directives = this.directives.reduce((acc, p) => {
|
|
355
|
+
const r = p.onPreview();
|
|
356
|
+
if (r) Property.mergePreview(acc, r);
|
|
357
|
+
return acc;
|
|
358
|
+
}, {});
|
|
359
|
+
const properties = this.properties.reduce((acc, p) => {
|
|
360
|
+
const r = p.onPreview();
|
|
361
|
+
if (r) Property.mergePreview(acc, r);
|
|
362
|
+
return acc;
|
|
363
|
+
}, {});
|
|
364
|
+
|
|
365
|
+
const viewNode = {
|
|
366
|
+
id: this.id,
|
|
367
|
+
componentId: this.componentId,
|
|
368
|
+
name: this.name,
|
|
369
|
+
className: this.className,
|
|
370
|
+
version: this.version,
|
|
371
|
+
directives,
|
|
372
|
+
style: this.style.onPreview(),
|
|
373
|
+
properties,
|
|
374
|
+
eventactions: this.eventactions,
|
|
375
|
+
slots: this.slots.map((s) => s.onPreview()),
|
|
376
|
+
visible: this.visible,
|
|
377
|
+
metaMissing: this.metaMissing || undefined,
|
|
378
|
+
// 原始 schema 节点引用(非 enumerable,JSON.stringify 时自动忽略)
|
|
379
|
+
_schemaNode: this._schemaNode,
|
|
380
|
+
};
|
|
381
|
+
// Task #5:把本题的「Property 树模型实例」挂到预览输出上,供 AI / skill / MCP 直接读取可编辑结构。
|
|
382
|
+
// 非 enumerable:不会被 JSON.stringify 误带进保存/传输(保存路径只序列化 currentSchema 并剥离 _schemaNode)。
|
|
383
|
+
attachModel(viewNode, {
|
|
384
|
+
className: this.className,
|
|
385
|
+
properties: this.properties,
|
|
386
|
+
style: this.style.properties,
|
|
387
|
+
directives: this.directives,
|
|
388
|
+
events: this.events,
|
|
389
|
+
});
|
|
390
|
+
return viewNode;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function makeComponent(node) {
|
|
395
|
+
return new Component(node);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/* --------------------------------- Node --------------------------------- */
|
|
399
|
+
|
|
400
|
+
/** 根节点(className "Root"),没有 directives / slots */
|
|
401
|
+
class Node {
|
|
402
|
+
constructor(view = {}) {
|
|
403
|
+
this.id = nextVal('view');
|
|
404
|
+
this.width = view.width || '375';
|
|
405
|
+
this.className = view.className || 'Root';
|
|
406
|
+
this.style = new Style({ state: {} });
|
|
407
|
+
this.properties = [];
|
|
408
|
+
this.eventactions = [];
|
|
409
|
+
this.components = [];
|
|
410
|
+
this._schemaNode = view;
|
|
411
|
+
this.onRestore(view);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
onRestore(view = {}) {
|
|
415
|
+
this.style.onRestore(view.style || {});
|
|
416
|
+
Property.restoreProperty(view.properties || [], this.properties);
|
|
417
|
+
Property.updatePropertyVisible(this.properties);
|
|
418
|
+
this.eventactions = (view.eventactions || []).map((ea, i) => {
|
|
419
|
+
const p = makeEventAction(ea, []);
|
|
420
|
+
p.schemaIndex = i;
|
|
421
|
+
return p;
|
|
422
|
+
});
|
|
423
|
+
this.components = (view.components || []).map((c) => makeComponent(c));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
onPreview() {
|
|
427
|
+
const properties = this.properties.reduce((acc, p) => {
|
|
428
|
+
const r = p.onPreview();
|
|
429
|
+
if (r) Property.mergePreview(acc, r);
|
|
430
|
+
return acc;
|
|
431
|
+
}, {});
|
|
432
|
+
const viewNode = {
|
|
433
|
+
id: this.id,
|
|
434
|
+
componentId: this.id,
|
|
435
|
+
width: this.width,
|
|
436
|
+
className: this.className,
|
|
437
|
+
style: this.style.onPreview(),
|
|
438
|
+
properties,
|
|
439
|
+
eventactions: this.eventactions,
|
|
440
|
+
components: this.components.map((c) => c.onPreview()),
|
|
441
|
+
_schemaNode: this._schemaNode,
|
|
442
|
+
};
|
|
443
|
+
// Task #5:根节点同样挂 Property 树模型实例(无 directives / events)
|
|
444
|
+
attachModel(viewNode, {
|
|
445
|
+
className: this.className,
|
|
446
|
+
properties: this.properties,
|
|
447
|
+
style: this.style.properties,
|
|
448
|
+
directives: [],
|
|
449
|
+
events: [],
|
|
450
|
+
});
|
|
451
|
+
return viewNode;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/* -------------------------------- 字典 ---------------------------------- */
|
|
456
|
+
|
|
457
|
+
function dictionaryPreview(dict) {
|
|
458
|
+
if (!dict) return undefined;
|
|
459
|
+
const reduceKV = (arr) =>
|
|
460
|
+
(arr || []).reduce((acc, it) => {
|
|
461
|
+
acc[it.key] = it.defaultValue;
|
|
462
|
+
return acc;
|
|
463
|
+
}, {});
|
|
464
|
+
return {
|
|
465
|
+
dictionaryList: reduceKV(dict.dictionaryList),
|
|
466
|
+
inputParams: reduceKV(dict.inputParams),
|
|
467
|
+
mocks: dict.mocks || {},
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/* ------------------------------- 对外接口 -------------------------------- */
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* 生成稳定的组件实例 ID(与线上编辑器风格一致:`className_<短序列>`)。
|
|
475
|
+
* 同模块内共享序列号,保证同一会话内不重复。
|
|
476
|
+
* @param {string} className 组件类型
|
|
477
|
+
* @returns {string} 形如 `text_3k` 的实例 ID
|
|
478
|
+
*/
|
|
479
|
+
export function genComponentId(className) {
|
|
480
|
+
const base = String(className || 'comp').toLowerCase()
|
|
481
|
+
return nextVal(base)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** 某组件类型在 meta 中是否声明了插槽(即是否为"容器节点",可承载子节点) */
|
|
485
|
+
export function isContainerMeta(className) {
|
|
486
|
+
const meta = COMPONENT_META[className]
|
|
487
|
+
return !!(meta && meta.slots && meta.slots.length)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/** 取某容器组件类型的默认插槽 key(无 meta 时回落到 'default') */
|
|
491
|
+
export function getDefaultSlotKey(className) {
|
|
492
|
+
const meta = COMPONENT_META[className]
|
|
493
|
+
if (meta && meta.slots && meta.slots.length) return meta.slots[0].slotKey
|
|
494
|
+
return 'default'
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* 造一个**完整带默认值**的 schema 组件节点(与线上编辑器「添加组件」的产物同构)。
|
|
499
|
+
* 直接用 meta 默认树填充 directives / style / properties / slots,保证落盘后 schema 完整合法,
|
|
500
|
+
* 不必依赖 buildPreview 在读取时的"空数组沿用默认值"兜底(否则保存出去的节点会是空壳)。
|
|
501
|
+
* @param {string} className 组件类型(与 component-meta.json 的 key 一致)
|
|
502
|
+
* @returns {Object} 可直接 push 进 currentSchema.view.components / slot.components 的 schema 节点
|
|
503
|
+
*/
|
|
504
|
+
export function createComponentNode(className) {
|
|
505
|
+
const meta = COMPONENT_META[className]
|
|
506
|
+
if (!meta) throw new Error('未知组件类型:' + className)
|
|
507
|
+
const comp = new Component({ className, componentId: genComponentId(className) })
|
|
508
|
+
const packProps = (props) =>
|
|
509
|
+
(props || []).map((p) => p.onExport()).filter((x) => x !== undefined)
|
|
510
|
+
return {
|
|
511
|
+
componentId: comp.componentId,
|
|
512
|
+
className: comp.className,
|
|
513
|
+
directives: packProps(comp.directives),
|
|
514
|
+
style: comp.style.onExport(),
|
|
515
|
+
properties: packProps(comp.properties),
|
|
516
|
+
eventactions: [],
|
|
517
|
+
slots: comp.slots.map((s) => s.onExport()),
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* @param {Object} schema src/cards/<id>/schema.json 的内容
|
|
523
|
+
* @returns {Object} 预览数据 `{id, version, pageId, name, desc, platform, dictionary, view}`
|
|
524
|
+
*/
|
|
525
|
+
export function buildPreview(schema = {}) {
|
|
526
|
+
const viewSchema = schema.view || schema;
|
|
527
|
+
const node = new Node(viewSchema);
|
|
528
|
+
const dictionary = dictionaryPreview(schema.dictionary);
|
|
529
|
+
return {
|
|
530
|
+
id: schema.id,
|
|
531
|
+
version: schema.version,
|
|
532
|
+
pageId: schema.pageId,
|
|
533
|
+
name: schema.name,
|
|
534
|
+
desc: schema.desc,
|
|
535
|
+
platform: schema.platform,
|
|
536
|
+
...(dictionary ? { dictionary } : {}),
|
|
537
|
+
view: node.onPreview(),
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** 遍历预览树上的所有组件(含插槽内的) */
|
|
542
|
+
export function walkComponents(previewView, fn) {
|
|
543
|
+
const visit = (c, parent, slot) => {
|
|
544
|
+
fn(c, parent, slot);
|
|
545
|
+
(c.slots || []).forEach((s) => (s.components || []).forEach((x) => visit(x, c, s)));
|
|
546
|
+
};
|
|
547
|
+
(previewView.components || []).forEach((c) => visit(c, null, null));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/* ----------------------- 模型实例暴露(Task #5) ----------------------- */
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* 单个 Property 实例 → 可传输纯对象(剔除函数/循环引用,保留编辑所需元信息),
|
|
554
|
+
* 用于把预览输出里的 Property 树「暴露」给 AI / skill / MCP 等外部消费者。
|
|
555
|
+
*/
|
|
556
|
+
function serializeItemOption(opt = {}) {
|
|
557
|
+
const o = {};
|
|
558
|
+
if (opt.iParamList) o.iParamList = opt.iParamList;
|
|
559
|
+
if (opt.iTypeList) o.iTypeList = opt.iTypeList;
|
|
560
|
+
if (opt.codeKey) o.codeKey = opt.codeKey;
|
|
561
|
+
if (opt.language) o.language = opt.language;
|
|
562
|
+
if (opt.max !== undefined) o.max = opt.max;
|
|
563
|
+
if (opt.min !== undefined) o.min = opt.min;
|
|
564
|
+
if (opt.step !== undefined) o.step = opt.step;
|
|
565
|
+
return o;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function serializeProperty(p) {
|
|
569
|
+
if (!p || !p.itemKey) return null;
|
|
570
|
+
const out = {
|
|
571
|
+
itemId: p.itemId,
|
|
572
|
+
itemKey: p.itemKey,
|
|
573
|
+
itemName: p.itemName,
|
|
574
|
+
itemType: p.itemType,
|
|
575
|
+
itemValue: p.itemValue,
|
|
576
|
+
itemValueFrom: p.itemValueFrom,
|
|
577
|
+
itemTypeRange: p.itemTypeRange,
|
|
578
|
+
itemVisible: p.itemVisible,
|
|
579
|
+
itemRequired: !!p.itemRequired,
|
|
580
|
+
itemDesc: p.itemDesc,
|
|
581
|
+
options: serializeItemOption(p.itemOption),
|
|
582
|
+
};
|
|
583
|
+
if (p.itemChildren && p.itemChildren.properties && p.itemChildren.properties.length) {
|
|
584
|
+
out.children = p.itemChildren.properties.map(serializeProperty).filter(Boolean);
|
|
585
|
+
}
|
|
586
|
+
if (p.itemList && p.itemList.length) {
|
|
587
|
+
out.itemList = p.itemList.map((g) => (g.properties || []).map(serializeProperty).filter(Boolean));
|
|
588
|
+
}
|
|
589
|
+
return out;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** 把某 viewNode 的 Property 树模型实例挂到预览输出上(非 enumerable,JSON.stringify 时自动忽略) */
|
|
593
|
+
function attachModel(viewNode, model) {
|
|
594
|
+
Object.defineProperty(viewNode, '_model', {
|
|
595
|
+
value: model,
|
|
596
|
+
enumerable: false,
|
|
597
|
+
configurable: true,
|
|
598
|
+
writable: false,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/** 收集某 viewNode 的直接子组件 viewNode(Node 用 components,Component 用 slots[].components) */
|
|
603
|
+
function childComponents(vn) {
|
|
604
|
+
const out = [];
|
|
605
|
+
if (vn.components) out.push(...vn.components);
|
|
606
|
+
if (vn.slots) for (const s of vn.slots) if (s.components) out.push(...s.components);
|
|
607
|
+
return out;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* 把已构建的预览树(每个节点带非枚举 _model)序列化为可传输的模型森林(嵌套,含根节点)。
|
|
612
|
+
* 直接读 viewNode._model 里的 live Property 实例,转成纯对象。
|
|
613
|
+
* @returns {{id,componentId,className,name,properties,style,directives,eventactions,components?}}
|
|
614
|
+
*/
|
|
615
|
+
export function serializePreviewModel(viewNode) {
|
|
616
|
+
if (!viewNode) return null;
|
|
617
|
+
const m = viewNode._model || {};
|
|
618
|
+
const node = {
|
|
619
|
+
id: viewNode.id,
|
|
620
|
+
componentId: viewNode.componentId,
|
|
621
|
+
className: viewNode.className,
|
|
622
|
+
name: viewNode.name,
|
|
623
|
+
properties: (m.properties || []).map(serializeProperty).filter(Boolean),
|
|
624
|
+
style: (m.style || []).map(serializeProperty).filter(Boolean),
|
|
625
|
+
directives: (m.directives || []).map(serializeProperty).filter(Boolean),
|
|
626
|
+
eventactions: viewNode.eventactions || [],
|
|
627
|
+
};
|
|
628
|
+
const kids = childComponents(viewNode);
|
|
629
|
+
if (kids.length) node.components = kids.map(serializePreviewModel).filter(Boolean);
|
|
630
|
+
return node;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* 直接由 schema 产出「可序列化 Property 树模型森林」,供 dev 端点 / AI / skill 调用,
|
|
635
|
+
* 无需先构建 UI 预览数据。
|
|
636
|
+
* @returns {{id:?, version:?, className:string, properties, style, directives, eventactions, components:Array}}
|
|
637
|
+
*/
|
|
638
|
+
export function buildModelView(schema = {}) {
|
|
639
|
+
const preview = buildPreview(schema);
|
|
640
|
+
const root = serializePreviewModel(preview.view);
|
|
641
|
+
if (root) {
|
|
642
|
+
root.id = schema.id;
|
|
643
|
+
root.version = schema.version;
|
|
644
|
+
}
|
|
645
|
+
return root;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export default buildPreview;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// 自包含卡片预览入口:只挂载 CardPreview,不加载编辑器外壳、卡片列表和 dev service 连接逻辑。
|
|
2
|
+
import './assets/svgIcons/index.js'
|
|
3
|
+
|
|
4
|
+
import { createApp, h } from 'vue'
|
|
5
|
+
import CardPreview from './components/CardPreview.vue'
|
|
6
|
+
import './styles/components.css'
|
|
7
|
+
import './styles/app.css'
|
|
8
|
+
import './styles/standalone.css'
|
|
9
|
+
|
|
10
|
+
const params = new URLSearchParams(window.location.search)
|
|
11
|
+
const id = params.get('card') || ''
|
|
12
|
+
const width = Number(params.get('width') || 375)
|
|
13
|
+
|
|
14
|
+
async function boot() {
|
|
15
|
+
const res = await fetch(`/__standalone-card/${encodeURIComponent(id)}`)
|
|
16
|
+
if (!res.ok) {
|
|
17
|
+
const detail = await res.json().catch(() => ({}))
|
|
18
|
+
throw new Error(detail.message || `HTTP ${res.status}`)
|
|
19
|
+
}
|
|
20
|
+
const { schema, data } = await res.json()
|
|
21
|
+
|
|
22
|
+
const app = createApp({
|
|
23
|
+
render() {
|
|
24
|
+
return h(CardPreview, { schema, data, width })
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
app.config.errorHandler = (err, vm, info) => {
|
|
28
|
+
console.error('[standalone render error]', info, err)
|
|
29
|
+
}
|
|
30
|
+
app.mount('#app')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
boot().catch((err) => {
|
|
34
|
+
console.error('[standalone boot failed]', err)
|
|
35
|
+
document.body.setAttribute('data-boot-error', String((err && err.message) || err))
|
|
36
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>CardBuddy Standalone Card Preview</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/standalone-main.js"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|