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,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 事件编辑模型:对齐线上编辑器「事件」页签,节点持有 eventactions(触发事件 + 动作),
|
|
3
|
+
* 动作参数由 action-meta.json 声明,直接复用 Property/PropTree/PropControl 渲染回写机制。
|
|
4
|
+
* 三条纪律(与 NodeEditModel 一致):①回填保真(孤儿项原样留存)②不污染 DSL(仅
|
|
5
|
+
* itemRequired / schema 原有 / 改过值的项写回)③顺序即身份(eventactions 整表按下标导出)。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import Property from './property.js';
|
|
9
|
+
import ACTION_META from '../meta/action-meta.json';
|
|
10
|
+
import { getComponentMeta } from './node-model.js';
|
|
11
|
+
|
|
12
|
+
let __seq = 0;
|
|
13
|
+
const uid = (p) => `${p}_${(++__seq).toString(36)}`;
|
|
14
|
+
const clone = (v) => (v === undefined ? v : JSON.parse(JSON.stringify(v)));
|
|
15
|
+
|
|
16
|
+
/** 「就绪类」生命周期事件:与 RenderTree.LIFECYCLE_KEYS 保持一致 */
|
|
17
|
+
export const LIFECYCLE_KEYS = new Set(['', 'onReady', 'onLoad', 'onCreate']);
|
|
18
|
+
export const isLifecycleKey = (k) => LIFECYCLE_KEYS.has(k || '');
|
|
19
|
+
|
|
20
|
+
export const LIFECYCLE_EVENTS = ACTION_META.lifecycleEvents || [];
|
|
21
|
+
export const ACTION_LIST = ACTION_META.actions || [];
|
|
22
|
+
export const ACTION_MAP = ACTION_LIST.reduce((m, a) => {
|
|
23
|
+
m[a.actionType] = a;
|
|
24
|
+
return m;
|
|
25
|
+
}, {});
|
|
26
|
+
|
|
27
|
+
export const actionNameOf = (t) => (ACTION_MAP[t] && ACTION_MAP[t].actionName) || t || '未选择';
|
|
28
|
+
|
|
29
|
+
/** 收集 schema 中存在、但 meta 模型里没有对应项的参数(按 itemId / itemKey 判定) */
|
|
30
|
+
function orphans(schemaItems, modelProps) {
|
|
31
|
+
const known = new Set();
|
|
32
|
+
(modelProps || []).forEach((p) => {
|
|
33
|
+
if (p.itemId) known.add(p.itemId);
|
|
34
|
+
if (p.itemKey) known.add(p.itemKey);
|
|
35
|
+
});
|
|
36
|
+
return (schemaItems || [])
|
|
37
|
+
.filter((s) => s && s.itemId && !known.has(s.itemId) && !(s.itemKey && known.has(s.itemKey)))
|
|
38
|
+
.map((s) => clone(s));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** event / action 上除已知键之外的杂项,原样留存,导出时合并回去 */
|
|
42
|
+
function rest(obj, keys) {
|
|
43
|
+
const out = {};
|
|
44
|
+
Object.keys(obj || {}).forEach((k) => {
|
|
45
|
+
if (keys.indexOf(k) < 0) out[k] = clone(obj[k]);
|
|
46
|
+
});
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ------------------------------ 单条事件动作 ----------------------------- */
|
|
51
|
+
|
|
52
|
+
export class EventActionRow {
|
|
53
|
+
constructor(schemaEA = {}) {
|
|
54
|
+
const ev = schemaEA.event || {};
|
|
55
|
+
const ac = schemaEA.action || {};
|
|
56
|
+
|
|
57
|
+
this.uid = uid('ea');
|
|
58
|
+
this.eventKey = ev.eventKey || '';
|
|
59
|
+
this.eventName = ev.eventName || '';
|
|
60
|
+
this.actionType = ac.actionType || '';
|
|
61
|
+
|
|
62
|
+
this._extraEvent = rest(ev, ['eventKey', 'eventName']);
|
|
63
|
+
this._extraAction = rest(ac, ['actionType', 'properties']);
|
|
64
|
+
|
|
65
|
+
this.properties = [];
|
|
66
|
+
this.extraProperties = [];
|
|
67
|
+
this.buildProps(ac.properties || []);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 按当前 actionType 从 meta 造参数树,再用 schema 项覆盖 */
|
|
71
|
+
buildProps(schemaProps = []) {
|
|
72
|
+
const metaProps = (ACTION_MAP[this.actionType] || {}).properties || [];
|
|
73
|
+
this.properties = metaProps.map((m) => {
|
|
74
|
+
const p = new Property(m);
|
|
75
|
+
// Property 只搬运它认识的字段,itemRequired 需要手工带上
|
|
76
|
+
p.itemRequired = !!m.itemRequired;
|
|
77
|
+
return p;
|
|
78
|
+
});
|
|
79
|
+
Property.restoreProperty(schemaProps, this.properties);
|
|
80
|
+
Property.updatePropertyVisible(this.properties);
|
|
81
|
+
this.extraProperties = orphans(schemaProps, this.properties);
|
|
82
|
+
// 动作类型换了以后,编辑器要重建 CodeJar 等托管 DOM 的控件
|
|
83
|
+
this.formKey = uid('form');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 该参数当前是否仍是「未填」状态(空值或与默认值相同) */
|
|
87
|
+
isPristine(p) {
|
|
88
|
+
const v = p.itemValue;
|
|
89
|
+
if (v === '' || v === null || v === undefined) return true;
|
|
90
|
+
try {
|
|
91
|
+
return JSON.stringify(v) === JSON.stringify(p.itemDefaultValue);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return v === p.itemDefaultValue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 是否配置了有效内容(用于列表摘要) */
|
|
98
|
+
get summary() {
|
|
99
|
+
if (this.actionType === 'customAction') {
|
|
100
|
+
const fn = this.properties.find((p) => p.itemKey === 'function');
|
|
101
|
+
const code = fn && fn.itemValue && typeof fn.itemValue === 'object' ? fn.itemValue.code || '' : '';
|
|
102
|
+
if (!code) return '空脚本';
|
|
103
|
+
const first = String(code).split('\n').find((l) => l.trim()) || '';
|
|
104
|
+
return first.trim().slice(0, 46) + (first.trim().length > 46 ? '…' : '');
|
|
105
|
+
}
|
|
106
|
+
const filled = this.properties.filter((p) => !this.isPristine(p));
|
|
107
|
+
if (!filled.length) return '未配置参数';
|
|
108
|
+
return filled.map((p) => `${p.itemName}=${String(p.itemValue).slice(0, 20)}`).join(',');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 导出参数数组。
|
|
113
|
+
* 必填项 / schema 原有项 / 用户改过的项才写回,其余不落 schema(见文件头纪律 2)。
|
|
114
|
+
*/
|
|
115
|
+
exportProps() {
|
|
116
|
+
const out = [];
|
|
117
|
+
this.properties.forEach((p) => {
|
|
118
|
+
if (!(p.itemRequired || p.itemUpdated || !this.isPristine(p))) return;
|
|
119
|
+
const e = p.onExport();
|
|
120
|
+
if (e) out.push(e);
|
|
121
|
+
});
|
|
122
|
+
return out.concat(this.extraProperties.map((p) => clone(p)));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* -------------------------------- 节点模型 ------------------------------- */
|
|
127
|
+
|
|
128
|
+
export class EventEditModel {
|
|
129
|
+
constructor(schemaNode = {}) {
|
|
130
|
+
this.schemaNode = schemaNode;
|
|
131
|
+
this.className = schemaNode.className || 'Root';
|
|
132
|
+
|
|
133
|
+
const meta = getComponentMeta(this.className) || {};
|
|
134
|
+
this.metaEvents = (meta.events || []).map((e) => clone(e));
|
|
135
|
+
this.hadKey = Object.prototype.hasOwnProperty.call(schemaNode, 'eventactions');
|
|
136
|
+
|
|
137
|
+
this.rows = (schemaNode.eventactions || []).map((ea) => new EventActionRow(ea));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get count() {
|
|
141
|
+
return this.rows.length;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** 组件自身事件 + 全局生命周期事件(按 eventKey 去重,组件优先) */
|
|
145
|
+
get eventOptions() {
|
|
146
|
+
const seen = new Set();
|
|
147
|
+
const out = [];
|
|
148
|
+
const base = this.className === 'Root'
|
|
149
|
+
? this.metaEvents.concat(LIFECYCLE_EVENTS)
|
|
150
|
+
: this.metaEvents;
|
|
151
|
+
base.forEach((e) => {
|
|
152
|
+
if (!e || !e.eventKey || seen.has(e.eventKey)) return;
|
|
153
|
+
seen.add(e.eventKey);
|
|
154
|
+
out.push(e);
|
|
155
|
+
});
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 某行的事件下拉项:常规项 + 该行当前这个 meta 里没有的 key(避免选中值丢失) */
|
|
160
|
+
eventOptionsFor(row) {
|
|
161
|
+
const list = this.eventOptions;
|
|
162
|
+
if (row.eventKey && !list.some((e) => e.eventKey === row.eventKey)) {
|
|
163
|
+
return list.concat([{ eventKey: row.eventKey, eventName: row.eventName || row.eventKey, _unknown: true }]);
|
|
164
|
+
}
|
|
165
|
+
return list;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** 某行的动作下拉项:事件声明了 includes 时只放行其中列出的动作类型 */
|
|
169
|
+
actionOptionsFor(row) {
|
|
170
|
+
const ev = this.eventOptions.find((e) => e.eventKey === row.eventKey);
|
|
171
|
+
const inc = ev && ev.includes;
|
|
172
|
+
const list = inc && inc.length ? ACTION_LIST.filter((a) => inc.indexOf(a.actionType) >= 0) : ACTION_LIST;
|
|
173
|
+
if (row.actionType && !list.some((a) => a.actionType === row.actionType)) {
|
|
174
|
+
return list.concat([{ actionType: row.actionType, actionName: row.actionType, _unknown: true }]);
|
|
175
|
+
}
|
|
176
|
+
return list;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
eventNameOf(key) {
|
|
180
|
+
const e = this.eventOptions.find((x) => x.eventKey === key);
|
|
181
|
+
return e ? e.eventName : '';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
get eventactionMap() {
|
|
185
|
+
const m = {};
|
|
186
|
+
this.rows.forEach((r) => { if (r.eventKey) m[r.eventKey] = r; });
|
|
187
|
+
return m;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
rowForEvent(eventKey) { return this.eventactionMap[eventKey] || null; }
|
|
191
|
+
|
|
192
|
+
toggleEvent(event) {
|
|
193
|
+
const key = event && event.eventKey;
|
|
194
|
+
if (!key) return;
|
|
195
|
+
const existing = this.rowForEvent(key);
|
|
196
|
+
if (existing) { this.removeRow(existing); }
|
|
197
|
+
else {
|
|
198
|
+
const allow = this.actionOptionsForEvent(key, '');
|
|
199
|
+
const actionType = allow.length ? allow[0].actionType : 'customAction';
|
|
200
|
+
const row = new EventActionRow({
|
|
201
|
+
event: { eventKey: key, eventName: event.eventName || this.eventNameOf(key) },
|
|
202
|
+
action: { actionType, properties: [] },
|
|
203
|
+
});
|
|
204
|
+
this.rows.push(row);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
actionOptionsForEvent(eventKey, currentActionType) {
|
|
209
|
+
const ev = this.eventOptions.find((e) => e.eventKey === eventKey);
|
|
210
|
+
const inc = ev && ev.includes;
|
|
211
|
+
const list = inc && inc.length ? ACTION_LIST.filter((a) => inc.indexOf(a.actionType) >= 0) : ACTION_LIST;
|
|
212
|
+
if (currentActionType && !list.some((a) => a.actionType === currentActionType))
|
|
213
|
+
return list.concat([{ actionType: currentActionType, actionName: currentActionType, _unknown: true }]);
|
|
214
|
+
return list;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
setEventKey(row, key) {
|
|
218
|
+
row.eventKey = key || '';
|
|
219
|
+
row.eventName = this.eventNameOf(row.eventKey) || row.eventName;
|
|
220
|
+
// 新事件若限制了动作类型,当前动作不在白名单里就切到第一个可选项
|
|
221
|
+
const allow = this.actionOptionsFor(row);
|
|
222
|
+
if (row.actionType && !allow.some((a) => a.actionType === row.actionType) && allow.length) {
|
|
223
|
+
this.setActionType(row, allow[0].actionType);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** 切换动作类型:同名参数的值尽量迁移,旧类型的孤儿项丢弃 */
|
|
228
|
+
setActionType(row, type) {
|
|
229
|
+
if (type === row.actionType) return;
|
|
230
|
+
const keep = {};
|
|
231
|
+
row.properties.forEach((p) => {
|
|
232
|
+
if (p.itemKey) keep[p.itemKey] = { itemValue: clone(p.itemValue), itemValueFrom: p.itemValueFrom };
|
|
233
|
+
});
|
|
234
|
+
row.actionType = type || '';
|
|
235
|
+
row.buildProps([]);
|
|
236
|
+
row.properties.forEach((p) => {
|
|
237
|
+
const k = keep[p.itemKey];
|
|
238
|
+
if (!k || k.itemValue === '' || k.itemValue == null) return;
|
|
239
|
+
p.itemValue = k.itemValue;
|
|
240
|
+
p.itemValueFrom = k.itemValueFrom;
|
|
241
|
+
p.itemUpdated = true;
|
|
242
|
+
});
|
|
243
|
+
Property.updatePropertyVisible(row.properties);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
addRow() {
|
|
247
|
+
const first = this.eventOptions[0];
|
|
248
|
+
const row = new EventActionRow({
|
|
249
|
+
event: first ? { eventKey: first.eventKey, eventName: first.eventName } : {},
|
|
250
|
+
action: { actionType: 'customAction', properties: [] },
|
|
251
|
+
});
|
|
252
|
+
this.rows.push(row);
|
|
253
|
+
return row;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
removeRow(row) {
|
|
257
|
+
const i = this.rows.indexOf(row);
|
|
258
|
+
if (i >= 0) this.rows.splice(i, 1);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
moveRow(from, to) {
|
|
262
|
+
if (from < 0 || from >= this.rows.length) return;
|
|
263
|
+
const t = Math.max(0, Math.min(this.rows.length - 1, to));
|
|
264
|
+
if (t === from) return;
|
|
265
|
+
const [r] = this.rows.splice(from, 1);
|
|
266
|
+
this.rows.splice(t, 0, r);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
refreshVisible() {
|
|
270
|
+
this.rows.forEach((r) => Property.updatePropertyVisible(r.properties));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** 序列化成 schema.eventactions */
|
|
274
|
+
export() {
|
|
275
|
+
return this.rows.map((r) => {
|
|
276
|
+
const event = r.eventKey
|
|
277
|
+
? Object.assign({}, r._extraEvent, {
|
|
278
|
+
eventName: this.eventNameOf(r.eventKey) || r.eventName || undefined,
|
|
279
|
+
eventKey: r.eventKey,
|
|
280
|
+
})
|
|
281
|
+
: Object.assign({}, r._extraEvent);
|
|
282
|
+
if (event.eventName === undefined) delete event.eventName;
|
|
283
|
+
const action = Object.assign({}, r._extraAction, {
|
|
284
|
+
actionType: r.actionType,
|
|
285
|
+
properties: r.exportProps(),
|
|
286
|
+
});
|
|
287
|
+
return { event, action };
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** 就地写回 schema 节点(保持与 currentSchema 的引用关系) */
|
|
292
|
+
applyToSchema() {
|
|
293
|
+
// 原本没有这个字段、现在也没配任何事件 —— 不要凭空加上
|
|
294
|
+
if (!this.hadKey && !this.rows.length) return;
|
|
295
|
+
this.schemaNode.eventactions = this.export();
|
|
296
|
+
this.hadKey = true;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export default EventEditModel;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// hover 参考线覆盖层:在「组件结构」tab 把鼠标 hover 到某节点时,于画布上框出对应元素,
|
|
2
|
+
// 用于「正式选中/定位前」的快速预览。与选中态遮罩 (selectionOverlay) 相互独立——
|
|
3
|
+
// 选中框与 hover 参考线可同时显示、互不干扰。
|
|
4
|
+
import { reactive } from 'vue'
|
|
5
|
+
|
|
6
|
+
export const hoverGuide = reactive({
|
|
7
|
+
// 当前 hover 节点对应的画布根 DOM 元素
|
|
8
|
+
el: null,
|
|
9
|
+
visible: false,
|
|
10
|
+
// 当前高亮节点的标识(componentId 回退 id),用于离开时「只清自己」的守卫
|
|
11
|
+
nodeId: null,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
/** 标记某节点为 hover:写入根元素、记录节点标识并置可见。重复写入同一元素由 watch 去重。 */
|
|
15
|
+
export function setHoverEl(el, id) {
|
|
16
|
+
hoverGuide.el = el
|
|
17
|
+
hoverGuide.nodeId = id
|
|
18
|
+
hoverGuide.visible = true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 取消 hover:仅当传入标识正是当前高亮节点时才清除(防止「进入 B 后再离开 A」的竞态误清)。
|
|
23
|
+
* @param {*} id 离开节点的标识;传 null/undefined 表示无条件清除(如离开 tab)。
|
|
24
|
+
*/
|
|
25
|
+
export function clearHoverEl(id) {
|
|
26
|
+
if (hoverGuide.el && (id == null || hoverGuide.nodeId === id)) {
|
|
27
|
+
hoverGuide.el = null
|
|
28
|
+
hoverGuide.nodeId = null
|
|
29
|
+
hoverGuide.visible = false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSAPI 模拟统一分发中心。
|
|
3
|
+
*
|
|
4
|
+
* 卡片内所有 ynet.callJSAPI 调用(含弹窗卡片实例)都经由 window.ynet 汇聚到这里,
|
|
5
|
+
* 避免桩逻辑散落在预览外壳各处。对齐 docs/动态卡片方案详细说明.md 的 JSAPI 设计:
|
|
6
|
+
* - alertCard / closeAlertCard -> alert-card.js 弹窗卡片运行时
|
|
7
|
+
* - alertDialog -> 原生风格确认对话框(手机框内模拟,回调 { ok, dismissByClickBg })
|
|
8
|
+
* - showToast -> 弱提示 Toast(手机框内模拟,showIcon:0错误/1正确/2不显示;不传则不显示图标)
|
|
9
|
+
* - showPicker -> 底部弹出级联选择器(手机框内模拟,回调 { result:[{key,value}] })
|
|
10
|
+
* - showDatePicker -> 底部弹出日期选择器(手机框内模拟,回调 { error:'0', currentDate, data:{year,month,day,value} })
|
|
11
|
+
* - 其余 jsapi -> 仅记录调用流水(调试面板)+ 通用成功回调,保证卡片代码不中断
|
|
12
|
+
*
|
|
13
|
+
* 回调统一遵循文档的通用结构:{ success, error, errorMessage, data }。
|
|
14
|
+
*/
|
|
15
|
+
import { reactive } from 'vue'
|
|
16
|
+
import { openAlertCard, closeAlertCard } from '@/runtime/alert-card.js'
|
|
17
|
+
|
|
18
|
+
/** 设备级模拟 UI 状态:dialog = 当前展示的 alertDialog,toast = 当前 Toast,picker = 当前选择器,datePicker = 当前日期选择器 */
|
|
19
|
+
export const deviceUI = reactive({ dialog: null, toast: null, picker: null, datePicker: null })
|
|
20
|
+
|
|
21
|
+
const dialogQueue = []
|
|
22
|
+
let dialogSeq = 0
|
|
23
|
+
let toastTimer = null
|
|
24
|
+
const pickerQueue = []
|
|
25
|
+
let pickerSeq = 0
|
|
26
|
+
const datePickerQueue = []
|
|
27
|
+
let datePickerSeq = 0
|
|
28
|
+
|
|
29
|
+
/** Toast 自动消失时长(毫秒),对齐原生弱提示体验 */
|
|
30
|
+
const TOAST_DURATION = 2200
|
|
31
|
+
|
|
32
|
+
function safeCall(cb, res) {
|
|
33
|
+
if (typeof cb !== 'function') return
|
|
34
|
+
try {
|
|
35
|
+
cb(res)
|
|
36
|
+
} catch (_) {
|
|
37
|
+
/* 卡片回调内报错忽略,不影响桥接层 */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 从队列取下一个 dialog 展示(无则置空) */
|
|
42
|
+
function shiftDialog() {
|
|
43
|
+
deviceUI.dialog = dialogQueue.length ? dialogQueue.shift() : null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* alertDialog - 原生确认对话框(文档「alertDialog - 弹窗」)。
|
|
48
|
+
* 入参:title / message / okButton(默认"确定") / cancelButton(空串不显示) /
|
|
49
|
+
* clickBgToHide(默认 false) / cancelGrayStyle(默认 false)
|
|
50
|
+
* 连续调用时排队依次弹出;回调 data:{ ok, dismissByClickBg }
|
|
51
|
+
*/
|
|
52
|
+
function mockAlertDialog(p, cb) {
|
|
53
|
+
const params = p || {}
|
|
54
|
+
dialogQueue.push({
|
|
55
|
+
id: ++dialogSeq,
|
|
56
|
+
title: params.title || '',
|
|
57
|
+
message: params.message == null ? '' : String(params.message),
|
|
58
|
+
okButton: params.okButton || '确定',
|
|
59
|
+
cancelButton: params.cancelButton || '',
|
|
60
|
+
clickBgToHide: params.clickBgToHide === true,
|
|
61
|
+
cancelGrayStyle: params.cancelGrayStyle === true,
|
|
62
|
+
callback: cb,
|
|
63
|
+
})
|
|
64
|
+
if (!deviceUI.dialog) shiftDialog()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 关闭当前 alertDialog 并把结果回调给卡片(由 DeviceOverlay 的按钮/遮罩点击触发)。
|
|
69
|
+
* @param {{ ok?: boolean, dismissByClickBg?: boolean }} result
|
|
70
|
+
*/
|
|
71
|
+
export function dismissDialog(result) {
|
|
72
|
+
const cur = deviceUI.dialog
|
|
73
|
+
if (!cur) return
|
|
74
|
+
deviceUI.dialog = null
|
|
75
|
+
safeCall(cur.callback, {
|
|
76
|
+
success: true,
|
|
77
|
+
data: { ok: !!result.ok, dismissByClickBg: !!result.dismissByClickBg },
|
|
78
|
+
})
|
|
79
|
+
shiftDialog()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* showToast - 弱提示(文档「showToast - 显示弱提示」)。
|
|
84
|
+
* 入参:message(必选),showIcon:'0'-错误 icon / '1'-正确 icon / '2'-不显示。
|
|
85
|
+
* 注意:不传 showIcon 时**不显示图标**(与真机默认行为一致;此前误默认 '0' 导致
|
|
86
|
+
* 不传参也带了 × 错误图标)。重复调用时替换当前 Toast 并重置计时。
|
|
87
|
+
*/
|
|
88
|
+
function mockShowToast(p, cb) {
|
|
89
|
+
const params = p || {}
|
|
90
|
+
// 不传 showIcon(null/undefined)→ 空串,渲染层据此不渲染图标;显式 '0'/'1'/'2' 仍生效
|
|
91
|
+
const raw = params.showIcon == null ? '' : String(params.showIcon)
|
|
92
|
+
const icon = raw === '1' ? 'ok' : raw === '0' ? 'err' : ''
|
|
93
|
+
if (toastTimer) {
|
|
94
|
+
clearTimeout(toastTimer)
|
|
95
|
+
toastTimer = null
|
|
96
|
+
}
|
|
97
|
+
deviceUI.toast = {
|
|
98
|
+
message: params.message == null ? '' : String(params.message),
|
|
99
|
+
icon,
|
|
100
|
+
}
|
|
101
|
+
toastTimer = setTimeout(() => {
|
|
102
|
+
deviceUI.toast = null
|
|
103
|
+
toastTimer = null
|
|
104
|
+
}, TOAST_DURATION)
|
|
105
|
+
safeCall(cb, { success: true, data: {} })
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* showPicker - 底部弹出级联选择器(文档「showPicker - 选择器」)。
|
|
110
|
+
* 入参:title(可选) / mode('single'单列 | 'multi'多列(级联)) / dataSource(必选,节点 {text,key,children?}) /
|
|
111
|
+
* currentValue(可选,各列选中 key,如 ["2","lq"];兼容 [["2","lq"]] 写法)。
|
|
112
|
+
* 回调 data.result:各列选中对象数组 [{key, value}](value 取节点 text),如 [{key:'1',value:'文科'},{key:'lq',value:'篮球'}]。
|
|
113
|
+
* 连续调用时排队依次弹出(与 dialog 队列一致)。
|
|
114
|
+
*/
|
|
115
|
+
function mockShowPicker(p, cb) {
|
|
116
|
+
const params = p || {}
|
|
117
|
+
// 兼容 currentValue 的两种写法:["2","lq"] 与 [["2","lq"]]
|
|
118
|
+
let cv = Array.isArray(params.currentValue) ? params.currentValue : []
|
|
119
|
+
if (cv.length && Array.isArray(cv[0])) cv = cv[0]
|
|
120
|
+
pickerQueue.push({
|
|
121
|
+
id: ++pickerSeq,
|
|
122
|
+
title: params.title || '',
|
|
123
|
+
mode: params.mode === 'multi' ? 'multi' : 'single',
|
|
124
|
+
dataSource: Array.isArray(params.dataSource) ? params.dataSource : [],
|
|
125
|
+
currentValue: cv.map((x) => String(x)),
|
|
126
|
+
callback: cb,
|
|
127
|
+
})
|
|
128
|
+
if (!deviceUI.picker) shiftPicker()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** 从队列取下一个 picker 展示(无则置空) */
|
|
132
|
+
function shiftPicker() {
|
|
133
|
+
deviceUI.picker = pickerQueue.length ? pickerQueue.shift() : null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 关闭当前 showPicker 并把结果回调给卡片(由 PickerOverlay 的确定/取消触发)。
|
|
138
|
+
* @param {boolean} commit 是否确认选择(false=取消,不回传 result)
|
|
139
|
+
* @param {Array} result 确认时回传的选中对象数组 [{key, value}]
|
|
140
|
+
*/
|
|
141
|
+
export function dismissPicker(commit, result) {
|
|
142
|
+
const cur = deviceUI.picker
|
|
143
|
+
if (!cur) return
|
|
144
|
+
deviceUI.picker = null
|
|
145
|
+
if (commit) {
|
|
146
|
+
// 对齐真实 JSAPI 出参:error='0' 表示成功,卡片回调据此判断并回填 data。
|
|
147
|
+
const res = { success: true, error: '0', data: { result }, result }
|
|
148
|
+
safeCall(cur.callback, res)
|
|
149
|
+
} else {
|
|
150
|
+
safeCall(cur.callback, { success: true, data: {} })
|
|
151
|
+
}
|
|
152
|
+
shiftPicker()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* showDatePicker - 底部弹出日期选择器(文档「showDatePicker - 时间选择器」)。
|
|
157
|
+
* 入参(对齐 docs/动态卡片方案详细说明.md):
|
|
158
|
+
* type : 'year_month_day'(默认) | 'year_month' | 'month_day'
|
|
159
|
+
* startDate : 最小可选日期,默认 '1970-1-1'
|
|
160
|
+
* endDate : 最大可选日期,默认 '2100-12-31'
|
|
161
|
+
* selectDate : 默认选中日期,默认今天(受起止范围约束)
|
|
162
|
+
* title : 可选,选择器标题(transferPlan 实传 '选择日期',文档入参表未列,兼容接受)
|
|
163
|
+
* 出参(对齐文档 + transferPlan 实际回调):
|
|
164
|
+
* success: true / error: '0'(成功) / currentDate: 完整日期串(transferPlan 回读 res.currentDate)
|
|
165
|
+
* data: { year, month, day, value }(文档规定字段;不适用的分量置空串)
|
|
166
|
+
* 连续调用时排队依次弹出(与 picker 队列一致)。
|
|
167
|
+
*/
|
|
168
|
+
function mockShowDatePicker(p, cb) {
|
|
169
|
+
const params = p || {}
|
|
170
|
+
const VALID = ['year_month_day', 'year_month', 'month_day']
|
|
171
|
+
const type = VALID.includes(params.type) ? params.type : 'year_month_day'
|
|
172
|
+
datePickerQueue.push({
|
|
173
|
+
id: ++datePickerSeq,
|
|
174
|
+
title: params.title || '',
|
|
175
|
+
type,
|
|
176
|
+
startDate: params.startDate || '1970-1-1',
|
|
177
|
+
endDate: params.endDate || '2100-12-31',
|
|
178
|
+
selectDate: params.selectDate || '',
|
|
179
|
+
callback: cb,
|
|
180
|
+
})
|
|
181
|
+
if (!deviceUI.datePicker) shiftDatePicker()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 从队列取下一个 datePicker 展示(无则置空) */
|
|
185
|
+
function shiftDatePicker() {
|
|
186
|
+
deviceUI.datePicker = datePickerQueue.length ? datePickerQueue.shift() : null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 关闭当前 showDatePicker 并把结果回调给卡片(由 DatePickerOverlay 的确定/取消触发)。
|
|
191
|
+
* @param {boolean} commit 是否确认选择(false=取消,不回传)
|
|
192
|
+
* @param {{ year:string, month:string, day:string, value:string }} sel 确认时回传的日期分量
|
|
193
|
+
*/
|
|
194
|
+
export function dismissDatePicker(commit, sel) {
|
|
195
|
+
const cur = deviceUI.datePicker
|
|
196
|
+
if (!cur) return
|
|
197
|
+
deviceUI.datePicker = null
|
|
198
|
+
if (commit) {
|
|
199
|
+
// error='0' 成功(transferPlan 据此 res.error==='0' 判断);currentDate 同步 transferPlan 的 res.currentDate
|
|
200
|
+
const res = {
|
|
201
|
+
success: true,
|
|
202
|
+
error: '0',
|
|
203
|
+
currentDate: sel.value,
|
|
204
|
+
data: { year: sel.year, month: sel.month, day: sel.day, value: sel.value },
|
|
205
|
+
}
|
|
206
|
+
safeCall(cur.callback, res)
|
|
207
|
+
} else {
|
|
208
|
+
safeCall(cur.callback, { success: true, data: {} })
|
|
209
|
+
}
|
|
210
|
+
shiftDatePicker()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** JSAPI 处理器注册表:需要真实模拟的 jsapi 全部在此登记 */
|
|
214
|
+
const handlers = {
|
|
215
|
+
alertCard: (p, cb) => openAlertCard(p, cb),
|
|
216
|
+
closeAlertCard: (p, cb) => closeAlertCard(p, cb),
|
|
217
|
+
alertDialog: mockAlertDialog,
|
|
218
|
+
showToast: mockShowToast,
|
|
219
|
+
showPicker: mockShowPicker,
|
|
220
|
+
showDatePicker: mockShowDatePicker,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 安装统一桩:window.ynet.callJSAPI。预览启动时调用一次。
|
|
225
|
+
* @param {object} runtime 主卡片 runtime,用于记录 jsapi 调用流水(调试面板展示)
|
|
226
|
+
*/
|
|
227
|
+
export function installJsApiMock(runtime) {
|
|
228
|
+
if (typeof window === 'undefined') return
|
|
229
|
+
window.ynet = {
|
|
230
|
+
callJSAPI(api, p, cb) {
|
|
231
|
+
if (runtime && runtime.pushApiCall) runtime.pushApiCall(api, p)
|
|
232
|
+
const handler = handlers[api]
|
|
233
|
+
if (handler) {
|
|
234
|
+
handler(p, cb)
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
// 未模拟的 jsapi:通用成功回调,保证卡片后续逻辑可继续执行
|
|
238
|
+
safeCall(cb, { success: true, data: {} })
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** 切换主卡片时清理模拟 UI(对话框队列 + Toast + 选择器),避免残留旧上下文 */
|
|
244
|
+
export function clearDeviceUI() {
|
|
245
|
+
dialogQueue.length = 0
|
|
246
|
+
deviceUI.dialog = null
|
|
247
|
+
deviceUI.toast = null
|
|
248
|
+
if (toastTimer) {
|
|
249
|
+
clearTimeout(toastTimer)
|
|
250
|
+
toastTimer = null
|
|
251
|
+
}
|
|
252
|
+
pickerQueue.length = 0
|
|
253
|
+
deviceUI.picker = null
|
|
254
|
+
datePickerQueue.length = 0
|
|
255
|
+
deviceUI.datePicker = null
|
|
256
|
+
}
|