@sunny-base-web/effects 0.8.43 → 0.8.45
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/dist/Design-Bryr-rNU.mjs +4079 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +24 -10
- package/dist/index.mjs +26304 -22905
- package/package.json +10 -10
|
@@ -0,0 +1,4079 @@
|
|
|
1
|
+
import { reactive as ne, nextTick as we, defineComponent as _, h as W, ref as A, computed as x, watch as Ce, onUpdated as Se, onUnmounted as he, onMounted as se, provide as ae, openBlock as a, createElementBlock as m, createVNode as N, withCtx as P, renderSlot as E, createElementVNode as p, inject as F, resolveComponent as R, Fragment as T, renderList as z, normalizeClass as q, toDisplayString as D, createCommentVNode as S, createBlock as w, resolveDynamicComponent as $, markRaw as V, normalizeStyle as j, KeepAlive as Ne, shallowRef as le, onBeforeUnmount as pe, createApp as _e, unref as H, getCurrentInstance as Ie } from "vue";
|
|
2
|
+
import { ChevronUp as Be, ChevronDown as Oe, Trash2 as Re } from "@sunny-base-web/icons";
|
|
3
|
+
import { SunnyForm as Ae, SunnySelect as Pe } from "@sunny-base-web/ui";
|
|
4
|
+
import { Button as Ee, Input as De, Textarea as Te } from "@arco-design/web-vue";
|
|
5
|
+
function ze(s) {
|
|
6
|
+
return { all: s = s || /* @__PURE__ */ new Map(), on: function(e, t) {
|
|
7
|
+
var n = s.get(e);
|
|
8
|
+
n ? n.push(t) : s.set(e, [t]);
|
|
9
|
+
}, off: function(e, t) {
|
|
10
|
+
var n = s.get(e);
|
|
11
|
+
n && (t ? n.splice(n.indexOf(t) >>> 0, 1) : s.set(e, []));
|
|
12
|
+
}, emit: function(e, t) {
|
|
13
|
+
var n = s.get(e);
|
|
14
|
+
n && n.slice().map(function(i) {
|
|
15
|
+
i(t);
|
|
16
|
+
}), (n = s.get("*")) && n.slice().map(function(i) {
|
|
17
|
+
i(e, t);
|
|
18
|
+
});
|
|
19
|
+
} };
|
|
20
|
+
}
|
|
21
|
+
let K = (s = 21) => crypto.getRandomValues(new Uint8Array(s)).reduce((e, t) => (t &= 63, t < 36 ? e += t.toString(36) : t < 62 ? e += (t - 26).toString(36).toUpperCase() : t > 62 ? e += "-" : e += "_", e), "");
|
|
22
|
+
var k = "node:change", U = "block:change", J = "page:switch", G = "project:load", je = "history:change", L = "history:restore", u = ze(), me = class M {
|
|
23
|
+
/** 节点唯一标识(自动生成,可通过 options.id 指定) */
|
|
24
|
+
id;
|
|
25
|
+
/**
|
|
26
|
+
* 组件名称
|
|
27
|
+
*
|
|
28
|
+
* 对应 Vue 模板中的标签名,如 `'SunnyButton'`、`'a-input'`、`'div'`。
|
|
29
|
+
* 用于在 ComponentRegistry 中查找组件定义和元信息。
|
|
30
|
+
*/
|
|
31
|
+
name;
|
|
32
|
+
/**
|
|
33
|
+
* 组件属性(props)
|
|
34
|
+
*
|
|
35
|
+
* 键值对结构,key 为属性名,value 为属性值。
|
|
36
|
+
* 值可以是静态值(字符串、数字、布尔值)或表达式字符串(运行时求值)。
|
|
37
|
+
*
|
|
38
|
+
* @example `{ type: 'primary', size: 'large', disabled: false }`
|
|
39
|
+
*/
|
|
40
|
+
props;
|
|
41
|
+
/**
|
|
42
|
+
* 事件绑定
|
|
43
|
+
*
|
|
44
|
+
* 键值对结构,key 为事件名,value 为处理函数名称或表达式字符串。
|
|
45
|
+
*
|
|
46
|
+
* @example `{ click: 'handleClick', change: 'onValueChange' }`
|
|
47
|
+
*/
|
|
48
|
+
events;
|
|
49
|
+
/**
|
|
50
|
+
* Vue 指令列表
|
|
51
|
+
*
|
|
52
|
+
* 支持常用指令如 v-show、v-if、v-for、v-model 等。
|
|
53
|
+
*/
|
|
54
|
+
directives;
|
|
55
|
+
/**
|
|
56
|
+
* 默认插槽子节点列表
|
|
57
|
+
*
|
|
58
|
+
* 对应 Vue 模板中标签内部的子元素。
|
|
59
|
+
* 只有 isContainer=true 的节点才应该有 children。
|
|
60
|
+
*/
|
|
61
|
+
children;
|
|
62
|
+
/**
|
|
63
|
+
* 具名插槽
|
|
64
|
+
*
|
|
65
|
+
* key 为插槽名,value 为该插槽内的子节点列表。
|
|
66
|
+
*
|
|
67
|
+
* @example `{ header: [NodeModel], footer: [NodeModel] }`
|
|
68
|
+
*/
|
|
69
|
+
slots;
|
|
70
|
+
/**
|
|
71
|
+
* 父节点 ID
|
|
72
|
+
*
|
|
73
|
+
* 根节点的 parentId 为 null。
|
|
74
|
+
* 添加到 BlockModel.rootNode 时设置为 null。
|
|
75
|
+
* 从父节点移除时重置为 null。
|
|
76
|
+
*/
|
|
77
|
+
parentId;
|
|
78
|
+
/**
|
|
79
|
+
* 是否为容器节点
|
|
80
|
+
*
|
|
81
|
+
* 容器节点可以接受子节点拖入(如 Layout、Card)。
|
|
82
|
+
* 非容器节点拒绝子节点拖入(如 Button、Input)。
|
|
83
|
+
* 拖拽投放时通过此字段判断 canDrop。
|
|
84
|
+
*/
|
|
85
|
+
isContainer;
|
|
86
|
+
/**
|
|
87
|
+
* 组件来源标识(可选)
|
|
88
|
+
*
|
|
89
|
+
* 用于渲染器区分组件查找策略:
|
|
90
|
+
* - `undefined` / 普通字符串 → 从 ComponentRegistry 查找
|
|
91
|
+
* - `'schema:<id>'` → 异步加载 BlockSchema 并递归渲染
|
|
92
|
+
*/
|
|
93
|
+
from;
|
|
94
|
+
/**
|
|
95
|
+
* 创建 NodeModel 实例
|
|
96
|
+
*
|
|
97
|
+
* @param name - 组件名称(如 'SunnyButton'、'div')
|
|
98
|
+
* @param options - 可选配置
|
|
99
|
+
* @param options.id - 指定 ID(用于反序列化),默认自动生成
|
|
100
|
+
* @param options.props - 初始属性
|
|
101
|
+
* @param options.events - 初始事件绑定
|
|
102
|
+
* @param options.directives - 初始指令列表
|
|
103
|
+
* @param options.isContainer - 是否为容器节点,默认 false
|
|
104
|
+
*/
|
|
105
|
+
constructor(e, t) {
|
|
106
|
+
this.id = t?.id ?? K(), this.name = e, this.props = t?.props ? { ...t.props } : {}, this.events = t?.events ? { ...t.events } : {}, this.directives = t?.directives ? [...t.directives] : [], this.children = [], this.slots = {}, this.parentId = null, this.isContainer = t?.isContainer ?? !1;
|
|
107
|
+
}
|
|
108
|
+
// ── Props ──────────────────────────────────────────────
|
|
109
|
+
/**
|
|
110
|
+
* 获取单个属性值
|
|
111
|
+
*
|
|
112
|
+
* @param key - 属性名
|
|
113
|
+
* @returns 属性值,不存在时返回 undefined
|
|
114
|
+
*/
|
|
115
|
+
getProp(e) {
|
|
116
|
+
return this.props[e];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 设置单个属性值
|
|
120
|
+
*
|
|
121
|
+
* 广播 EVENT_NODE_CHANGE { action: 'props' }
|
|
122
|
+
*
|
|
123
|
+
* @param key - 属性名
|
|
124
|
+
* @param value - 属性值(任意类型)
|
|
125
|
+
*/
|
|
126
|
+
setProp(e, t) {
|
|
127
|
+
this.props[e] = t, u.emit(k, {
|
|
128
|
+
nodeId: this.id,
|
|
129
|
+
action: "props",
|
|
130
|
+
parentId: this.parentId
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 批量设置属性
|
|
135
|
+
*
|
|
136
|
+
* 遍历传入的 props 对象,逐个调用 setProp。
|
|
137
|
+
* 每次调用都会广播一个 props 事件。
|
|
138
|
+
*
|
|
139
|
+
* @param props - 属性键值对
|
|
140
|
+
*/
|
|
141
|
+
setProps(e) {
|
|
142
|
+
for (const [t, n] of Object.entries(e))
|
|
143
|
+
this.setProp(t, n);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 移除单个属性
|
|
147
|
+
*
|
|
148
|
+
* 广播 EVENT_NODE_CHANGE { action: 'props' }
|
|
149
|
+
*
|
|
150
|
+
* @param key - 要移除的属性名
|
|
151
|
+
*/
|
|
152
|
+
removeProp(e) {
|
|
153
|
+
delete this.props[e], u.emit(k, {
|
|
154
|
+
nodeId: this.id,
|
|
155
|
+
action: "props",
|
|
156
|
+
parentId: this.parentId
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// ── Events ─────────────────────────────────────────────
|
|
160
|
+
/**
|
|
161
|
+
* 设置事件绑定
|
|
162
|
+
*
|
|
163
|
+
* 广播 EVENT_NODE_CHANGE { action: 'events' }
|
|
164
|
+
*
|
|
165
|
+
* @param eventName - 事件名(如 'click'、'change')
|
|
166
|
+
* @param handler - 处理函数名称或表达式字符串
|
|
167
|
+
*/
|
|
168
|
+
setEvent(e, t) {
|
|
169
|
+
this.events[e] = t, u.emit(k, {
|
|
170
|
+
nodeId: this.id,
|
|
171
|
+
action: "events",
|
|
172
|
+
parentId: this.parentId
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 移除事件绑定
|
|
177
|
+
*
|
|
178
|
+
* 广播 EVENT_NODE_CHANGE { action: 'events' }
|
|
179
|
+
*
|
|
180
|
+
* @param eventName - 要移除的事件名
|
|
181
|
+
*/
|
|
182
|
+
removeEvent(e) {
|
|
183
|
+
delete this.events[e], u.emit(k, {
|
|
184
|
+
nodeId: this.id,
|
|
185
|
+
action: "events",
|
|
186
|
+
parentId: this.parentId
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
// ── Directives ─────────────────────────────────────────
|
|
190
|
+
/**
|
|
191
|
+
* 添加指令
|
|
192
|
+
*
|
|
193
|
+
* 广播 EVENT_NODE_CHANGE { action: 'directive' }
|
|
194
|
+
*
|
|
195
|
+
* @param directive - 指令绑定对象
|
|
196
|
+
*/
|
|
197
|
+
addDirective(e) {
|
|
198
|
+
this.directives.push(e), u.emit(k, {
|
|
199
|
+
nodeId: this.id,
|
|
200
|
+
action: "directive",
|
|
201
|
+
parentId: this.parentId
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 移除指令
|
|
206
|
+
*
|
|
207
|
+
* 广播 EVENT_NODE_CHANGE { action: 'directive' }
|
|
208
|
+
*
|
|
209
|
+
* @param name - 指令名(如 'show'、'if')
|
|
210
|
+
*/
|
|
211
|
+
removeDirective(e) {
|
|
212
|
+
const t = this.directives.findIndex((n) => n.name === e);
|
|
213
|
+
t !== -1 && (this.directives.splice(t, 1), u.emit(k, {
|
|
214
|
+
nodeId: this.id,
|
|
215
|
+
action: "directive",
|
|
216
|
+
parentId: this.parentId
|
|
217
|
+
}));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* 更新指令的表达式值
|
|
221
|
+
*
|
|
222
|
+
* 广播 EVENT_NODE_CHANGE { action: 'directive' }
|
|
223
|
+
*
|
|
224
|
+
* @param name - 指令名
|
|
225
|
+
* @param value - 新的表达式字符串
|
|
226
|
+
*/
|
|
227
|
+
updateDirective(e, t) {
|
|
228
|
+
const n = this.directives.find((i) => i.name === e);
|
|
229
|
+
n && (n.value = t, u.emit(k, {
|
|
230
|
+
nodeId: this.id,
|
|
231
|
+
action: "directive",
|
|
232
|
+
parentId: this.parentId
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
// ── Children ───────────────────────────────────────────
|
|
236
|
+
/**
|
|
237
|
+
* 添加子节点到默认插槽
|
|
238
|
+
*
|
|
239
|
+
* 自动设置 child.parentId 为当前节点 ID。
|
|
240
|
+
* 广播 EVENT_NODE_CHANGE { action: 'add' }
|
|
241
|
+
*
|
|
242
|
+
* @param child - 要添加的子节点
|
|
243
|
+
* @param index - 插入位置,默认追加到末尾
|
|
244
|
+
*/
|
|
245
|
+
addChild(e, t) {
|
|
246
|
+
e.parentId = this.id;
|
|
247
|
+
const n = t ?? this.children.length;
|
|
248
|
+
this.children.splice(n, 0, e), u.emit(k, {
|
|
249
|
+
nodeId: e.id,
|
|
250
|
+
action: "add",
|
|
251
|
+
parentId: this.id,
|
|
252
|
+
index: n
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* 移除子节点
|
|
257
|
+
*
|
|
258
|
+
* 自动将 child.parentId 重置为 null。
|
|
259
|
+
* 广播 EVENT_NODE_CHANGE { action: 'remove' }
|
|
260
|
+
*
|
|
261
|
+
* @param childId - 要移除的子节点 ID
|
|
262
|
+
* @returns 被移除的 NodeModel,不存在时返回 null
|
|
263
|
+
*/
|
|
264
|
+
removeChild(e) {
|
|
265
|
+
const t = this.children.findIndex((i) => i.id === e);
|
|
266
|
+
if (t === -1) return null;
|
|
267
|
+
const [n] = this.children.splice(t, 1);
|
|
268
|
+
return n.parentId = null, u.emit(k, {
|
|
269
|
+
nodeId: e,
|
|
270
|
+
action: "remove",
|
|
271
|
+
parentId: this.id
|
|
272
|
+
}), n;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 移动子节点在 children 中的位置
|
|
276
|
+
*
|
|
277
|
+
* 广播 EVENT_NODE_CHANGE { action: 'move' }
|
|
278
|
+
*
|
|
279
|
+
* @param childId - 要移动的子节点 ID
|
|
280
|
+
* @param toIndex - 目标位置索引
|
|
281
|
+
*/
|
|
282
|
+
moveChild(e, t) {
|
|
283
|
+
const n = this.children.findIndex((r) => r.id === e);
|
|
284
|
+
if (n === -1) return;
|
|
285
|
+
const [i] = this.children.splice(n, 1);
|
|
286
|
+
this.children.splice(t, 0, i), u.emit(k, {
|
|
287
|
+
nodeId: e,
|
|
288
|
+
action: "move",
|
|
289
|
+
parentId: this.id
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// ── Slots ──────────────────────────────────────────────
|
|
293
|
+
/**
|
|
294
|
+
* 添加子节点到具名插槽
|
|
295
|
+
*
|
|
296
|
+
* 如果插槽不存在,自动创建。
|
|
297
|
+
* 广播 EVENT_NODE_CHANGE { action: 'add' }
|
|
298
|
+
*
|
|
299
|
+
* @param slotName - 插槽名(如 'header'、'footer')
|
|
300
|
+
* @param child - 要添加的子节点
|
|
301
|
+
* @param index - 插入位置,默认追加到末尾
|
|
302
|
+
*/
|
|
303
|
+
addToSlot(e, t, n) {
|
|
304
|
+
t.parentId = this.id, this.slots[e] || (this.slots[e] = []);
|
|
305
|
+
const i = n ?? this.slots[e].length;
|
|
306
|
+
this.slots[e].splice(i, 0, t), u.emit(k, {
|
|
307
|
+
nodeId: t.id,
|
|
308
|
+
action: "add",
|
|
309
|
+
parentId: this.id,
|
|
310
|
+
index: i
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* 从具名插槽中移除子节点
|
|
315
|
+
*
|
|
316
|
+
* 如果插槽变为空,自动删除该插槽键。
|
|
317
|
+
* 广播 EVENT_NODE_CHANGE { action: 'remove' }
|
|
318
|
+
*
|
|
319
|
+
* @param slotName - 插槽名
|
|
320
|
+
* @param childId - 要移除的子节点 ID
|
|
321
|
+
* @returns 被移除的 NodeModel,不存在时返回 null
|
|
322
|
+
*/
|
|
323
|
+
removeFromSlot(e, t) {
|
|
324
|
+
const n = this.slots[e];
|
|
325
|
+
if (!n) return null;
|
|
326
|
+
const i = n.findIndex((o) => o.id === t);
|
|
327
|
+
if (i === -1) return null;
|
|
328
|
+
const [r] = n.splice(i, 1);
|
|
329
|
+
return r.parentId = null, n.length === 0 && delete this.slots[e], u.emit(k, {
|
|
330
|
+
nodeId: t,
|
|
331
|
+
action: "remove",
|
|
332
|
+
parentId: this.id
|
|
333
|
+
}), r;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* 获取指定具名插槽的子节点列表
|
|
337
|
+
*
|
|
338
|
+
* @param slotName - 插槽名
|
|
339
|
+
* @returns 子节点数组,插槽不存在时返回空数组
|
|
340
|
+
*/
|
|
341
|
+
getSlotChildren(e) {
|
|
342
|
+
return this.slots[e] ?? [];
|
|
343
|
+
}
|
|
344
|
+
// ── 序列化 ─────────────────────────────────────────────
|
|
345
|
+
/**
|
|
346
|
+
* 序列化为 JSON 对象
|
|
347
|
+
*
|
|
348
|
+
* 递归序列化所有子节点和插槽子节点。
|
|
349
|
+
* 用于持久化存储、跨进程传输、History 快照等。
|
|
350
|
+
*
|
|
351
|
+
* @returns 可 JSON.stringify 的 NodeModelJSON 对象
|
|
352
|
+
*/
|
|
353
|
+
toJSON() {
|
|
354
|
+
return {
|
|
355
|
+
id: this.id,
|
|
356
|
+
name: this.name,
|
|
357
|
+
props: { ...this.props },
|
|
358
|
+
events: { ...this.events },
|
|
359
|
+
directives: this.directives.map((e) => ({ ...e })),
|
|
360
|
+
children: this.children.map((e) => e.toJSON()),
|
|
361
|
+
slots: Object.fromEntries(
|
|
362
|
+
Object.entries(this.slots).map(([e, t]) => [e, t.map((n) => n.toJSON())])
|
|
363
|
+
),
|
|
364
|
+
isContainer: this.isContainer,
|
|
365
|
+
...this.from ? { from: this.from } : {}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* 从 JSON 对象反序列化为 NodeModel 实例
|
|
370
|
+
*
|
|
371
|
+
* 递归还原所有子节点和插槽子节点,自动设置 parentId。
|
|
372
|
+
*
|
|
373
|
+
* @param json - 序列化的 NodeModelJSON 对象
|
|
374
|
+
* @returns 还原的 NodeModel 实例
|
|
375
|
+
*/
|
|
376
|
+
static fromJSON(e) {
|
|
377
|
+
const t = new M(e.name, {
|
|
378
|
+
id: e.id,
|
|
379
|
+
props: e.props,
|
|
380
|
+
events: e.events,
|
|
381
|
+
directives: e.directives,
|
|
382
|
+
isContainer: e.isContainer
|
|
383
|
+
});
|
|
384
|
+
e.from && (t.from = e.from);
|
|
385
|
+
for (const n of e.children) {
|
|
386
|
+
const i = M.fromJSON(n);
|
|
387
|
+
t.children.push(i), i.parentId = t.id;
|
|
388
|
+
}
|
|
389
|
+
for (const [n, i] of Object.entries(e.slots ?? {}))
|
|
390
|
+
t.slots[n] = i.map((r) => {
|
|
391
|
+
const o = M.fromJSON(r);
|
|
392
|
+
return o.parentId = t.id, o;
|
|
393
|
+
});
|
|
394
|
+
return t;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* 深拷贝当前节点
|
|
398
|
+
*
|
|
399
|
+
* 通过 toJSON → fromJSON 实现,生成完全独立的副本(新 ID)。
|
|
400
|
+
*
|
|
401
|
+
* @returns 深拷贝的 NodeModel 实例
|
|
402
|
+
*/
|
|
403
|
+
clone() {
|
|
404
|
+
return M.fromJSON(this.toJSON());
|
|
405
|
+
}
|
|
406
|
+
}, fe = class ge {
|
|
407
|
+
/** Block 唯一标识(自动生成,可通过 options.id 指定用于反序列化) */
|
|
408
|
+
id;
|
|
409
|
+
/**
|
|
410
|
+
* 页面/组件名称
|
|
411
|
+
*
|
|
412
|
+
* 在 ProjectModel 的 pages 列表中作为显示名称。
|
|
413
|
+
* 代码生成时作为 Vue 组件的 name 属性。
|
|
414
|
+
*/
|
|
415
|
+
name;
|
|
416
|
+
/**
|
|
417
|
+
* 页面路由路径
|
|
418
|
+
*
|
|
419
|
+
* 仅对「页面」类型有意义,用于 URL 映射。
|
|
420
|
+
* 如 '/'、'/dashboard'、'/settings'。
|
|
421
|
+
* 对「组件」类型通常为 '/'(不使用)。
|
|
422
|
+
*/
|
|
423
|
+
route;
|
|
424
|
+
/**
|
|
425
|
+
* 根节点
|
|
426
|
+
*
|
|
427
|
+
* 页面组件树的入口,通常是一个容器节点(如 div、SunnyLayout)。
|
|
428
|
+
* 初始为 null,需要通过 setRootNode() 设置。
|
|
429
|
+
*/
|
|
430
|
+
rootNode;
|
|
431
|
+
/**
|
|
432
|
+
* 响应式状态声明列表
|
|
433
|
+
*
|
|
434
|
+
* 对应 Vue `<script setup>` 中的 `ref()` / `reactive()`。
|
|
435
|
+
*/
|
|
436
|
+
state;
|
|
437
|
+
/**
|
|
438
|
+
* 计算属性声明列表
|
|
439
|
+
*
|
|
440
|
+
* 对应 Vue `<script setup>` 中的 `computed()`。
|
|
441
|
+
*/
|
|
442
|
+
computed;
|
|
443
|
+
/**
|
|
444
|
+
* 方法声明列表
|
|
445
|
+
*
|
|
446
|
+
* 对应 Vue `<script setup>` 中的普通函数。
|
|
447
|
+
*/
|
|
448
|
+
methods;
|
|
449
|
+
/**
|
|
450
|
+
* 侦听器声明列表
|
|
451
|
+
*
|
|
452
|
+
* 对应 Vue `<script setup>` 中的 `watch()`。
|
|
453
|
+
*/
|
|
454
|
+
watch;
|
|
455
|
+
/**
|
|
456
|
+
* CSS 样式块列表
|
|
457
|
+
*
|
|
458
|
+
* 每个块包含 selector(选择器)和 properties(属性键值对)。
|
|
459
|
+
* 代码生成时合并到 `<style scoped>` 中。
|
|
460
|
+
*/
|
|
461
|
+
css;
|
|
462
|
+
/**
|
|
463
|
+
* 组件 props 定义列表
|
|
464
|
+
*
|
|
465
|
+
* 对应 `defineProps()` 宏。
|
|
466
|
+
*/
|
|
467
|
+
props;
|
|
468
|
+
/**
|
|
469
|
+
* 组件 emits 定义列表
|
|
470
|
+
*
|
|
471
|
+
* 对应 `defineEmits()` 宏。
|
|
472
|
+
*/
|
|
473
|
+
emits;
|
|
474
|
+
/**
|
|
475
|
+
* 组件 expose 定义列表
|
|
476
|
+
*
|
|
477
|
+
* 对应 `defineExpose()` 宏。
|
|
478
|
+
*/
|
|
479
|
+
expose;
|
|
480
|
+
/**
|
|
481
|
+
* 组件插槽定义列表
|
|
482
|
+
*
|
|
483
|
+
* 对应 `defineSlots()` 宏。
|
|
484
|
+
*/
|
|
485
|
+
slots;
|
|
486
|
+
/**
|
|
487
|
+
* 生命周期钩子列表
|
|
488
|
+
*
|
|
489
|
+
* 对应 `onMounted()` / `onUnmounted()` 等生命周期 API。
|
|
490
|
+
*/
|
|
491
|
+
lifecycleHooks;
|
|
492
|
+
/**
|
|
493
|
+
* 依赖注入声明列表
|
|
494
|
+
*
|
|
495
|
+
* 对应 `inject()` API。
|
|
496
|
+
*/
|
|
497
|
+
inject;
|
|
498
|
+
/**
|
|
499
|
+
* 创建 BlockModel 实例
|
|
500
|
+
*
|
|
501
|
+
* @param name - 页面/组件名称
|
|
502
|
+
* @param options - 可选配置
|
|
503
|
+
* @param options.id - 指定 ID(用于反序列化),默认自动生成
|
|
504
|
+
* @param options.route - 页面路由,默认 '/'
|
|
505
|
+
*/
|
|
506
|
+
constructor(e, t) {
|
|
507
|
+
this.id = t?.id ?? K(), this.name = e, this.route = t?.route ?? "/", this.rootNode = null, this.state = [], this.computed = [], this.methods = [], this.watch = [], this.css = [], this.props = [], this.emits = [], this.expose = [], this.slots = [], this.lifecycleHooks = [], this.inject = [];
|
|
508
|
+
}
|
|
509
|
+
// ── 页面基础信息 ────────────────────────────────────
|
|
510
|
+
/**
|
|
511
|
+
* 修改页面名称
|
|
512
|
+
*
|
|
513
|
+
* 广播 EVENT_BLOCK_CHANGE { action: 'update' }
|
|
514
|
+
*/
|
|
515
|
+
setName(e) {
|
|
516
|
+
this.name = e, this._emitBlockChange("update");
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* 修改页面路由
|
|
520
|
+
*
|
|
521
|
+
* 广播 EVENT_BLOCK_CHANGE { action: 'update' }
|
|
522
|
+
*/
|
|
523
|
+
setRoute(e) {
|
|
524
|
+
this.route = e, this._emitBlockChange("update");
|
|
525
|
+
}
|
|
526
|
+
// ── 节点树操作 ─────────────────────────────────────
|
|
527
|
+
/**
|
|
528
|
+
* 设置根节点
|
|
529
|
+
*
|
|
530
|
+
* 将 node.parentId 设为 null,并广播变更事件。
|
|
531
|
+
*/
|
|
532
|
+
setRootNode(e) {
|
|
533
|
+
e.parentId = null, this.rootNode = e, this._emitBlockChange("update");
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* 在节点树中查找指定 ID 的节点
|
|
537
|
+
*
|
|
538
|
+
* 深度优先递归搜索整个组件树。
|
|
539
|
+
*
|
|
540
|
+
* @param nodeId - 目标节点 ID
|
|
541
|
+
* @returns 找到的 NodeModel,不存在时返回 null
|
|
542
|
+
*/
|
|
543
|
+
findNode(e) {
|
|
544
|
+
return this.rootNode ? this._findNodeRecursive(this.rootNode, e) : null;
|
|
545
|
+
}
|
|
546
|
+
/** 深度优先递归查找节点 */
|
|
547
|
+
_findNodeRecursive(e, t) {
|
|
548
|
+
if (e.id === t) return e;
|
|
549
|
+
for (const n of e.children) {
|
|
550
|
+
const i = this._findNodeRecursive(n, t);
|
|
551
|
+
if (i) return i;
|
|
552
|
+
}
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* 查找指定节点的父节点
|
|
557
|
+
*
|
|
558
|
+
* @param nodeId - 目标节点 ID
|
|
559
|
+
* @returns 父节点 NodeModel,不存在时返回 null(可能是根节点)
|
|
560
|
+
*/
|
|
561
|
+
findParent(e) {
|
|
562
|
+
return this.rootNode ? this._findParentRecursive(this.rootNode, e) : null;
|
|
563
|
+
}
|
|
564
|
+
/** 递归查找父节点 */
|
|
565
|
+
_findParentRecursive(e, t) {
|
|
566
|
+
for (const n of e.children) {
|
|
567
|
+
if (n.id === t) return e;
|
|
568
|
+
const i = this._findParentRecursive(n, t);
|
|
569
|
+
if (i) return i;
|
|
570
|
+
}
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* 在指定父节点下添加子节点
|
|
575
|
+
*
|
|
576
|
+
* 如果 parentId 是 rootNode 的 ID,直接调用 rootNode.addChild。
|
|
577
|
+
* 广播由 NodeModel.addChild 触发 EVENT_NODE_CHANGE。
|
|
578
|
+
*
|
|
579
|
+
* @param parentId - 父节点 ID
|
|
580
|
+
* @param node - 要添加的节点
|
|
581
|
+
* @param index - 插入位置,默认追加到末尾
|
|
582
|
+
*/
|
|
583
|
+
addNode(e, t, n) {
|
|
584
|
+
if (e === this.rootNode?.id) {
|
|
585
|
+
this.rootNode.addChild(t, n);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const i = this.findNode(e);
|
|
589
|
+
i && i.addChild(t, n);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* 从节点树中移除指定节点
|
|
593
|
+
*
|
|
594
|
+
* @param nodeId - 要移除的节点 ID
|
|
595
|
+
* @returns 被移除的 NodeModel,不存在时返回 null
|
|
596
|
+
*/
|
|
597
|
+
removeNode(e) {
|
|
598
|
+
if (!this.rootNode) return null;
|
|
599
|
+
const t = this.findParent(e);
|
|
600
|
+
return t ? t.removeChild(e) : null;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* 将节点从一个父节点移动到另一个父节点
|
|
604
|
+
*
|
|
605
|
+
* 先从原父节点移除,再添加到目标父节点。
|
|
606
|
+
* 广播由 NodeModel 的 removeChild 和 addChild 触发。
|
|
607
|
+
*
|
|
608
|
+
* @param nodeId - 要移动的节点 ID
|
|
609
|
+
* @param targetParentId - 目标父节点 ID
|
|
610
|
+
* @param index - 插入位置,默认追加到末尾
|
|
611
|
+
*/
|
|
612
|
+
moveNode(e, t, n) {
|
|
613
|
+
const i = this.findNode(e);
|
|
614
|
+
if (!i) return;
|
|
615
|
+
const r = this.findParent(e);
|
|
616
|
+
r && r.removeChild(e);
|
|
617
|
+
const o = this.findNode(t);
|
|
618
|
+
o && o.addChild(i, n);
|
|
619
|
+
}
|
|
620
|
+
// ── 拖拽投放 ───────────────────────────────────────
|
|
621
|
+
/**
|
|
622
|
+
* 判断是否可以将源节点拖放到目标节点的指定位置
|
|
623
|
+
*
|
|
624
|
+
* 规则:
|
|
625
|
+
* - 不能拖放到自身
|
|
626
|
+
* - 源和目标节点必须都存在
|
|
627
|
+
* - position=inside 时,目标必须是容器
|
|
628
|
+
* - position=inside 时,不能将祖先拖入自己的后代(防止循环)
|
|
629
|
+
* - position=before/after 时,不能拖到根节点前后
|
|
630
|
+
*
|
|
631
|
+
* @param sourceNodeId - 被拖拽的节点 ID
|
|
632
|
+
* @param targetNodeId - 目标节点 ID
|
|
633
|
+
* @param position - 投放位置(before/after/inside)
|
|
634
|
+
* @returns 是否允许投放
|
|
635
|
+
*/
|
|
636
|
+
canDrop(e, t, n) {
|
|
637
|
+
if (e === t) return !1;
|
|
638
|
+
const i = this.findNode(e), r = this.findNode(t);
|
|
639
|
+
return !(!i || !r || n === "inside" && !r.isContainer || n === "inside" && this._isDescendant(e, t) || (n === "before" || n === "after") && this.rootNode?.id === t);
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* 执行拖拽投放操作
|
|
643
|
+
*
|
|
644
|
+
* 先通过 canDrop 校验,校验不通过则静默返回。
|
|
645
|
+
* 从原父节点移除源节点,插入到目标位置。
|
|
646
|
+
*
|
|
647
|
+
* @param sourceNodeId - 被拖拽的节点 ID
|
|
648
|
+
* @param targetNodeId - 目标节点 ID
|
|
649
|
+
* @param position - 投放位置
|
|
650
|
+
*/
|
|
651
|
+
dropTo(e, t, n) {
|
|
652
|
+
if (!this.canDrop(e, t, n)) return;
|
|
653
|
+
const i = this.findNode(e), r = this.findNode(t);
|
|
654
|
+
if (!i || !r) return;
|
|
655
|
+
const o = this.findParent(e);
|
|
656
|
+
if (o && o.removeChild(e), n === "inside")
|
|
657
|
+
r.addChild(i);
|
|
658
|
+
else {
|
|
659
|
+
const c = this.findParent(t);
|
|
660
|
+
if (c) {
|
|
661
|
+
const l = c.children.findIndex((h) => h.id === t), d = n === "before" ? l : l + 1;
|
|
662
|
+
c.addChild(i, d);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* 检查 targetNodeId 是否是 sourceNodeId 的后代节点
|
|
668
|
+
*
|
|
669
|
+
* 用于防止将父节点拖入子节点导致循环引用。
|
|
670
|
+
*/
|
|
671
|
+
_isDescendant(e, t) {
|
|
672
|
+
const n = this.findNode(e);
|
|
673
|
+
return n ? this._checkDescendantRecursive(n, t) : !1;
|
|
674
|
+
}
|
|
675
|
+
/** 递归检查 targetId 是否在 node 的后代中 */
|
|
676
|
+
_checkDescendantRecursive(e, t) {
|
|
677
|
+
for (const n of e.children)
|
|
678
|
+
if (n.id === t || this._checkDescendantRecursive(n, t)) return !0;
|
|
679
|
+
return !1;
|
|
680
|
+
}
|
|
681
|
+
// ── JS 声明操作 ────────────────────────────────────
|
|
682
|
+
/**
|
|
683
|
+
* 添加响应式状态声明
|
|
684
|
+
*
|
|
685
|
+
* 自动设置 decl.type = 'state'。
|
|
686
|
+
* 广播 EVENT_BLOCK_CHANGE { action: 'state' }
|
|
687
|
+
*/
|
|
688
|
+
addState(e) {
|
|
689
|
+
e.type = "state", this.state.push(e), this._emitBlockChange("state");
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* 移除响应式状态声明
|
|
693
|
+
*
|
|
694
|
+
* @param name - 状态变量名
|
|
695
|
+
*/
|
|
696
|
+
removeState(e) {
|
|
697
|
+
const t = this.state.findIndex((n) => n.name === e);
|
|
698
|
+
t > -1 && (this.state.splice(t, 1), this._emitBlockChange("state"));
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* 更新响应式状态的表达式值
|
|
702
|
+
*
|
|
703
|
+
* @param name - 状态变量名
|
|
704
|
+
* @param value - 新的表达式字符串
|
|
705
|
+
*/
|
|
706
|
+
updateState(e, t) {
|
|
707
|
+
const n = this.state.find((i) => i.name === e);
|
|
708
|
+
n && (n.value = t, this._emitBlockChange("state"));
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* 添加计算属性声明
|
|
712
|
+
*
|
|
713
|
+
* 自动设置 decl.type = 'computed'。
|
|
714
|
+
* 广播 EVENT_BLOCK_CHANGE { action: 'computed' }
|
|
715
|
+
*/
|
|
716
|
+
addComputed(e) {
|
|
717
|
+
e.type = "computed", this.computed.push(e), this._emitBlockChange("computed");
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* 移除计算属性声明
|
|
721
|
+
*
|
|
722
|
+
* @param name - 计算属性名
|
|
723
|
+
*/
|
|
724
|
+
removeComputed(e) {
|
|
725
|
+
const t = this.computed.findIndex((n) => n.name === e);
|
|
726
|
+
t > -1 && (this.computed.splice(t, 1), this._emitBlockChange("computed"));
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* 添加方法声明
|
|
730
|
+
*
|
|
731
|
+
* 自动设置 decl.type = 'method'。
|
|
732
|
+
* 广播 EVENT_BLOCK_CHANGE { action: 'method' }
|
|
733
|
+
*/
|
|
734
|
+
addMethod(e) {
|
|
735
|
+
e.type = "method", this.methods.push(e), this._emitBlockChange("method");
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* 移除方法声明
|
|
739
|
+
*
|
|
740
|
+
* @param name - 方法名
|
|
741
|
+
*/
|
|
742
|
+
removeMethod(e) {
|
|
743
|
+
const t = this.methods.findIndex((n) => n.name === e);
|
|
744
|
+
t > -1 && (this.methods.splice(t, 1), this._emitBlockChange("method"));
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* 添加侦听器声明
|
|
748
|
+
*
|
|
749
|
+
* 自动设置 decl.type = 'watch'。
|
|
750
|
+
* 广播 EVENT_BLOCK_CHANGE { action: 'watch' }
|
|
751
|
+
*/
|
|
752
|
+
addWatch(e) {
|
|
753
|
+
e.type = "watch", this.watch.push(e), this._emitBlockChange("watch");
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* 移除侦听器声明
|
|
757
|
+
*
|
|
758
|
+
* @param name - 侦听器名
|
|
759
|
+
*/
|
|
760
|
+
removeWatch(e) {
|
|
761
|
+
const t = this.watch.findIndex((n) => n.name === e);
|
|
762
|
+
t > -1 && (this.watch.splice(t, 1), this._emitBlockChange("watch"));
|
|
763
|
+
}
|
|
764
|
+
// ── CSS 样式操作 ───────────────────────────────────
|
|
765
|
+
/**
|
|
766
|
+
* 添加 CSS 样式块
|
|
767
|
+
*
|
|
768
|
+
* 广播 EVENT_BLOCK_CHANGE { action: 'css' }
|
|
769
|
+
*/
|
|
770
|
+
addCssBlock(e) {
|
|
771
|
+
this.css.push(e), this._emitBlockChange("css");
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* 移除 CSS 样式块
|
|
775
|
+
*
|
|
776
|
+
* @param id - 样式块 ID
|
|
777
|
+
*/
|
|
778
|
+
removeCssBlock(e) {
|
|
779
|
+
const t = this.css.findIndex((n) => n.id === e);
|
|
780
|
+
t > -1 && (this.css.splice(t, 1), this._emitBlockChange("css"));
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* 更新 CSS 样式块的属性(合并更新)
|
|
784
|
+
*
|
|
785
|
+
* @param id - 样式块 ID
|
|
786
|
+
* @param properties - 要更新的 CSS 属性键值对
|
|
787
|
+
*/
|
|
788
|
+
updateCssBlock(e, t) {
|
|
789
|
+
const n = this.css.find((i) => i.id === e);
|
|
790
|
+
n && (n.properties = { ...n.properties, ...t }, this._emitBlockChange("css"));
|
|
791
|
+
}
|
|
792
|
+
// ── Vue SFC 编译宏操作 ─────────────────────────────
|
|
793
|
+
/** 添加 defineProps 属性定义,广播 EVENT_BLOCK_CHANGE { action: 'update' } */
|
|
794
|
+
addProp(e) {
|
|
795
|
+
this.props.push(e), this._emitBlockChange("update");
|
|
796
|
+
}
|
|
797
|
+
/** 移除 defineProps 属性定义 */
|
|
798
|
+
removeProp(e) {
|
|
799
|
+
const t = this.props.findIndex((n) => n.name === e);
|
|
800
|
+
t > -1 && (this.props.splice(t, 1), this._emitBlockChange("update"));
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* 更新 defineProps 属性定义(部分更新)
|
|
804
|
+
*
|
|
805
|
+
* @param name - 属性名
|
|
806
|
+
* @param updates - 要更新的字段
|
|
807
|
+
*/
|
|
808
|
+
updateProp(e, t) {
|
|
809
|
+
const n = this.props.find((i) => i.name === e);
|
|
810
|
+
n && (Object.assign(n, t), this._emitBlockChange("update"));
|
|
811
|
+
}
|
|
812
|
+
/** 添加 defineEmits 事件定义 */
|
|
813
|
+
addEmit(e) {
|
|
814
|
+
this.emits.push(e), this._emitBlockChange("update");
|
|
815
|
+
}
|
|
816
|
+
/** 移除 defineEmits 事件定义 */
|
|
817
|
+
removeEmit(e) {
|
|
818
|
+
const t = this.emits.findIndex((n) => n.name === e);
|
|
819
|
+
t > -1 && (this.emits.splice(t, 1), this._emitBlockChange("update"));
|
|
820
|
+
}
|
|
821
|
+
/** 更新 defineEmits 事件定义(部分更新) */
|
|
822
|
+
updateEmit(e, t) {
|
|
823
|
+
const n = this.emits.find((i) => i.name === e);
|
|
824
|
+
n && (Object.assign(n, t), this._emitBlockChange("update"));
|
|
825
|
+
}
|
|
826
|
+
/** 添加 defineExpose 暴露定义 */
|
|
827
|
+
addExpose(e) {
|
|
828
|
+
this.expose.push(e), this._emitBlockChange("update");
|
|
829
|
+
}
|
|
830
|
+
/** 移除 defineExpose 暴露定义 */
|
|
831
|
+
removeExpose(e) {
|
|
832
|
+
const t = this.expose.findIndex((n) => n.name === e);
|
|
833
|
+
t > -1 && (this.expose.splice(t, 1), this._emitBlockChange("update"));
|
|
834
|
+
}
|
|
835
|
+
/** 添加 defineSlots 插槽定义 */
|
|
836
|
+
addSlot(e) {
|
|
837
|
+
this.slots.push(e), this._emitBlockChange("update");
|
|
838
|
+
}
|
|
839
|
+
/** 移除 defineSlots 插槽定义 */
|
|
840
|
+
removeSlot(e) {
|
|
841
|
+
const t = this.slots.findIndex((n) => n.name === e);
|
|
842
|
+
t > -1 && (this.slots.splice(t, 1), this._emitBlockChange("update"));
|
|
843
|
+
}
|
|
844
|
+
/** 更新 defineSlots 插槽定义(部分更新) */
|
|
845
|
+
updateSlot(e, t) {
|
|
846
|
+
const n = this.slots.find((i) => i.name === e);
|
|
847
|
+
n && (Object.assign(n, t), this._emitBlockChange("update"));
|
|
848
|
+
}
|
|
849
|
+
/** 添加生命周期钩子 */
|
|
850
|
+
addLifecycleHook(e) {
|
|
851
|
+
this.lifecycleHooks.push(e), this._emitBlockChange("update");
|
|
852
|
+
}
|
|
853
|
+
/** 移除生命周期钩子 */
|
|
854
|
+
removeLifecycleHook(e) {
|
|
855
|
+
const t = this.lifecycleHooks.findIndex((n) => n.name === e);
|
|
856
|
+
t > -1 && (this.lifecycleHooks.splice(t, 1), this._emitBlockChange("update"));
|
|
857
|
+
}
|
|
858
|
+
/** 更新生命周期钩子的函数体 */
|
|
859
|
+
updateLifecycleHook(e, t) {
|
|
860
|
+
const n = this.lifecycleHooks.find((i) => i.name === e);
|
|
861
|
+
n && (n.value = t, this._emitBlockChange("update"));
|
|
862
|
+
}
|
|
863
|
+
/** 添加 inject 依赖注入声明 */
|
|
864
|
+
addInject(e) {
|
|
865
|
+
this.inject.push(e), this._emitBlockChange("update");
|
|
866
|
+
}
|
|
867
|
+
/** 移除 inject 依赖注入声明 */
|
|
868
|
+
removeInject(e) {
|
|
869
|
+
const t = this.inject.findIndex((n) => n.name === e);
|
|
870
|
+
t > -1 && (this.inject.splice(t, 1), this._emitBlockChange("update"));
|
|
871
|
+
}
|
|
872
|
+
/** 更新 inject 依赖注入声明(部分更新) */
|
|
873
|
+
updateInject(e, t) {
|
|
874
|
+
const n = this.inject.find((i) => i.name === e);
|
|
875
|
+
n && (Object.assign(n, t), this._emitBlockChange("update"));
|
|
876
|
+
}
|
|
877
|
+
// ── 序列化 ─────────────────────────────────────────
|
|
878
|
+
/**
|
|
879
|
+
* 序列化为 JSON 对象
|
|
880
|
+
*
|
|
881
|
+
* 递归序列化根节点,浅拷贝所有声明数组。
|
|
882
|
+
* 用于持久化存储、History 快照、跨进程传输。
|
|
883
|
+
*
|
|
884
|
+
* @returns 可 JSON.stringify 的 BlockModelJSON 对象
|
|
885
|
+
*/
|
|
886
|
+
toJSON() {
|
|
887
|
+
return {
|
|
888
|
+
id: this.id,
|
|
889
|
+
name: this.name,
|
|
890
|
+
route: this.route,
|
|
891
|
+
rootNode: this.rootNode?.toJSON() ?? null,
|
|
892
|
+
state: [...this.state],
|
|
893
|
+
computed: [...this.computed],
|
|
894
|
+
methods: [...this.methods],
|
|
895
|
+
watch: [...this.watch],
|
|
896
|
+
css: this.css.map((e) => ({ ...e })),
|
|
897
|
+
props: [...this.props],
|
|
898
|
+
emits: [...this.emits],
|
|
899
|
+
expose: [...this.expose],
|
|
900
|
+
slots: [...this.slots],
|
|
901
|
+
lifecycleHooks: [...this.lifecycleHooks],
|
|
902
|
+
inject: [...this.inject]
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* 从 JSON 对象反序列化为 BlockModel 实例
|
|
907
|
+
*
|
|
908
|
+
* 递归还原根节点树,浅拷贝所有声明数组。
|
|
909
|
+
*
|
|
910
|
+
* @param json - 序列化的 BlockModelJSON 对象
|
|
911
|
+
* @returns 还原的 BlockModel 实例
|
|
912
|
+
*/
|
|
913
|
+
static fromJSON(e) {
|
|
914
|
+
const t = new ge(e.name, {
|
|
915
|
+
id: e.id,
|
|
916
|
+
route: e.route
|
|
917
|
+
});
|
|
918
|
+
return e.rootNode && (t.rootNode = me.fromJSON(e.rootNode)), t.state = [...e.state], t.computed = [...e.computed], t.methods = [...e.methods], t.watch = [...e.watch], t.css = [...e.css], t.props = [...e.props], t.emits = [...e.emits], t.expose = [...e.expose], t.slots = [...e.slots], t.lifecycleHooks = [...e.lifecycleHooks], t.inject = [...e.inject], t;
|
|
919
|
+
}
|
|
920
|
+
// ── 内部 ────────────────────────────────────────────
|
|
921
|
+
/**
|
|
922
|
+
* 广播 Block 变更事件
|
|
923
|
+
*
|
|
924
|
+
* @param action - 变更动作类型
|
|
925
|
+
*/
|
|
926
|
+
_emitBlockChange(e) {
|
|
927
|
+
u.emit(U, { blockId: this.id, action: e });
|
|
928
|
+
}
|
|
929
|
+
}, Le = class {
|
|
930
|
+
/**
|
|
931
|
+
* 快照栈
|
|
932
|
+
*
|
|
933
|
+
* 按时间顺序排列的快照数组,cursor 指向当前状态。
|
|
934
|
+
* undo 后 redo 栈仍然存在,直到新操作截断。
|
|
935
|
+
*/
|
|
936
|
+
snapshots = [];
|
|
937
|
+
/**
|
|
938
|
+
* 当前游标
|
|
939
|
+
*
|
|
940
|
+
* 指向当前状态在 snapshots 中的索引。
|
|
941
|
+
* - 初始值 -1(空栈)
|
|
942
|
+
* - 新增快照后指向最后一个元素
|
|
943
|
+
* - undo 后向左移动
|
|
944
|
+
* - redo 后向右移动
|
|
945
|
+
*/
|
|
946
|
+
cursor = -1;
|
|
947
|
+
/** 最大快照数 */
|
|
948
|
+
maxSize;
|
|
949
|
+
/** 批处理窗口(ms) */
|
|
950
|
+
batchWindow;
|
|
951
|
+
/**
|
|
952
|
+
* 获取当前活跃 Block 的函数
|
|
953
|
+
*
|
|
954
|
+
* 由 Engine 注入,返回当前激活页面对应的 BlockModel。
|
|
955
|
+
* History 不持有 BlockModel 引用,避免循环依赖。
|
|
956
|
+
*/
|
|
957
|
+
getActiveBlock;
|
|
958
|
+
/** 批处理定时器 */
|
|
959
|
+
batchTimer = null;
|
|
960
|
+
/** 批处理中积攒的标签 */
|
|
961
|
+
pendingLabel = "";
|
|
962
|
+
/**
|
|
963
|
+
* 是否正在恢复中
|
|
964
|
+
*
|
|
965
|
+
* 恢复期间设为 true,防止恢复过程中的事件触发新的快照录制。
|
|
966
|
+
* 恢复完成后重置为 false。
|
|
967
|
+
*/
|
|
968
|
+
isRestoring = !1;
|
|
969
|
+
/** 取消订阅函数列表 */
|
|
970
|
+
unsubscribers = [];
|
|
971
|
+
/**
|
|
972
|
+
* 创建 History 实例
|
|
973
|
+
*
|
|
974
|
+
* @param getActiveBlock - 获取当前活跃 Block 的函数(由 Engine 注入)
|
|
975
|
+
* @param options - 构造选项
|
|
976
|
+
*/
|
|
977
|
+
constructor(s, e) {
|
|
978
|
+
this.getActiveBlock = s, this.maxSize = e?.maxSize ?? 50, this.batchWindow = e?.batchWindow ?? 50, this.setupListeners();
|
|
979
|
+
}
|
|
980
|
+
// ── 公共 API ──────────────────────────────────────
|
|
981
|
+
/**
|
|
982
|
+
* 撤销
|
|
983
|
+
*
|
|
984
|
+
* 将游标左移一位,恢复到上一个快照。
|
|
985
|
+
* 触发 EVENT_HISTORY_RESTORE(恢复开始 + 恢复完成)和 EVENT_HISTORY_CHANGE。
|
|
986
|
+
*/
|
|
987
|
+
undo() {
|
|
988
|
+
if (!this.canUndo()) return;
|
|
989
|
+
const s = this.snapshots[this.cursor - 1];
|
|
990
|
+
this.restore(s);
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* 重做
|
|
994
|
+
*
|
|
995
|
+
* 将游标右移一位,恢复到下一个快照。
|
|
996
|
+
*/
|
|
997
|
+
redo() {
|
|
998
|
+
if (!this.canRedo()) return;
|
|
999
|
+
const s = this.snapshots[this.cursor + 1];
|
|
1000
|
+
this.restore(s);
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* 是否可以撤销
|
|
1004
|
+
*
|
|
1005
|
+
* cursor > 0 时表示还有更早的快照可恢复。
|
|
1006
|
+
*/
|
|
1007
|
+
canUndo() {
|
|
1008
|
+
return this.cursor > 0;
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* 是否可以重做
|
|
1012
|
+
*
|
|
1013
|
+
* cursor < snapshots.length - 1 时表示有更新的快照可恢复。
|
|
1014
|
+
*/
|
|
1015
|
+
canRedo() {
|
|
1016
|
+
return this.cursor < this.snapshots.length - 1;
|
|
1017
|
+
}
|
|
1018
|
+
/** 当前游标位置 */
|
|
1019
|
+
getCursor() {
|
|
1020
|
+
return this.cursor;
|
|
1021
|
+
}
|
|
1022
|
+
/** 快照总数 */
|
|
1023
|
+
getSnapshotCount() {
|
|
1024
|
+
return this.snapshots.length;
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* 获取所有快照(只读)
|
|
1028
|
+
*
|
|
1029
|
+
* 用于 UI 展示历史记录列表。
|
|
1030
|
+
*/
|
|
1031
|
+
getSnapshots() {
|
|
1032
|
+
return this.snapshots;
|
|
1033
|
+
}
|
|
1034
|
+
// ── 手动快照 ──────────────────────────────────────
|
|
1035
|
+
/**
|
|
1036
|
+
* 立即拍一个快照(不经过批处理)
|
|
1037
|
+
*
|
|
1038
|
+
* Engine 可在关键时刻(如 loadProject 后)主动调用。
|
|
1039
|
+
*
|
|
1040
|
+
* @param label - 操作标签
|
|
1041
|
+
*/
|
|
1042
|
+
takeSnapshot(s) {
|
|
1043
|
+
const e = this.getActiveBlock();
|
|
1044
|
+
if (!e) return;
|
|
1045
|
+
const t = {
|
|
1046
|
+
id: K(),
|
|
1047
|
+
timestamp: Date.now(),
|
|
1048
|
+
label: s,
|
|
1049
|
+
blockId: e.id,
|
|
1050
|
+
data: e.toJSON()
|
|
1051
|
+
};
|
|
1052
|
+
this.pushSnapshot(t);
|
|
1053
|
+
}
|
|
1054
|
+
// ── 内部 ──────────────────────────────────────────
|
|
1055
|
+
/**
|
|
1056
|
+
* 设置事件监听
|
|
1057
|
+
*
|
|
1058
|
+
* 监听 EVENT_NODE_CHANGE 和 EVENT_BLOCK_CHANGE,
|
|
1059
|
+
* 触发批处理快照。恢复期间(isRestoring=true)不录制。
|
|
1060
|
+
*/
|
|
1061
|
+
setupListeners() {
|
|
1062
|
+
const s = () => {
|
|
1063
|
+
this.isRestoring || this.scheduleSnapshot("node:change");
|
|
1064
|
+
};
|
|
1065
|
+
u.on(k, s), this.unsubscribers.push(() => u.off(k, s));
|
|
1066
|
+
const e = () => {
|
|
1067
|
+
this.isRestoring || this.scheduleSnapshot("block:change");
|
|
1068
|
+
};
|
|
1069
|
+
u.on(U, e), this.unsubscribers.push(() => u.off(U, e));
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* 调度快照(批处理)
|
|
1073
|
+
*
|
|
1074
|
+
* 首次调用时记录标签,启动定时器。
|
|
1075
|
+
* 定时器到期前的新调用会重置定时器(延迟拍照)。
|
|
1076
|
+
* 定时器到期后执行一次 takeSnapshot。
|
|
1077
|
+
*/
|
|
1078
|
+
scheduleSnapshot(s) {
|
|
1079
|
+
this.pendingLabel || (this.pendingLabel = s), this.batchTimer && clearTimeout(this.batchTimer), this.batchTimer = setTimeout(() => {
|
|
1080
|
+
this.batchTimer = null, this.takeSnapshot(this.pendingLabel), this.pendingLabel = "";
|
|
1081
|
+
}, this.batchWindow);
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* 压入快照
|
|
1085
|
+
*
|
|
1086
|
+
* 截断当前位置之后的所有快照(undo 后的新操作会覆盖 redo 栈)。
|
|
1087
|
+
* 超出上限时移除最旧的快照。
|
|
1088
|
+
* 广播 EVENT_HISTORY_CHANGE。
|
|
1089
|
+
*/
|
|
1090
|
+
pushSnapshot(s) {
|
|
1091
|
+
this.cursor < this.snapshots.length - 1 && (this.snapshots = this.snapshots.slice(0, this.cursor + 1)), this.snapshots.push(s), this.snapshots.length > this.maxSize && this.snapshots.shift(), this.cursor = this.snapshots.length - 1, this.emitChange();
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* 恢复到指定快照
|
|
1095
|
+
*
|
|
1096
|
+
* 1. 标记 isRestoring(防止恢复事件触发新快照)
|
|
1097
|
+
* 2. 广播 EVENT_HISTORY_RESTORE(恢复开始)
|
|
1098
|
+
* 3. 用快照数据 fromJSON 还原 BlockModel
|
|
1099
|
+
* 4. 将还原的数据逐字段写回当前 Block
|
|
1100
|
+
* 5. 更新游标位置
|
|
1101
|
+
* 6. 广播 EVENT_HISTORY_RESTORE(恢复完成)
|
|
1102
|
+
* 7. 重置 isRestoring
|
|
1103
|
+
*
|
|
1104
|
+
* @param snapshot - 目标快照
|
|
1105
|
+
*/
|
|
1106
|
+
restore(s) {
|
|
1107
|
+
const e = this.getActiveBlock();
|
|
1108
|
+
if (!e) return;
|
|
1109
|
+
this.isRestoring = !0, u.emit(L, {
|
|
1110
|
+
snapshotId: s.id,
|
|
1111
|
+
blockId: s.blockId
|
|
1112
|
+
});
|
|
1113
|
+
const t = fe.fromJSON(s.data);
|
|
1114
|
+
e.rootNode = t.rootNode, e.state = t.state, e.computed = t.computed, e.methods = t.methods, e.watch = t.watch, e.css = t.css, e.props = t.props, e.emits = t.emits, e.expose = t.expose, e.slots = t.slots, e.lifecycleHooks = t.lifecycleHooks, e.inject = t.inject, this.cursor = this.snapshots.indexOf(s), u.emit(L, {
|
|
1115
|
+
snapshotId: s.id,
|
|
1116
|
+
blockId: s.blockId
|
|
1117
|
+
}), this.isRestoring = !1, this.emitChange();
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* 广播 EVENT_HISTORY_CHANGE
|
|
1121
|
+
*
|
|
1122
|
+
* 通知 UI 层更新撤销/重做按钮状态。
|
|
1123
|
+
*/
|
|
1124
|
+
emitChange() {
|
|
1125
|
+
u.emit(je, {
|
|
1126
|
+
cursor: this.cursor,
|
|
1127
|
+
total: this.snapshots.length,
|
|
1128
|
+
canUndo: this.canUndo(),
|
|
1129
|
+
canRedo: this.canRedo()
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* 销毁 History,释放所有资源
|
|
1134
|
+
*
|
|
1135
|
+
* 取消事件订阅,清除定时器,清空快照栈。
|
|
1136
|
+
*/
|
|
1137
|
+
dispose() {
|
|
1138
|
+
for (const s of this.unsubscribers)
|
|
1139
|
+
s();
|
|
1140
|
+
this.unsubscribers = [], this.batchTimer && (clearTimeout(this.batchTimer), this.batchTimer = null), this.snapshots = [], this.cursor = -1;
|
|
1141
|
+
}
|
|
1142
|
+
}, ce = class ve {
|
|
1143
|
+
/** 项目唯一标识(自动生成,可通过 options.id 指定用于反序列化) */
|
|
1144
|
+
id;
|
|
1145
|
+
/**
|
|
1146
|
+
* 项目名称
|
|
1147
|
+
*
|
|
1148
|
+
* 用于显示项目标题,代码生成时作为项目标识。
|
|
1149
|
+
*/
|
|
1150
|
+
name;
|
|
1151
|
+
/**
|
|
1152
|
+
* 页面/组件列表
|
|
1153
|
+
*
|
|
1154
|
+
* 每个 BlockModel 代表一个独立的页面或可复用组件。
|
|
1155
|
+
* 页面通过 route 关联到 URL 路径。
|
|
1156
|
+
*/
|
|
1157
|
+
pages;
|
|
1158
|
+
/**
|
|
1159
|
+
* 当前激活页面的 ID
|
|
1160
|
+
*
|
|
1161
|
+
* 画布渲染器根据此字段决定展示哪个页面。
|
|
1162
|
+
* 初始为空字符串,添加第一个页面时自动设置。
|
|
1163
|
+
* 切换页面时由 switchPage() 更新。
|
|
1164
|
+
*/
|
|
1165
|
+
activePageId;
|
|
1166
|
+
/**
|
|
1167
|
+
* 外部依赖声明列表
|
|
1168
|
+
*
|
|
1169
|
+
* 声明项目运行时需要的 npm 包,代码生成时转换为 import 语句。
|
|
1170
|
+
* 同一个包名只会保留一个版本(后注册的覆盖先注册的)。
|
|
1171
|
+
*
|
|
1172
|
+
* @example `[{ package: 'lodash-es', version: '^4.17.21' }]`
|
|
1173
|
+
*/
|
|
1174
|
+
dependencies;
|
|
1175
|
+
/**
|
|
1176
|
+
* API 接口定义列表
|
|
1177
|
+
*
|
|
1178
|
+
* 定义项目中使用的 HTTP 接口,供页面内方法调用。
|
|
1179
|
+
* 代码生成时可根据这些定义生成 API 请求函数。
|
|
1180
|
+
*/
|
|
1181
|
+
apis;
|
|
1182
|
+
/**
|
|
1183
|
+
* 创建 ProjectModel 实例
|
|
1184
|
+
*
|
|
1185
|
+
* @param name - 项目名称
|
|
1186
|
+
* @param options - 可选配置
|
|
1187
|
+
* @param options.id - 指定 ID(用于反序列化),默认自动生成
|
|
1188
|
+
*/
|
|
1189
|
+
constructor(e, t) {
|
|
1190
|
+
this.id = t?.id ?? K(), this.name = e, this.pages = [], this.activePageId = "", this.dependencies = [], this.apis = [];
|
|
1191
|
+
}
|
|
1192
|
+
// ── 页面管理 ───────────────────────────────────────────
|
|
1193
|
+
/**
|
|
1194
|
+
* 添加页面到项目
|
|
1195
|
+
*
|
|
1196
|
+
* 将页面追加到 pages 列表末尾。如果是第一个页面,自动设为激活页面。
|
|
1197
|
+
* 广播 EVENT_PROJECT_LOAD 事件。
|
|
1198
|
+
*
|
|
1199
|
+
* @param page - 要添加的 BlockModel 实例
|
|
1200
|
+
*/
|
|
1201
|
+
addPage(e) {
|
|
1202
|
+
this.pages.push(e), this.pages.length === 1 && (this.activePageId = e.id), this._emitChanged();
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* 从项目中移除指定页面
|
|
1206
|
+
*
|
|
1207
|
+
* 如果移除的是当前激活页面,自动切换到剩余的第一个页面。
|
|
1208
|
+
* 广播 EVENT_PROJECT_LOAD 事件。
|
|
1209
|
+
*
|
|
1210
|
+
* @param pageId - 要移除的页面 ID
|
|
1211
|
+
*/
|
|
1212
|
+
removePage(e) {
|
|
1213
|
+
const t = this.pages.findIndex((n) => n.id === e);
|
|
1214
|
+
t !== -1 && (this.pages.splice(t, 1), this.activePageId === e && (this.activePageId = this.pages.length > 0 ? this.pages[0].id : ""), this._emitChanged());
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* 切换当前激活页面
|
|
1218
|
+
*
|
|
1219
|
+
* 画布渲染器会根据 activePageId 展示对应的页面。
|
|
1220
|
+
* 广播 EVENT_PAGE_SWITCH 事件(含 fromPageId / toPageId)。
|
|
1221
|
+
*
|
|
1222
|
+
* @param pageId - 目标页面 ID(必须存在于 pages 中)
|
|
1223
|
+
*/
|
|
1224
|
+
switchPage(e) {
|
|
1225
|
+
if (!this.pages.find((i) => i.id === e)) return;
|
|
1226
|
+
const n = this.activePageId;
|
|
1227
|
+
this.activePageId = e, u.emit(J, {
|
|
1228
|
+
fromPageId: n,
|
|
1229
|
+
toPageId: e
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* 获取当前激活的页面
|
|
1234
|
+
*
|
|
1235
|
+
* @returns 当前激活的 BlockModel 实例,无激活页面时返回 null
|
|
1236
|
+
*/
|
|
1237
|
+
getActivePage() {
|
|
1238
|
+
return this.pages.find((e) => e.id === this.activePageId) ?? null;
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* 根据 ID 获取指定页面
|
|
1242
|
+
*
|
|
1243
|
+
* @param pageId - 页面 ID
|
|
1244
|
+
* @returns 对应的 BlockModel 实例,不存在时返回 null
|
|
1245
|
+
*/
|
|
1246
|
+
getPage(e) {
|
|
1247
|
+
return this.pages.find((t) => t.id === e) ?? null;
|
|
1248
|
+
}
|
|
1249
|
+
// ── 依赖管理 ───────────────────────────────────────────
|
|
1250
|
+
/**
|
|
1251
|
+
* 添加或更新外部依赖
|
|
1252
|
+
*
|
|
1253
|
+
* 如果同名包已存在,更新其版本号;否则添加新依赖。
|
|
1254
|
+
* 广播 EVENT_PROJECT_LOAD 事件。
|
|
1255
|
+
*
|
|
1256
|
+
* @param dep - 依赖声明(包名 + 版本)
|
|
1257
|
+
*
|
|
1258
|
+
* @example
|
|
1259
|
+
* ```ts
|
|
1260
|
+
* project.addDependency({ package: 'dayjs', version: '^1.11.10' })
|
|
1261
|
+
* ```
|
|
1262
|
+
*/
|
|
1263
|
+
addDependency(e) {
|
|
1264
|
+
const t = this.dependencies.find((n) => n.package === e.package);
|
|
1265
|
+
t ? t.version = e.version : this.dependencies.push({ ...e }), this._emitChanged();
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* 移除指定外部依赖
|
|
1269
|
+
*
|
|
1270
|
+
* @param packageName - 要移除的包名
|
|
1271
|
+
*/
|
|
1272
|
+
removeDependency(e) {
|
|
1273
|
+
const t = this.dependencies.findIndex((n) => n.package === e);
|
|
1274
|
+
t > -1 && (this.dependencies.splice(t, 1), this._emitChanged());
|
|
1275
|
+
}
|
|
1276
|
+
// ── API 管理 ───────────────────────────────────────────
|
|
1277
|
+
/**
|
|
1278
|
+
* 添加 API 接口定义
|
|
1279
|
+
*
|
|
1280
|
+
* @param api - API 定义对象(会浅拷贝存储)
|
|
1281
|
+
*/
|
|
1282
|
+
addApi(e) {
|
|
1283
|
+
this.apis.push({ ...e }), this._emitChanged();
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* 移除指定 API 接口定义
|
|
1287
|
+
*
|
|
1288
|
+
* @param apiId - 要移除的 API ID
|
|
1289
|
+
*/
|
|
1290
|
+
removeApi(e) {
|
|
1291
|
+
const t = this.apis.findIndex((n) => n.id === e);
|
|
1292
|
+
t > -1 && (this.apis.splice(t, 1), this._emitChanged());
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* 更新指定 API 接口定义(部分更新)
|
|
1296
|
+
*
|
|
1297
|
+
* @param apiId - 要更新的 API ID
|
|
1298
|
+
* @param updates - 要更新的字段
|
|
1299
|
+
*/
|
|
1300
|
+
updateApi(e, t) {
|
|
1301
|
+
const n = this.apis.find((i) => i.id === e);
|
|
1302
|
+
n && (Object.assign(n, t), this._emitChanged());
|
|
1303
|
+
}
|
|
1304
|
+
// ── 序列化 ─────────────────────────────────────────────
|
|
1305
|
+
/**
|
|
1306
|
+
* 序列化为 JSON 对象
|
|
1307
|
+
*
|
|
1308
|
+
* 递归序列化所有页面。用于持久化存储、项目导出、跨进程传输。
|
|
1309
|
+
*
|
|
1310
|
+
* @returns 可 JSON.stringify 的 ProjectModelJSON 对象
|
|
1311
|
+
*/
|
|
1312
|
+
toJSON() {
|
|
1313
|
+
return {
|
|
1314
|
+
id: this.id,
|
|
1315
|
+
name: this.name,
|
|
1316
|
+
pages: this.pages.map((e) => e.toJSON()),
|
|
1317
|
+
activePageId: this.activePageId,
|
|
1318
|
+
dependencies: this.dependencies.map((e) => ({ ...e })),
|
|
1319
|
+
apis: this.apis.map((e) => ({ ...e }))
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* 从 JSON 对象反序列化为 ProjectModel 实例
|
|
1324
|
+
*
|
|
1325
|
+
* 递归还原所有页面及其节点树。
|
|
1326
|
+
*
|
|
1327
|
+
* @param json - 序列化的 ProjectModelJSON 对象
|
|
1328
|
+
* @returns 还原的 ProjectModel 实例
|
|
1329
|
+
*/
|
|
1330
|
+
static fromJSON(e) {
|
|
1331
|
+
const t = new ve(e.name, { id: e.id });
|
|
1332
|
+
return t.pages = e.pages.map((n) => fe.fromJSON(n)), t.activePageId = e.activePageId, t.dependencies = e.dependencies.map((n) => ({ ...n })), t.apis = e.apis.map((n) => ({ ...n })), t;
|
|
1333
|
+
}
|
|
1334
|
+
// ── 内部 ────────────────────────────────────────────────
|
|
1335
|
+
/** 广播项目变更事件 */
|
|
1336
|
+
_emitChanged() {
|
|
1337
|
+
u.emit(G, { projectId: this.id });
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
function Ve(s) {
|
|
1341
|
+
if (s !== "undefined") {
|
|
1342
|
+
if (s === "null") return null;
|
|
1343
|
+
if (s === "true") return !0;
|
|
1344
|
+
if (s === "false") return !1;
|
|
1345
|
+
if (s === "") return "";
|
|
1346
|
+
if (/^-?\d+(\.\d+)?$/.test(s)) return Number(s);
|
|
1347
|
+
if (s.startsWith("'") && s.endsWith("'") || s.startsWith('"') && s.endsWith('"'))
|
|
1348
|
+
return s.slice(1, -1);
|
|
1349
|
+
if (s.startsWith("{") || s.startsWith("[")) {
|
|
1350
|
+
try {
|
|
1351
|
+
return JSON.parse(s);
|
|
1352
|
+
} catch {
|
|
1353
|
+
}
|
|
1354
|
+
try {
|
|
1355
|
+
return new Function(`return (${s})`)();
|
|
1356
|
+
} catch {
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
return s;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
class Y {
|
|
1363
|
+
/**
|
|
1364
|
+
* 响应式状态对象
|
|
1365
|
+
*
|
|
1366
|
+
* 所有的 state 声明都会挂载在此对象上。
|
|
1367
|
+
* 由 block.ts 在 setup() 中初始化,通过 provide/inject 传递给子组件。
|
|
1368
|
+
*/
|
|
1369
|
+
state;
|
|
1370
|
+
/**
|
|
1371
|
+
* DOM 引用存储(设计态使用)
|
|
1372
|
+
*
|
|
1373
|
+
* key 为节点 ID,value 为 DOM 元素。
|
|
1374
|
+
* 通过 ref() 方法绑定,设计态用于选中、拖拽、高亮等交互。
|
|
1375
|
+
*/
|
|
1376
|
+
refs;
|
|
1377
|
+
constructor() {
|
|
1378
|
+
this.state = ne({}), this.refs = {};
|
|
1379
|
+
}
|
|
1380
|
+
// ── 表达式求值 ─────────────────────────────────────
|
|
1381
|
+
/**
|
|
1382
|
+
* 求值表达式
|
|
1383
|
+
*
|
|
1384
|
+
* 在 state 上下文中执行任意表达式字符串。
|
|
1385
|
+
* 用于 props 求值、指令表达式(v-if/v-show)、模板插值等。
|
|
1386
|
+
*
|
|
1387
|
+
* @param expression - 表达式字符串(如 'count > 5', 'form.name')
|
|
1388
|
+
* @returns 求值结果,失败返回 undefined
|
|
1389
|
+
*/
|
|
1390
|
+
evaluate(e) {
|
|
1391
|
+
try {
|
|
1392
|
+
return new Function("__ctx__", `with(__ctx__) { return (${e}) }`)(this.state);
|
|
1393
|
+
} catch {
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* 解析函数
|
|
1399
|
+
*
|
|
1400
|
+
* 将函数体字符串解析为可执行函数。
|
|
1401
|
+
* 函数体内的 this 指向 state,可以访问所有状态变量。
|
|
1402
|
+
*
|
|
1403
|
+
* @param body - 函数体字符串(如 'count.value++')
|
|
1404
|
+
* @param params - 参数名列表(如 ['newVal', 'oldVal'])
|
|
1405
|
+
* @returns 解析后的函数,解析失败返回空函数
|
|
1406
|
+
*/
|
|
1407
|
+
parseFunction(e, t = []) {
|
|
1408
|
+
try {
|
|
1409
|
+
const n = new Function("__ctx__", ...t, `with(__ctx__) { ${e} }`);
|
|
1410
|
+
return (...i) => n(this.state, ...i);
|
|
1411
|
+
} catch {
|
|
1412
|
+
return console.warn(`[RenderContext] Failed to parse function: ${e}`), () => {
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
// ── 赋值(v-model 用) ────────────────────────────
|
|
1417
|
+
/**
|
|
1418
|
+
* 赋值表达式
|
|
1419
|
+
*
|
|
1420
|
+
* 支持 dotted path 赋值,用于 v-model 双向绑定。
|
|
1421
|
+
*
|
|
1422
|
+
* @param path - 赋值目标(如 'form.name', 'count')
|
|
1423
|
+
* @param value - 新值
|
|
1424
|
+
*/
|
|
1425
|
+
assign(e, t) {
|
|
1426
|
+
if (!e.includes(".")) {
|
|
1427
|
+
this.state[e] = t;
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
const n = e.split("."), i = n.pop(), r = n.join(".");
|
|
1431
|
+
try {
|
|
1432
|
+
const o = this.evaluate(r);
|
|
1433
|
+
o && typeof o == "object" && (o[i] = t);
|
|
1434
|
+
} catch {
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
// ── 作用域克隆(v-for 用) ────────────────────────
|
|
1438
|
+
/**
|
|
1439
|
+
* 克隆当前上下文(用于 v-for 迭代)
|
|
1440
|
+
*
|
|
1441
|
+
* 继承父作用域的所有状态,并注入局部变量(循环变量 item/index)。
|
|
1442
|
+
*
|
|
1443
|
+
* @param locals - 要注入的局部变量
|
|
1444
|
+
* @returns 新的 RenderContext 实例
|
|
1445
|
+
*/
|
|
1446
|
+
clone(e) {
|
|
1447
|
+
const t = new Y();
|
|
1448
|
+
if (t.state = this.state, e)
|
|
1449
|
+
for (const [n, i] of Object.entries(e))
|
|
1450
|
+
t.state[n] = i;
|
|
1451
|
+
return t;
|
|
1452
|
+
}
|
|
1453
|
+
// ── DOM 引用(设计态用) ───────────────────────────
|
|
1454
|
+
/**
|
|
1455
|
+
* 创建 DOM ref 绑定函数
|
|
1456
|
+
*
|
|
1457
|
+
* 返回一个函数,Vue 会将 DOM 元素传入。
|
|
1458
|
+
* 设计态通过此函数获取每个节点的 DOM 元素,用于选中/高亮/拖拽。
|
|
1459
|
+
*
|
|
1460
|
+
* @param nodeId - 节点 ID
|
|
1461
|
+
* @returns ref 回调函数
|
|
1462
|
+
*/
|
|
1463
|
+
ref(e) {
|
|
1464
|
+
return (t) => {
|
|
1465
|
+
if (!t) return;
|
|
1466
|
+
const n = () => {
|
|
1467
|
+
if (t instanceof HTMLElement) return t;
|
|
1468
|
+
if (t.$el instanceof HTMLElement) return t.$el;
|
|
1469
|
+
if (t.$?.subTree?.el instanceof HTMLElement) return t.$.subTree.el;
|
|
1470
|
+
const r = t.$?.subTree;
|
|
1471
|
+
if (r) {
|
|
1472
|
+
const o = ee(r);
|
|
1473
|
+
if (o) return o;
|
|
1474
|
+
}
|
|
1475
|
+
return null;
|
|
1476
|
+
}, i = () => {
|
|
1477
|
+
const r = n();
|
|
1478
|
+
return r ? (this.refs[e] = r, this.markElement(r, e), !0) : !1;
|
|
1479
|
+
};
|
|
1480
|
+
i() || we(() => i());
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
// ── 设计态 DOM 标记 ────────────────────────────────
|
|
1484
|
+
/**
|
|
1485
|
+
* 给 DOM 元素打设计态标记
|
|
1486
|
+
*
|
|
1487
|
+
* 在设计态模式下,给每个渲染出的 DOM 元素注入:
|
|
1488
|
+
* - `__nodeId__` dataset:节点 ID,用于事件定位
|
|
1489
|
+
* - `draggable`:允许拖拽
|
|
1490
|
+
* - CSS class:设计态样式(可选)
|
|
1491
|
+
*
|
|
1492
|
+
* @param el - DOM 元素
|
|
1493
|
+
* @param nodeId - 节点 ID
|
|
1494
|
+
*/
|
|
1495
|
+
markElement(e, t) {
|
|
1496
|
+
e && (e.dataset.__nodeId__ = t, e.draggable = !0);
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* 清理所有引用
|
|
1500
|
+
*/
|
|
1501
|
+
dispose() {
|
|
1502
|
+
this.refs = {};
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
function ee(s) {
|
|
1506
|
+
if (!s) return null;
|
|
1507
|
+
if (s.el instanceof HTMLElement) return s.el;
|
|
1508
|
+
const e = s.children ?? s.dynamicChildren;
|
|
1509
|
+
if (Array.isArray(e))
|
|
1510
|
+
for (const t of e) {
|
|
1511
|
+
const n = ee(t);
|
|
1512
|
+
if (n) return n;
|
|
1513
|
+
}
|
|
1514
|
+
return s.component?.subTree ? ee(s.component.subTree) : null;
|
|
1515
|
+
}
|
|
1516
|
+
function Me(s) {
|
|
1517
|
+
const { materialStore: e, schemaLoader: t } = s;
|
|
1518
|
+
return {
|
|
1519
|
+
load(n, i) {
|
|
1520
|
+
if (i?.startsWith("schema:")) {
|
|
1521
|
+
const o = i.slice(7);
|
|
1522
|
+
return t ? $e(o, t) : (console.warn(`[Loader] schemaLoader not configured, cannot load: ${i}`), te(n));
|
|
1523
|
+
}
|
|
1524
|
+
return e.getComponent(n) || (console.warn(`[Loader] Component not found: ${n}`), te(n));
|
|
1525
|
+
}
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
async function $e(s, e) {
|
|
1529
|
+
try {
|
|
1530
|
+
const t = await e(s), { createRenderer: n } = await Promise.resolve().then(() => Ke);
|
|
1531
|
+
return n(t);
|
|
1532
|
+
} catch (t) {
|
|
1533
|
+
return console.warn(`[Loader] Failed to load schema: ${s}`, t), te(`Schema:${s}`);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
function te(s) {
|
|
1537
|
+
return _({
|
|
1538
|
+
name: "PlaceholderComponent",
|
|
1539
|
+
setup() {
|
|
1540
|
+
return () => W("div", {
|
|
1541
|
+
class: "sunny-renderer-placeholder",
|
|
1542
|
+
style: {
|
|
1543
|
+
padding: "8px 12px",
|
|
1544
|
+
border: "1px dashed #ccc",
|
|
1545
|
+
borderRadius: "4px",
|
|
1546
|
+
color: "#999",
|
|
1547
|
+
fontSize: "12px"
|
|
1548
|
+
}
|
|
1549
|
+
}, [`未注册组件: ${s}`]);
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
function He(s) {
|
|
1554
|
+
const e = s.match(/^\(?\s*(\w+)\s*(?:,\s*(\w+))?\s*\)?\s+in\s+(.+)$/);
|
|
1555
|
+
return e ? {
|
|
1556
|
+
itemName: e[1],
|
|
1557
|
+
indexName: e[2] || void 0,
|
|
1558
|
+
listExpr: e[3].trim()
|
|
1559
|
+
} : null;
|
|
1560
|
+
}
|
|
1561
|
+
function We(s, e) {
|
|
1562
|
+
if (!e) return { ...s };
|
|
1563
|
+
const t = {};
|
|
1564
|
+
for (const [n, i] of Object.entries(s))
|
|
1565
|
+
if (typeof i == "string" && e.state[i] !== void 0) {
|
|
1566
|
+
const r = e.state[i];
|
|
1567
|
+
t[n] = r?.__v_isRef ? r.value : r;
|
|
1568
|
+
} else
|
|
1569
|
+
t[n] = i;
|
|
1570
|
+
return t;
|
|
1571
|
+
}
|
|
1572
|
+
function Ue(s, e) {
|
|
1573
|
+
if (!e || !Object.keys(s).length) return {};
|
|
1574
|
+
const t = {};
|
|
1575
|
+
for (const [n, i] of Object.entries(s)) {
|
|
1576
|
+
const r = `on${n.charAt(0).toUpperCase()}${n.slice(1)}`, o = e.state[i];
|
|
1577
|
+
if (o && typeof o == "function")
|
|
1578
|
+
t[r] = o;
|
|
1579
|
+
else {
|
|
1580
|
+
const c = e.parseFunction(`return (${i})`, ["__event__"]);
|
|
1581
|
+
t[r] = (...l) => {
|
|
1582
|
+
try {
|
|
1583
|
+
const d = c(l[0]);
|
|
1584
|
+
typeof d == "function" && d(...l);
|
|
1585
|
+
} catch {
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return t;
|
|
1591
|
+
}
|
|
1592
|
+
function Je(s, e) {
|
|
1593
|
+
if (!e) return {};
|
|
1594
|
+
const t = {};
|
|
1595
|
+
for (const n of s) {
|
|
1596
|
+
if (n.name !== "model") continue;
|
|
1597
|
+
const i = n.arg || "modelValue", r = `onUpdate:${i}`;
|
|
1598
|
+
t[i] = e.evaluate(n.value), t[r] = (o) => {
|
|
1599
|
+
e.assign(n.value, o);
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
return t;
|
|
1603
|
+
}
|
|
1604
|
+
function ie(s, e, t, n) {
|
|
1605
|
+
const i = n === "preview", r = s.directives, o = r.find((l) => l.name === "if");
|
|
1606
|
+
if (o && !(!i || e.evaluate(o.value)))
|
|
1607
|
+
return null;
|
|
1608
|
+
const c = r.find((l) => l.name === "for");
|
|
1609
|
+
if (c && i) {
|
|
1610
|
+
const l = He(c.value);
|
|
1611
|
+
if (l) {
|
|
1612
|
+
const d = e.evaluate(l.listExpr);
|
|
1613
|
+
if (Array.isArray(d))
|
|
1614
|
+
return d.map((h, y) => (e.clone({
|
|
1615
|
+
[l.itemName]: h,
|
|
1616
|
+
...l.indexName ? { [l.indexName]: y } : {}
|
|
1617
|
+
}), Z(s, e, t, n)));
|
|
1618
|
+
}
|
|
1619
|
+
return Z(s, e, t, n);
|
|
1620
|
+
}
|
|
1621
|
+
return Z(s, e, t, n);
|
|
1622
|
+
}
|
|
1623
|
+
function Z(s, e, t, n) {
|
|
1624
|
+
const i = n === "preview", r = s.directives, o = t.load(s.name, s.from);
|
|
1625
|
+
!o || typeof o == "string" && o === s.name && t.load(s.name);
|
|
1626
|
+
const c = i ? e : null, l = {
|
|
1627
|
+
...We(s.props, c),
|
|
1628
|
+
...Ue(s.events, c),
|
|
1629
|
+
...Je(r, c)
|
|
1630
|
+
}, d = r.find((b) => b.name === "show");
|
|
1631
|
+
d && i && (e.evaluate(d.value) || (l.style = { ...l.style, display: "none" })), n === "design" && (l.ref = e.ref(s.id));
|
|
1632
|
+
const h = s.children?.length > 0, y = Object.keys(s.slots ?? {}).length > 0;
|
|
1633
|
+
if (!h && !y)
|
|
1634
|
+
return W(o, l);
|
|
1635
|
+
const f = {};
|
|
1636
|
+
if (h && (f.default = () => s.children.map(
|
|
1637
|
+
(b) => de(b, e, t, n)
|
|
1638
|
+
)), y)
|
|
1639
|
+
for (const [b, v] of Object.entries(s.slots))
|
|
1640
|
+
v?.length && (f[b] = () => v.map(
|
|
1641
|
+
(C) => de(C, e, t, n)
|
|
1642
|
+
));
|
|
1643
|
+
return W(o, l, f);
|
|
1644
|
+
}
|
|
1645
|
+
function de(s, e, t, n) {
|
|
1646
|
+
const i = ie(s, e, t, n);
|
|
1647
|
+
return i === null ? W("template") : Array.isArray(i) ? W("div", { class: "sunny-renderer-fragment" }, i) : i;
|
|
1648
|
+
}
|
|
1649
|
+
const Fe = {
|
|
1650
|
+
onMounted: se,
|
|
1651
|
+
onUnmounted: he,
|
|
1652
|
+
onUpdated: Se
|
|
1653
|
+
};
|
|
1654
|
+
function qe(s, e, t) {
|
|
1655
|
+
const n = t?.mode ?? "preview";
|
|
1656
|
+
return _({
|
|
1657
|
+
name: s.name || "BlockRenderer",
|
|
1658
|
+
setup() {
|
|
1659
|
+
const i = new Y();
|
|
1660
|
+
for (const o of s.state) {
|
|
1661
|
+
const c = Ve(o.value);
|
|
1662
|
+
i.state[o.name] = Ge(c) ? A(c) : ne(c);
|
|
1663
|
+
}
|
|
1664
|
+
for (const o of s.methods)
|
|
1665
|
+
i.state[o.name] = i.parseFunction(o.value, o.params);
|
|
1666
|
+
for (const o of s.computed) {
|
|
1667
|
+
i.evaluate.bind(i);
|
|
1668
|
+
const c = o.value;
|
|
1669
|
+
i.state[o.name] = x(() => {
|
|
1670
|
+
try {
|
|
1671
|
+
return new Function("__ctx__", `with(__ctx__) { return (${c}) }`)(i.state);
|
|
1672
|
+
} catch {
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
const r = [];
|
|
1678
|
+
for (const o of s.watch) {
|
|
1679
|
+
const c = i.state[o.name];
|
|
1680
|
+
if (!c) continue;
|
|
1681
|
+
const l = o.params ?? ["newVal", "oldVal"], d = Ce(
|
|
1682
|
+
c,
|
|
1683
|
+
(...h) => {
|
|
1684
|
+
i.parseFunction(o.value, l)(...h);
|
|
1685
|
+
}
|
|
1686
|
+
);
|
|
1687
|
+
r.push(d);
|
|
1688
|
+
}
|
|
1689
|
+
for (const o of s.lifecycleHooks) {
|
|
1690
|
+
const c = Fe[o.name];
|
|
1691
|
+
if (!c) {
|
|
1692
|
+
console.warn(`[createRenderer] Unknown lifecycle hook: ${o.name}`);
|
|
1693
|
+
continue;
|
|
1694
|
+
}
|
|
1695
|
+
c(() => {
|
|
1696
|
+
i.parseFunction(o.value)();
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
return he(() => {
|
|
1700
|
+
r.forEach((o) => o()), i.dispose();
|
|
1701
|
+
}), { ctx: i };
|
|
1702
|
+
},
|
|
1703
|
+
render() {
|
|
1704
|
+
return s.rootNode ? e ? ie(s.rootNode, this.ctx, e, n) : (console.warn("[createRenderer] No loader provided, using fallback"), null) : null;
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
function Ge(s) {
|
|
1709
|
+
return typeof s == "number" || typeof s == "string" || typeof s == "boolean" || s === null || s === void 0;
|
|
1710
|
+
}
|
|
1711
|
+
const Ke = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1712
|
+
__proto__: null,
|
|
1713
|
+
createRenderer: qe
|
|
1714
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1715
|
+
class Ye {
|
|
1716
|
+
/** 组件元数据映射(name → ComponentMeta) */
|
|
1717
|
+
metas = /* @__PURE__ */ new Map();
|
|
1718
|
+
// ── 注册 ──────────────────────────────────────────
|
|
1719
|
+
/**
|
|
1720
|
+
* 注册一个组件物料
|
|
1721
|
+
*
|
|
1722
|
+
* @param meta - 组件元数据
|
|
1723
|
+
*/
|
|
1724
|
+
register(e) {
|
|
1725
|
+
this.metas.has(e.name) && console.warn(`[MaterialStore] Overwriting existing material: ${e.name}`), this.metas.set(e.name, e);
|
|
1726
|
+
}
|
|
1727
|
+
/**
|
|
1728
|
+
* 批量注册组件物料
|
|
1729
|
+
*/
|
|
1730
|
+
registerAll(e) {
|
|
1731
|
+
for (const t of e)
|
|
1732
|
+
this.register(t);
|
|
1733
|
+
}
|
|
1734
|
+
// ── 查询(渲染器用) ────────────────────────────────
|
|
1735
|
+
/**
|
|
1736
|
+
* 获取组件元数据
|
|
1737
|
+
*/
|
|
1738
|
+
getMeta(e) {
|
|
1739
|
+
return this.metas.get(e) ?? null;
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* 获取 Vue 组件引用
|
|
1743
|
+
*
|
|
1744
|
+
* 替代原 ComponentRegistry.get()。
|
|
1745
|
+
*/
|
|
1746
|
+
getComponent(e) {
|
|
1747
|
+
return this.metas.get(e)?.component ?? null;
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* 检查组件是否已注册
|
|
1751
|
+
*/
|
|
1752
|
+
has(e) {
|
|
1753
|
+
return this.metas.has(e);
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* 获取所有组件名 → Vue 组件映射
|
|
1757
|
+
*
|
|
1758
|
+
* 用于将物料中的组件批量注册到 Vue App。
|
|
1759
|
+
*/
|
|
1760
|
+
getAllComponents() {
|
|
1761
|
+
return Array.from(this.metas.entries()).map(([e, t]) => [e, t.component]);
|
|
1762
|
+
}
|
|
1763
|
+
/**
|
|
1764
|
+
* 获取嵌套规则
|
|
1765
|
+
*/
|
|
1766
|
+
getNestingRules(e) {
|
|
1767
|
+
return this.metas.get(e)?.nestingRules ?? null;
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1770
|
+
* 获取拖入片段
|
|
1771
|
+
*/
|
|
1772
|
+
getSnippets(e) {
|
|
1773
|
+
return this.metas.get(e)?.snippets ?? [];
|
|
1774
|
+
}
|
|
1775
|
+
// ── 查询(设计器面板用) ────────────────────────────
|
|
1776
|
+
/**
|
|
1777
|
+
* 获取所有分组名(去重、排序)
|
|
1778
|
+
*
|
|
1779
|
+
* 用于左侧面板渲染分组标题。
|
|
1780
|
+
*/
|
|
1781
|
+
getGroups() {
|
|
1782
|
+
const e = /* @__PURE__ */ new Set();
|
|
1783
|
+
let t = 0;
|
|
1784
|
+
const n = /* @__PURE__ */ new Map();
|
|
1785
|
+
for (const i of this.metas.values())
|
|
1786
|
+
i.hidden || e.has(i.group) || (e.add(i.group), n.set(i.group, t++));
|
|
1787
|
+
return Array.from(e);
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* 按分组获取组件列表
|
|
1791
|
+
*
|
|
1792
|
+
* 用于左侧面板渲染各组下的组件。
|
|
1793
|
+
*/
|
|
1794
|
+
getByGroup(e) {
|
|
1795
|
+
return this.filterVisible().filter((t) => t.group === e);
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* 按分类获取组件列表
|
|
1799
|
+
*/
|
|
1800
|
+
getByCategory(e) {
|
|
1801
|
+
return this.filterVisible().filter((t) => t.category === e);
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* 获取所有可见组件(非隐藏)
|
|
1805
|
+
*/
|
|
1806
|
+
getAllVisible() {
|
|
1807
|
+
return this.filterVisible();
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* 搜索组件(按 name / title / description 匹配)
|
|
1811
|
+
*/
|
|
1812
|
+
search(e) {
|
|
1813
|
+
const t = e.toLowerCase();
|
|
1814
|
+
return this.filterVisible().filter(
|
|
1815
|
+
(n) => n.name.toLowerCase().includes(t) || n.title.toLowerCase().includes(t) || (n.description?.toLowerCase().includes(t) ?? !1)
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
// ── 嵌套判断(拖拽引擎用) ──────────────────────────
|
|
1819
|
+
/**
|
|
1820
|
+
* 判断能否将子组件放入目标容器
|
|
1821
|
+
*/
|
|
1822
|
+
canAcceptChild(e, t) {
|
|
1823
|
+
const n = this.metas.get(e);
|
|
1824
|
+
if (!n) return !1;
|
|
1825
|
+
const i = n.nestingRules;
|
|
1826
|
+
return !(!i.isContainer || i.deniedChildren?.includes(t) || i.allowedChildren && !i.allowedChildren.includes(t));
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* 判断组件能否作为某父组件的子节点
|
|
1830
|
+
*/
|
|
1831
|
+
canBeChildOf(e, t) {
|
|
1832
|
+
const n = this.metas.get(e);
|
|
1833
|
+
if (!n) return !1;
|
|
1834
|
+
const i = n.nestingRules;
|
|
1835
|
+
return i.allowedParents && !i.allowedParents.includes(t) ? !1 : this.canAcceptChild(t, e);
|
|
1836
|
+
}
|
|
1837
|
+
// ── 管理 ──────────────────────────────────────────
|
|
1838
|
+
/**
|
|
1839
|
+
* 取消注册
|
|
1840
|
+
*/
|
|
1841
|
+
unregister(e) {
|
|
1842
|
+
this.metas.delete(e);
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* 清空所有物料
|
|
1846
|
+
*/
|
|
1847
|
+
clear() {
|
|
1848
|
+
this.metas.clear();
|
|
1849
|
+
}
|
|
1850
|
+
/**
|
|
1851
|
+
* 获取已注册组件数量
|
|
1852
|
+
*/
|
|
1853
|
+
get size() {
|
|
1854
|
+
return this.metas.size;
|
|
1855
|
+
}
|
|
1856
|
+
// ── 内部 ──────────────────────────────────────────
|
|
1857
|
+
/** 过滤隐藏组件 */
|
|
1858
|
+
filterVisible() {
|
|
1859
|
+
return Array.from(this.metas.values()).filter((e) => !e.hidden);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
class Ze {
|
|
1863
|
+
/** 项目模型(持有所有页面和依赖) */
|
|
1864
|
+
project;
|
|
1865
|
+
/** 历史记录管理器(快照式撤销/重做) */
|
|
1866
|
+
history;
|
|
1867
|
+
/** 物料存储(组件元数据 + 运行时组件注册) */
|
|
1868
|
+
materialStore;
|
|
1869
|
+
/**
|
|
1870
|
+
* 当前选中的节点 ID
|
|
1871
|
+
*
|
|
1872
|
+
* 简单的字符串引用,不持有 NodeModel 实例。
|
|
1873
|
+
* 通过 getSelected() 查询实际的 NodeModel。
|
|
1874
|
+
*/
|
|
1875
|
+
_selectedNodeId = null;
|
|
1876
|
+
/**
|
|
1877
|
+
* 选中变更回调
|
|
1878
|
+
*
|
|
1879
|
+
* UI 层(SetterPanel、Designer)注册此回调以响应选中变化。
|
|
1880
|
+
* 替代了之前基于事件的 Selection 通知机制,更简单直接。
|
|
1881
|
+
*/
|
|
1882
|
+
onSelectionChange = null;
|
|
1883
|
+
/** 是否已初始化(loadProject 后为 true) */
|
|
1884
|
+
initialized = !1;
|
|
1885
|
+
/** 全局事件取消订阅函数列表 */
|
|
1886
|
+
unsubscribers = [];
|
|
1887
|
+
/**
|
|
1888
|
+
* 创建 Engine 实例
|
|
1889
|
+
*
|
|
1890
|
+
* @param options - 构造选项(必须提供 materialStore)
|
|
1891
|
+
*/
|
|
1892
|
+
constructor(e) {
|
|
1893
|
+
this.materialStore = e.materialStore, this.project = new ce("未命名项目"), this.history = new Le(
|
|
1894
|
+
() => this.project.getActivePage(),
|
|
1895
|
+
{
|
|
1896
|
+
maxSize: e.historyMaxSize,
|
|
1897
|
+
batchWindow: e.historyBatchWindow
|
|
1898
|
+
}
|
|
1899
|
+
);
|
|
1900
|
+
const t = () => {
|
|
1901
|
+
this._selectedNodeId = null, this.onSelectionChange?.();
|
|
1902
|
+
};
|
|
1903
|
+
u.on(J, t), this.unsubscribers.push(() => u.off(J, t));
|
|
1904
|
+
const n = (r) => {
|
|
1905
|
+
r.action === "remove" && this._selectedNodeId === r.nodeId && (this._selectedNodeId = null, this.onSelectionChange?.());
|
|
1906
|
+
};
|
|
1907
|
+
u.on(k, n), this.unsubscribers.push(() => u.off(k, n));
|
|
1908
|
+
const i = () => {
|
|
1909
|
+
};
|
|
1910
|
+
u.on(L, i), this.unsubscribers.push(() => u.off(L, i));
|
|
1911
|
+
}
|
|
1912
|
+
// ── 项目管理 ────────────────────────────────────────
|
|
1913
|
+
/**
|
|
1914
|
+
* 加载项目数据
|
|
1915
|
+
*
|
|
1916
|
+
* 从 ProjectModelJSON 反序列化并覆盖当前 project 的字段。
|
|
1917
|
+
* 不替换 project 引用本身(保持 Engine 其他子系统持有的一致性)。
|
|
1918
|
+
* 清空选中状态,广播 EVENT_PROJECT_LOAD。
|
|
1919
|
+
*
|
|
1920
|
+
* @param json - 项目序列化数据
|
|
1921
|
+
*/
|
|
1922
|
+
loadProject(e) {
|
|
1923
|
+
const t = ce.fromJSON(e);
|
|
1924
|
+
this.project.pages = t.pages, this.project.name = t.name, this.project.activePageId = t.activePageId, this.project.dependencies = t.dependencies, this.project.apis = t.apis, this._selectedNodeId = null, this.initialized = !0, u.emit(G, { projectId: this.project.id });
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* 获取当前激活页面的 BlockModel
|
|
1928
|
+
*
|
|
1929
|
+
* @returns 当前激活的 BlockModel,无页面时返回 null
|
|
1930
|
+
*/
|
|
1931
|
+
getActiveBlock() {
|
|
1932
|
+
return this.project.getActivePage();
|
|
1933
|
+
}
|
|
1934
|
+
/**
|
|
1935
|
+
* 获取当前激活页面的序列化数据
|
|
1936
|
+
*
|
|
1937
|
+
* @returns BlockModelJSON,无页面时返回 null
|
|
1938
|
+
*/
|
|
1939
|
+
getActiveBlockJSON() {
|
|
1940
|
+
return this.getActiveBlock()?.toJSON() ?? null;
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* 切换当前激活页面
|
|
1944
|
+
*
|
|
1945
|
+
* 委托给 ProjectModel.switchPage(),广播 EVENT_PAGE_SWITCH。
|
|
1946
|
+
* 构造函数中已监听此事件,自动清空选中。
|
|
1947
|
+
*
|
|
1948
|
+
* @param pageId - 目标页面 ID
|
|
1949
|
+
*/
|
|
1950
|
+
switchPage(e) {
|
|
1951
|
+
this.project.switchPage(e);
|
|
1952
|
+
}
|
|
1953
|
+
// ── 节点操作 ────────────────────────────────────────
|
|
1954
|
+
/**
|
|
1955
|
+
* 在指定父节点下添加子节点
|
|
1956
|
+
*
|
|
1957
|
+
* 委托给当前活跃 BlockModel 的 addNode()。
|
|
1958
|
+
* 触发 EVENT_NODE_CHANGE(action=add)。
|
|
1959
|
+
*
|
|
1960
|
+
* @param parentId - 父节点 ID
|
|
1961
|
+
* @param node - 要添加的 NodeModel 实例
|
|
1962
|
+
* @param index - 插入位置,默认追加到末尾
|
|
1963
|
+
*/
|
|
1964
|
+
addNode(e, t, n) {
|
|
1965
|
+
const i = this.getActiveBlock();
|
|
1966
|
+
i && i.addNode(e, t, n);
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* 从节点树中移除指定节点
|
|
1970
|
+
*
|
|
1971
|
+
* 委托给当前活跃 BlockModel 的 removeNode()。
|
|
1972
|
+
* 触发 EVENT_NODE_CHANGE(action=remove)。
|
|
1973
|
+
* 构造函数中已监听此事件,自动清空选中。
|
|
1974
|
+
*
|
|
1975
|
+
* @param nodeId - 要移除的节点 ID
|
|
1976
|
+
* @returns 被移除的 NodeModel,不存在时返回 null
|
|
1977
|
+
*/
|
|
1978
|
+
removeNode(e) {
|
|
1979
|
+
const t = this.getActiveBlock();
|
|
1980
|
+
return t ? t.removeNode(e) : null;
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* 将节点从一个父节点移动到另一个父节点
|
|
1984
|
+
*
|
|
1985
|
+
* 委托给 BlockModel.moveNode()。
|
|
1986
|
+
*
|
|
1987
|
+
* @param nodeId - 要移动的节点 ID
|
|
1988
|
+
* @param targetParentId - 目标父节点 ID
|
|
1989
|
+
* @param index - 插入位置,默认追加到末尾
|
|
1990
|
+
*/
|
|
1991
|
+
moveNode(e, t, n) {
|
|
1992
|
+
const i = this.getActiveBlock();
|
|
1993
|
+
i && i.moveNode(e, t, n);
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* 将节点在同级中上移一位
|
|
1997
|
+
*
|
|
1998
|
+
* 找到父节点后调用 NodeModel.moveChild(idx-1)。
|
|
1999
|
+
* 如果已是第一个子节点则不操作。
|
|
2000
|
+
*
|
|
2001
|
+
* @param nodeId - 要移动的节点 ID
|
|
2002
|
+
*/
|
|
2003
|
+
moveNodeUp(e) {
|
|
2004
|
+
const t = this.getActiveBlock();
|
|
2005
|
+
if (!t) return;
|
|
2006
|
+
const n = t.findParent(e);
|
|
2007
|
+
if (!n) return;
|
|
2008
|
+
const i = n.children.findIndex((r) => r.id === e);
|
|
2009
|
+
i <= 0 || n.moveChild(e, i - 1);
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* 将节点在同级中下移一位
|
|
2013
|
+
*
|
|
2014
|
+
* 找到父节点后调用 NodeModel.moveChild(idx+1)。
|
|
2015
|
+
* 如果已是最后一个子节点则不操作。
|
|
2016
|
+
*
|
|
2017
|
+
* @param nodeId - 要移动的节点 ID
|
|
2018
|
+
*/
|
|
2019
|
+
moveNodeDown(e) {
|
|
2020
|
+
const t = this.getActiveBlock();
|
|
2021
|
+
if (!t) return;
|
|
2022
|
+
const n = t.findParent(e);
|
|
2023
|
+
if (!n) return;
|
|
2024
|
+
const i = n.children.findIndex((r) => r.id === e);
|
|
2025
|
+
i === -1 || i >= n.children.length - 1 || n.moveChild(e, i + 1);
|
|
2026
|
+
}
|
|
2027
|
+
// ── 选中状态 ────────────────────────────────────────
|
|
2028
|
+
/**
|
|
2029
|
+
* 设置当前选中节点
|
|
2030
|
+
*
|
|
2031
|
+
* 更新 _selectedNodeId 并触发 onSelectionChange 回调。
|
|
2032
|
+
* 传入 null 清空选中。
|
|
2033
|
+
*
|
|
2034
|
+
* @param nodeId - 节点 ID,传入 null 清空选中
|
|
2035
|
+
*/
|
|
2036
|
+
select(e) {
|
|
2037
|
+
this._selectedNodeId = e, this.onSelectionChange?.();
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* 获取当前选中的 NodeModel 实例
|
|
2041
|
+
*
|
|
2042
|
+
* 通过 _selectedNodeId 查询 BlockModel 中的节点。
|
|
2043
|
+
*
|
|
2044
|
+
* @returns 选中的 NodeModel,未选中时返回 null
|
|
2045
|
+
*/
|
|
2046
|
+
getSelected() {
|
|
2047
|
+
return this._selectedNodeId ? this.getActiveBlock()?.findNode(this._selectedNodeId) ?? null : null;
|
|
2048
|
+
}
|
|
2049
|
+
/**
|
|
2050
|
+
* 获取当前选中的节点 ID
|
|
2051
|
+
*
|
|
2052
|
+
* @returns 节点 ID 字符串,未选中时返回 null
|
|
2053
|
+
*/
|
|
2054
|
+
getSelectedNodeId() {
|
|
2055
|
+
return this._selectedNodeId;
|
|
2056
|
+
}
|
|
2057
|
+
// ── History 快捷方法 ────────────────────────────────
|
|
2058
|
+
/** 撤销(委托给 History.undo()) */
|
|
2059
|
+
undo() {
|
|
2060
|
+
this.history.undo();
|
|
2061
|
+
}
|
|
2062
|
+
/** 重做(委托给 History.redo()) */
|
|
2063
|
+
redo() {
|
|
2064
|
+
this.history.redo();
|
|
2065
|
+
}
|
|
2066
|
+
/** 是否可以撤销 */
|
|
2067
|
+
canUndo() {
|
|
2068
|
+
return this.history.canUndo();
|
|
2069
|
+
}
|
|
2070
|
+
/** 是否可以重做 */
|
|
2071
|
+
canRedo() {
|
|
2072
|
+
return this.history.canRedo();
|
|
2073
|
+
}
|
|
2074
|
+
// ── 序列化 ──────────────────────────────────────────
|
|
2075
|
+
/**
|
|
2076
|
+
* 序列化项目数据
|
|
2077
|
+
*
|
|
2078
|
+
* 委托给 ProjectModel.toJSON()。
|
|
2079
|
+
*
|
|
2080
|
+
* @returns 可 JSON.stringify 的 ProjectModelJSON
|
|
2081
|
+
*/
|
|
2082
|
+
toJSON() {
|
|
2083
|
+
return this.project.toJSON();
|
|
2084
|
+
}
|
|
2085
|
+
// ── 生命周期 ────────────────────────────────────────
|
|
2086
|
+
/**
|
|
2087
|
+
* 销毁 Engine,释放所有资源
|
|
2088
|
+
*
|
|
2089
|
+
* 取消事件订阅,销毁 History,清空选中状态。
|
|
2090
|
+
*/
|
|
2091
|
+
destroy() {
|
|
2092
|
+
for (const e of this.unsubscribers)
|
|
2093
|
+
e();
|
|
2094
|
+
this.unsubscribers = [], this.history.dispose(), this._selectedNodeId = null, this.initialized = !1;
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
const Xe = { class: "flex items-center justify-between h-[48px] px-4 border-b border-[var(--color-border-2)] bg-[var(--color-bg-2)] shrink-0" }, Qe = { class: "flex items-center gap-2" }, et = { class: "text-sm font-semibold text-[var(--color-text-1)]" }, tt = { class: "flex items-center gap-2" }, nt = { class: "flex items-center gap-2" }, st = /* @__PURE__ */ _({
|
|
2098
|
+
name: "DesignerHeader",
|
|
2099
|
+
__name: "Header",
|
|
2100
|
+
props: {
|
|
2101
|
+
title: {},
|
|
2102
|
+
logo: {}
|
|
2103
|
+
},
|
|
2104
|
+
emits: ["togglePreview"],
|
|
2105
|
+
setup(s) {
|
|
2106
|
+
return (e, t) => (a(), m("header", Xe, [
|
|
2107
|
+
p("div", Qe, [
|
|
2108
|
+
s.logo ? (a(), w($(s.logo), {
|
|
2109
|
+
key: 0,
|
|
2110
|
+
size: 24
|
|
2111
|
+
})) : S("", !0),
|
|
2112
|
+
p("span", et, D(s.title), 1)
|
|
2113
|
+
]),
|
|
2114
|
+
p("div", tt, [
|
|
2115
|
+
E(e.$slots, "toolbar")
|
|
2116
|
+
]),
|
|
2117
|
+
p("div", nt, [
|
|
2118
|
+
E(e.$slots, "actions")
|
|
2119
|
+
])
|
|
2120
|
+
]));
|
|
2121
|
+
}
|
|
2122
|
+
}), it = { class: "flex h-full shrink-0" }, rt = { class: "w-[48px] flex flex-col items-center py-2 gap-1 border-r border-[var(--color-border-2)] bg-[var(--color-bg-2)] shrink-0" }, ot = ["onClick"], at = {
|
|
2123
|
+
key: 0,
|
|
2124
|
+
class: "w-[24px] h-[1px] bg-[var(--color-border-2)] my-1"
|
|
2125
|
+
}, lt = ["onClick"], ct = {
|
|
2126
|
+
key: 0,
|
|
2127
|
+
class: "w-[352px] h-full flex flex-col"
|
|
2128
|
+
}, dt = { class: "flex items-center justify-between h-[40px] px-3 border-b border-[var(--color-border-2)] shrink-0" }, ut = { class: "text-sm font-medium text-[var(--color-text-1)]" }, ht = { class: "flex-1 overflow-y-auto" }, pt = /* @__PURE__ */ _({
|
|
2129
|
+
name: "DesignerApps",
|
|
2130
|
+
__name: "Apps",
|
|
2131
|
+
props: {
|
|
2132
|
+
widgetRegistry: {}
|
|
2133
|
+
},
|
|
2134
|
+
setup(s) {
|
|
2135
|
+
const e = s, t = A(null), n = A(!1), i = x(() => e.widgetRegistry.getPanelWidgets("apps")), r = x(() => e.widgetRegistry.getOtherWidgets("apps")), o = x(() => t.value ? e.widgetRegistry.get(t.value) : null), c = x(() => i.value.filter((h) => h.component).map((h) => h.name));
|
|
2136
|
+
function l(h) {
|
|
2137
|
+
h.openType === "panel" ? d(h) : h.openType === "dialog" ? h.handler?.() : h.openType === "link" && h.link && window.open(h.link, "_blank");
|
|
2138
|
+
}
|
|
2139
|
+
function d(h) {
|
|
2140
|
+
t.value === h.name ? (n.value = !1, t.value = null) : (t.value = h.name, n.value = !0);
|
|
2141
|
+
}
|
|
2142
|
+
return (h, y) => {
|
|
2143
|
+
const f = R("a-tooltip"), b = R("icon-close");
|
|
2144
|
+
return a(), m("div", it, [
|
|
2145
|
+
p("div", rt, [
|
|
2146
|
+
(a(!0), m(T, null, z(i.value, (v) => (a(), w(f, {
|
|
2147
|
+
key: v.name,
|
|
2148
|
+
content: v.title,
|
|
2149
|
+
position: "right"
|
|
2150
|
+
}, {
|
|
2151
|
+
default: P(() => [
|
|
2152
|
+
p("span", {
|
|
2153
|
+
class: q(["flex items-center justify-center w-[36px] h-[36px] rounded cursor-pointer transition-colors", [
|
|
2154
|
+
t.value === v.name ? "bg-[rgba(var(--primary-6),0.1)] text-[rgb(var(--primary-6))]" : "text-[var(--color-text-2)] hover:bg-[var(--color-fill-2)] hover:text-[var(--color-text-1)]"
|
|
2155
|
+
]]),
|
|
2156
|
+
onClick: (C) => l(v)
|
|
2157
|
+
}, [
|
|
2158
|
+
(a(), w($(v.icon), { size: 20 }))
|
|
2159
|
+
], 10, ot)
|
|
2160
|
+
]),
|
|
2161
|
+
_: 2
|
|
2162
|
+
}, 1032, ["content"]))), 128)),
|
|
2163
|
+
i.value.length && r.value.length ? (a(), m("div", at)) : S("", !0),
|
|
2164
|
+
(a(!0), m(T, null, z(r.value, (v) => (a(), w(f, {
|
|
2165
|
+
key: v.name,
|
|
2166
|
+
content: v.title,
|
|
2167
|
+
position: "right"
|
|
2168
|
+
}, {
|
|
2169
|
+
default: P(() => [
|
|
2170
|
+
p("span", {
|
|
2171
|
+
class: "flex items-center justify-center w-[36px] h-[36px] rounded cursor-pointer text-[var(--color-text-2)] hover:bg-[var(--color-fill-2)] hover:text-[var(--color-text-1)] transition-colors",
|
|
2172
|
+
onClick: (C) => l(v)
|
|
2173
|
+
}, [
|
|
2174
|
+
(a(), w($(v.icon), { size: 20 }))
|
|
2175
|
+
], 8, lt)
|
|
2176
|
+
]),
|
|
2177
|
+
_: 2
|
|
2178
|
+
}, 1032, ["content"]))), 128))
|
|
2179
|
+
]),
|
|
2180
|
+
p("div", {
|
|
2181
|
+
class: "border-r border-[var(--color-border-2)] bg-[var(--color-bg-2)] overflow-hidden transition-[width] duration-200",
|
|
2182
|
+
style: j({ width: n.value ? "352px" : "0px" })
|
|
2183
|
+
}, [
|
|
2184
|
+
n.value && o.value ? (a(), m("div", ct, [
|
|
2185
|
+
p("div", dt, [
|
|
2186
|
+
p("span", ut, D(o.value.title), 1),
|
|
2187
|
+
p("span", {
|
|
2188
|
+
class: "flex items-center justify-center w-6 h-6 rounded cursor-pointer text-[var(--color-text-3)] hover:bg-[var(--color-fill-2)] hover:text-[var(--color-text-1)] transition-colors",
|
|
2189
|
+
onClick: y[0] || (y[0] = (v) => {
|
|
2190
|
+
n.value = !1, t.value = null;
|
|
2191
|
+
})
|
|
2192
|
+
}, [
|
|
2193
|
+
N(b, { size: 14 })
|
|
2194
|
+
])
|
|
2195
|
+
]),
|
|
2196
|
+
p("div", ht, [
|
|
2197
|
+
(a(), w(Ne, { include: c.value }, [
|
|
2198
|
+
(a(), w($(o.value.component), {
|
|
2199
|
+
key: o.value.name
|
|
2200
|
+
}))
|
|
2201
|
+
], 1032, ["include"]))
|
|
2202
|
+
])
|
|
2203
|
+
])) : S("", !0)
|
|
2204
|
+
], 4)
|
|
2205
|
+
]);
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
});
|
|
2209
|
+
class mt {
|
|
2210
|
+
/** 物料存储 */
|
|
2211
|
+
materialStore;
|
|
2212
|
+
/** 组件加载器(从 MaterialStore 创建) */
|
|
2213
|
+
loader = null;
|
|
2214
|
+
/** 渲染上下文(设计态,包含节点标记和 refs) */
|
|
2215
|
+
renderCtx = null;
|
|
2216
|
+
/** iframe DOM 元素 */
|
|
2217
|
+
iframe = null;
|
|
2218
|
+
/** iframe 内的 Vue 应用实例 */
|
|
2219
|
+
innerApp = null;
|
|
2220
|
+
/** 响应式 schema(变更触发重渲染) */
|
|
2221
|
+
schemaRef = { value: null };
|
|
2222
|
+
/** 父文档样式变化监听器 */
|
|
2223
|
+
styleObserver = null;
|
|
2224
|
+
/** 是否已挂载(iframe load 完成) */
|
|
2225
|
+
mounted = !1;
|
|
2226
|
+
/** 就绪回调队列(iframe load 后依次调用) */
|
|
2227
|
+
readyCallbacks = [];
|
|
2228
|
+
/**
|
|
2229
|
+
* 创建 Simulator 实例
|
|
2230
|
+
*
|
|
2231
|
+
* @param options - 构造选项
|
|
2232
|
+
*/
|
|
2233
|
+
constructor(e) {
|
|
2234
|
+
this.materialStore = e.materialStore;
|
|
2235
|
+
}
|
|
2236
|
+
// ── 生命周期 ──────────────────────────────────────────
|
|
2237
|
+
/**
|
|
2238
|
+
* 挂载模拟器到指定容器
|
|
2239
|
+
*
|
|
2240
|
+
* 创建 iframe,添加到 host 容器,等待 iframe load 事件。
|
|
2241
|
+
* load 完成后自动写入骨架、注入样式、启动 Vue 应用。
|
|
2242
|
+
*
|
|
2243
|
+
* @param host - 挂载容器 DOM 元素
|
|
2244
|
+
*/
|
|
2245
|
+
mount(e) {
|
|
2246
|
+
this.mounted || (this.iframe = document.createElement("iframe"), this.iframe.setAttribute("sandbox", "allow-same-origin allow-scripts"), this.iframe.style.width = "100%", this.iframe.style.height = "100%", this.iframe.style.border = "none", this.iframe.style.display = "block", e.appendChild(this.iframe), this.iframe.addEventListener("load", this.onIframeLoad));
|
|
2247
|
+
}
|
|
2248
|
+
/**
|
|
2249
|
+
* 销毁模拟器
|
|
2250
|
+
*
|
|
2251
|
+
* 卸载 Vue 应用,断开样式监听,移除 iframe。
|
|
2252
|
+
*/
|
|
2253
|
+
destroy() {
|
|
2254
|
+
this.innerApp && (this.innerApp.unmount(), this.innerApp = null), this.styleObserver && (this.styleObserver.disconnect(), this.styleObserver = null), this.iframe && (this.iframe.removeEventListener("load", this.onIframeLoad), this.iframe.remove(), this.iframe = null), this.renderCtx?.dispose(), this.renderCtx = null, this.loader = null, this.mounted = !1;
|
|
2255
|
+
}
|
|
2256
|
+
// ── 渲染控制 ──────────────────────────────────────────
|
|
2257
|
+
/**
|
|
2258
|
+
* 全量渲染 Block
|
|
2259
|
+
*
|
|
2260
|
+
* 更新 reactive schemaRef,触发 RootComponent 重渲染。
|
|
2261
|
+
* 在页面切换、History 恢复、结构变更后调用。
|
|
2262
|
+
*
|
|
2263
|
+
* @param blockJSON - 根节点的序列化数据,传 null 清空画布
|
|
2264
|
+
*/
|
|
2265
|
+
renderBlock(e) {
|
|
2266
|
+
this.schemaRef.value = e;
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* 增量更新节点(预留)
|
|
2270
|
+
*
|
|
2271
|
+
* 当前仍走全量渲染(structuredClone 触发 reactive 变更检测)。
|
|
2272
|
+
* 后续可通过 renderCtx.refs[nodeId] 获取组件实例,直接更新 props。
|
|
2273
|
+
*
|
|
2274
|
+
* @param _nodeId - 节点 ID
|
|
2275
|
+
* @param _nodeJSON - 节点序列化数据
|
|
2276
|
+
*/
|
|
2277
|
+
renderNodeUpdate(e, t) {
|
|
2278
|
+
const n = this.schemaRef.value;
|
|
2279
|
+
n && (this.schemaRef.value = structuredClone(n));
|
|
2280
|
+
}
|
|
2281
|
+
// ── DOM 查询 ──────────────────────────────────────────
|
|
2282
|
+
/**
|
|
2283
|
+
* 获取 iframe 的 Document
|
|
2284
|
+
*
|
|
2285
|
+
* 供 EventBridge 绑定事件使用。
|
|
2286
|
+
*
|
|
2287
|
+
* @returns iframe Document,iframe 不存在时返回 null
|
|
2288
|
+
*/
|
|
2289
|
+
getDocument() {
|
|
2290
|
+
return this.iframe?.contentDocument ?? null;
|
|
2291
|
+
}
|
|
2292
|
+
/**
|
|
2293
|
+
* 获取 iframe 的 Window
|
|
2294
|
+
*
|
|
2295
|
+
* @returns iframe Window,iframe 不存在时返回 null
|
|
2296
|
+
*/
|
|
2297
|
+
getWindow() {
|
|
2298
|
+
return this.iframe?.contentWindow ?? null;
|
|
2299
|
+
}
|
|
2300
|
+
/**
|
|
2301
|
+
* 获取 iframe 在主窗口中的偏移矩形
|
|
2302
|
+
*
|
|
2303
|
+
* 供 Designer 计算 Overlay 位置使用。
|
|
2304
|
+
*
|
|
2305
|
+
* @returns DOMRect,iframe 不存在时返回 null
|
|
2306
|
+
*/
|
|
2307
|
+
getIframeRect() {
|
|
2308
|
+
return this.iframe?.getBoundingClientRect() ?? null;
|
|
2309
|
+
}
|
|
2310
|
+
/**
|
|
2311
|
+
* 通过节点 ID 获取对应的 DOM 元素
|
|
2312
|
+
*
|
|
2313
|
+
* 在 iframe document 中查询 `data-__node-id__` 属性。
|
|
2314
|
+
* 由 @designer/render 的 RenderContext.markElement() 注入。
|
|
2315
|
+
*
|
|
2316
|
+
* @param nodeId - 节点 ID
|
|
2317
|
+
* @returns DOM 元素,不存在时返回 null
|
|
2318
|
+
*/
|
|
2319
|
+
getNodeElement(e) {
|
|
2320
|
+
const t = this.getDocument();
|
|
2321
|
+
return t ? t.querySelector(`[data-__node-id__="${e}"]`) : null;
|
|
2322
|
+
}
|
|
2323
|
+
/**
|
|
2324
|
+
* 获取渲染上下文
|
|
2325
|
+
*
|
|
2326
|
+
* @returns RenderContext 实例,未初始化时返回 null
|
|
2327
|
+
*/
|
|
2328
|
+
getRenderContext() {
|
|
2329
|
+
return this.renderCtx;
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* 是否已挂载(iframe load 完成)
|
|
2333
|
+
*/
|
|
2334
|
+
isMounted() {
|
|
2335
|
+
return this.mounted;
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* 注册就绪回调
|
|
2339
|
+
*
|
|
2340
|
+
* 如果 iframe 已加载则立即调用,否则等 load 后调用。
|
|
2341
|
+
* 供 Designer 等子系统等待 Simulator 初始化完成。
|
|
2342
|
+
*
|
|
2343
|
+
* @param cb - 就绪回调
|
|
2344
|
+
*/
|
|
2345
|
+
onReady(e) {
|
|
2346
|
+
this.mounted ? e() : this.readyCallbacks.push(e);
|
|
2347
|
+
}
|
|
2348
|
+
// ── 内部 ──────────────────────────────────────────────
|
|
2349
|
+
/**
|
|
2350
|
+
* iframe 加载完成回调
|
|
2351
|
+
*
|
|
2352
|
+
* 依次:写入骨架 → 注入样式 → 挂载渲染器 → 监听样式变化 → 通知就绪
|
|
2353
|
+
*/
|
|
2354
|
+
onIframeLoad = () => {
|
|
2355
|
+
const e = this.iframe?.contentDocument;
|
|
2356
|
+
if (!e) return;
|
|
2357
|
+
this.writeSkeleton(e), this.injectStyles(e), this.mountRenderer(e), this.observeStyleChanges(e), this.mounted = !0;
|
|
2358
|
+
const t = this.readyCallbacks;
|
|
2359
|
+
this.readyCallbacks = [];
|
|
2360
|
+
for (const n of t) n();
|
|
2361
|
+
};
|
|
2362
|
+
/**
|
|
2363
|
+
* 写入 HTML 骨架
|
|
2364
|
+
*
|
|
2365
|
+
* 在 iframe 中创建最小化的 HTML 文档,包含基础重置样式和渲染根节点。
|
|
2366
|
+
*
|
|
2367
|
+
* @param doc - iframe Document
|
|
2368
|
+
*/
|
|
2369
|
+
writeSkeleton(e) {
|
|
2370
|
+
e.open(), e.write(`<!DOCTYPE html>
|
|
2371
|
+
<html>
|
|
2372
|
+
<head>
|
|
2373
|
+
<meta charset="utf-8" />
|
|
2374
|
+
<style>
|
|
2375
|
+
/* 基础重置 */
|
|
2376
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
2377
|
+
body { margin: 0; padding: 0; font-family: inherit; }
|
|
2378
|
+
</style>
|
|
2379
|
+
</head>
|
|
2380
|
+
<body>
|
|
2381
|
+
<div id="renderer-root"></div>
|
|
2382
|
+
</body>
|
|
2383
|
+
</html>`), e.close();
|
|
2384
|
+
}
|
|
2385
|
+
/**
|
|
2386
|
+
* 注入父文档样式
|
|
2387
|
+
*
|
|
2388
|
+
* 遍历主文档的所有样式表,将 CSS 规则复制到 iframe 中。
|
|
2389
|
+
* 确保画布内的组件与主文档使用相同的样式(Arco Design、Tailwind 等)。
|
|
2390
|
+
* 跨域样式表无法读取,静默忽略。
|
|
2391
|
+
*
|
|
2392
|
+
* @param doc - iframe Document
|
|
2393
|
+
*/
|
|
2394
|
+
injectStyles(e) {
|
|
2395
|
+
const t = e.createElement("style");
|
|
2396
|
+
t.setAttribute("data-source", "designer-parent");
|
|
2397
|
+
let n = "";
|
|
2398
|
+
try {
|
|
2399
|
+
const i = Array.from(document.styleSheets);
|
|
2400
|
+
for (const r of i)
|
|
2401
|
+
try {
|
|
2402
|
+
const o = Array.from(r.cssRules);
|
|
2403
|
+
for (const c of o)
|
|
2404
|
+
n += c.cssText + `
|
|
2405
|
+
`;
|
|
2406
|
+
} catch {
|
|
2407
|
+
}
|
|
2408
|
+
} catch {
|
|
2409
|
+
}
|
|
2410
|
+
t.textContent = n, e.head.appendChild(t);
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* 在 iframe 中创建 Vue 应用
|
|
2414
|
+
*
|
|
2415
|
+
* 1. 创建 reactive schema(响应式数据驱动渲染)
|
|
2416
|
+
* 2. 创建 ComponentLoader(物料组件加载)
|
|
2417
|
+
* 3. 创建 RenderContext(设计态渲染上下文)
|
|
2418
|
+
* 4. 创建 RootComponent(setup 返回 render 函数)
|
|
2419
|
+
* 5. 注册所有物料组件到 Vue 应用
|
|
2420
|
+
* 6. 挂载到 iframe 的 #renderer-root
|
|
2421
|
+
*
|
|
2422
|
+
* @param doc - iframe Document
|
|
2423
|
+
*/
|
|
2424
|
+
mountRenderer(e) {
|
|
2425
|
+
const t = e.getElementById("renderer-root");
|
|
2426
|
+
if (!t) return;
|
|
2427
|
+
this.schemaRef = ne({ value: null }), this.loader = Me({ materialStore: this.materialStore }), this.renderCtx = new Y();
|
|
2428
|
+
const n = this, i = {
|
|
2429
|
+
setup() {
|
|
2430
|
+
return () => {
|
|
2431
|
+
if (!n.schemaRef.value) return null;
|
|
2432
|
+
try {
|
|
2433
|
+
return ie(
|
|
2434
|
+
n.schemaRef.value,
|
|
2435
|
+
n.renderCtx,
|
|
2436
|
+
n.loader,
|
|
2437
|
+
"design"
|
|
2438
|
+
);
|
|
2439
|
+
} catch (r) {
|
|
2440
|
+
return console.warn("[Simulator] render error:", r), null;
|
|
2441
|
+
}
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
};
|
|
2445
|
+
this.innerApp = _e(i), this.innerApp.config.errorHandler = (r) => {
|
|
2446
|
+
console.warn("[Simulator] component error:", r);
|
|
2447
|
+
};
|
|
2448
|
+
for (const [r, o] of this.materialStore.getAllComponents())
|
|
2449
|
+
typeof o != "string" && this.innerApp.component(r, o);
|
|
2450
|
+
this.innerApp.mount(t);
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* 监听父文档样式变化
|
|
2454
|
+
*
|
|
2455
|
+
* 通过 MutationObserver 监听 document.head 的变化(样式热更新等),
|
|
2456
|
+
* 变化时重新注入样式到 iframe。
|
|
2457
|
+
*
|
|
2458
|
+
* @param doc - iframe Document
|
|
2459
|
+
*/
|
|
2460
|
+
observeStyleChanges(e) {
|
|
2461
|
+
this.styleObserver = new MutationObserver(() => {
|
|
2462
|
+
const t = e.querySelector('[data-source="designer-parent"]');
|
|
2463
|
+
t && t.remove(), this.injectStyles(e);
|
|
2464
|
+
}), this.styleObserver.observe(document.head, {
|
|
2465
|
+
childList: !0,
|
|
2466
|
+
subtree: !0
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
class ft {
|
|
2471
|
+
/** 监听的 document(iframe 内) */
|
|
2472
|
+
doc;
|
|
2473
|
+
/** 事件处理器映射(每种事件类型对应一个 Set) */
|
|
2474
|
+
handlers = /* @__PURE__ */ new Map();
|
|
2475
|
+
/** DOM 事件清理函数列表 */
|
|
2476
|
+
cleanupFns = [];
|
|
2477
|
+
/**
|
|
2478
|
+
* 创建 EventBridge
|
|
2479
|
+
*
|
|
2480
|
+
* @param doc - iframe 的 Document 对象(可为 null,后续通过 setDocument 设置)
|
|
2481
|
+
*/
|
|
2482
|
+
constructor(e) {
|
|
2483
|
+
this.doc = e;
|
|
2484
|
+
}
|
|
2485
|
+
/**
|
|
2486
|
+
* 启动事件监听
|
|
2487
|
+
*
|
|
2488
|
+
* 在 iframe document 上绑定 click/mouseover/mouseout 事件(capture 阶段)。
|
|
2489
|
+
* 必须在 doc 可用后调用。
|
|
2490
|
+
*/
|
|
2491
|
+
setup() {
|
|
2492
|
+
this.doc && (this.bindDOMEvent("click", "nodeClick"), this.bindDOMEvent("mouseover", "nodeHover"), this.bindDOMEvent("mouseout", "nodeMouseLeave"));
|
|
2493
|
+
}
|
|
2494
|
+
/**
|
|
2495
|
+
* 停止事件监听
|
|
2496
|
+
*
|
|
2497
|
+
* 移除所有 DOM 事件监听,清空处理器。
|
|
2498
|
+
*/
|
|
2499
|
+
teardown() {
|
|
2500
|
+
for (const e of this.cleanupFns)
|
|
2501
|
+
e();
|
|
2502
|
+
this.cleanupFns = [], this.handlers.clear();
|
|
2503
|
+
}
|
|
2504
|
+
/**
|
|
2505
|
+
* 更新 document 引用
|
|
2506
|
+
*
|
|
2507
|
+
* iframe 重建时调用(如页面刷新),先 teardown 再重新绑定。
|
|
2508
|
+
*
|
|
2509
|
+
* @param doc - 新的 Document 对象
|
|
2510
|
+
*/
|
|
2511
|
+
setDocument(e) {
|
|
2512
|
+
this.teardown(), this.doc = e;
|
|
2513
|
+
}
|
|
2514
|
+
// ── 事件订阅 ──────────────────────────────────────────
|
|
2515
|
+
/**
|
|
2516
|
+
* 注册事件处理器
|
|
2517
|
+
*
|
|
2518
|
+
* @param event - Bridge 事件类型
|
|
2519
|
+
* @param handler - 处理函数
|
|
2520
|
+
* @returns 取消注册函数
|
|
2521
|
+
*/
|
|
2522
|
+
on(e, t) {
|
|
2523
|
+
let n = this.handlers.get(e);
|
|
2524
|
+
return n || (n = /* @__PURE__ */ new Set(), this.handlers.set(e, n)), n.add(t), () => {
|
|
2525
|
+
n.delete(t);
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2528
|
+
// ── 内部 ──────────────────────────────────────────────
|
|
2529
|
+
/**
|
|
2530
|
+
* 绑定 DOM 事件到 Bridge 事件
|
|
2531
|
+
*
|
|
2532
|
+
* 在 iframe document 上以 capture 模式监听 DOM 事件,
|
|
2533
|
+
* 通过 findNodeId() 定位节点后发射对应的 Bridge 事件。
|
|
2534
|
+
*
|
|
2535
|
+
* @param domEvent - DOM 事件名(如 'click')
|
|
2536
|
+
* @param bridgeEvent - Bridge 事件名(如 'nodeClick')
|
|
2537
|
+
*/
|
|
2538
|
+
bindDOMEvent(e, t) {
|
|
2539
|
+
const n = this.doc;
|
|
2540
|
+
if (!n) return;
|
|
2541
|
+
const i = (r) => {
|
|
2542
|
+
const o = this.findNodeId(r.target);
|
|
2543
|
+
o && this.emit(t, {
|
|
2544
|
+
nodeId: o,
|
|
2545
|
+
originalEvent: r
|
|
2546
|
+
});
|
|
2547
|
+
};
|
|
2548
|
+
n.addEventListener(e, i, !0), this.cleanupFns.push(() => {
|
|
2549
|
+
n.removeEventListener(e, i, !0);
|
|
2550
|
+
});
|
|
2551
|
+
}
|
|
2552
|
+
/**
|
|
2553
|
+
* 从 DOM 元素向上查找 data-__node-id__
|
|
2554
|
+
*
|
|
2555
|
+
* 沿 parentElement 向上遍历,找到第一个标记了节点 ID 的元素。
|
|
2556
|
+
* 这个属性由 @designer/render 的 RenderContext.markElement() 注入。
|
|
2557
|
+
*
|
|
2558
|
+
* @param el - 起始 DOM 元素
|
|
2559
|
+
* @returns 节点 ID,未找到时返回 null
|
|
2560
|
+
*/
|
|
2561
|
+
findNodeId(e) {
|
|
2562
|
+
let t = e;
|
|
2563
|
+
for (; t && t.parentElement !== null; ) {
|
|
2564
|
+
const n = t.getAttribute?.("data-__node-id__");
|
|
2565
|
+
if (n) return n;
|
|
2566
|
+
t = t.parentElement;
|
|
2567
|
+
}
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* 发射 Bridge 事件
|
|
2572
|
+
*
|
|
2573
|
+
* 通知所有注册了该事件类型的处理器。
|
|
2574
|
+
*
|
|
2575
|
+
* @param event - Bridge 事件类型
|
|
2576
|
+
* @param payload - 事件载荷
|
|
2577
|
+
*/
|
|
2578
|
+
emit(e, t) {
|
|
2579
|
+
const n = this.handlers.get(e);
|
|
2580
|
+
if (n)
|
|
2581
|
+
for (const i of n)
|
|
2582
|
+
i(t);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
class gt {
|
|
2586
|
+
/** Simulator 实例 */
|
|
2587
|
+
simulator;
|
|
2588
|
+
/** Engine 实例 */
|
|
2589
|
+
engine;
|
|
2590
|
+
/** 物料存储 */
|
|
2591
|
+
materialStore;
|
|
2592
|
+
/** 节点操作回调 */
|
|
2593
|
+
actions;
|
|
2594
|
+
/** 当前悬浮的节点 ID(响应式,供 useDesigner 消费) */
|
|
2595
|
+
hoveredNodeId = A(null);
|
|
2596
|
+
/** 当前选中的节点 ID(响应式,从 engine 同步) */
|
|
2597
|
+
selectedNodeId = A(null);
|
|
2598
|
+
/** iframe 事件桥梁 */
|
|
2599
|
+
bridge = null;
|
|
2600
|
+
/** EventBridge 取消订阅函数列表 */
|
|
2601
|
+
unsubscribers = [];
|
|
2602
|
+
/** 是否已激活 */
|
|
2603
|
+
active = !1;
|
|
2604
|
+
/** 悬浮框隐藏延迟定时器(30ms 防闪烁) */
|
|
2605
|
+
hoverHideTimer = null;
|
|
2606
|
+
/** engine.onSelectionChange 的前一个回调(deactivate 时恢复) */
|
|
2607
|
+
prevOnSelectionChange = null;
|
|
2608
|
+
/**
|
|
2609
|
+
* 创建 Designer 实例
|
|
2610
|
+
*
|
|
2611
|
+
* @param options - 构造选项
|
|
2612
|
+
*/
|
|
2613
|
+
constructor(e) {
|
|
2614
|
+
this.simulator = e.simulator, this.engine = e.engine, this.materialStore = e.materialStore, this.actions = e.actions ?? {};
|
|
2615
|
+
}
|
|
2616
|
+
// ── 生命周期 ──────────────────────────────────────────
|
|
2617
|
+
/**
|
|
2618
|
+
* 激活 Designer
|
|
2619
|
+
*
|
|
2620
|
+
* 等 Simulator 就绪后设置 EventBridge。
|
|
2621
|
+
* 调用多次安全(幂等)。
|
|
2622
|
+
*/
|
|
2623
|
+
activate() {
|
|
2624
|
+
this.active || (this.simulator.onReady(() => {
|
|
2625
|
+
this.setupBridge();
|
|
2626
|
+
}), this.prevOnSelectionChange = this.engine.onSelectionChange, this.engine.onSelectionChange = () => {
|
|
2627
|
+
this.prevOnSelectionChange?.(), this.selectedNodeId.value = this.engine.getSelectedNodeId();
|
|
2628
|
+
}, this.active = !0);
|
|
2629
|
+
}
|
|
2630
|
+
/**
|
|
2631
|
+
* 停用 Designer
|
|
2632
|
+
*
|
|
2633
|
+
* 清理 EventBridge、定时器、订阅。
|
|
2634
|
+
*/
|
|
2635
|
+
deactivate() {
|
|
2636
|
+
if (this.active) {
|
|
2637
|
+
this.teardownBridge(), this.engine.onSelectionChange = this.prevOnSelectionChange, this.prevOnSelectionChange = null, this.hoverHideTimer && (clearTimeout(this.hoverHideTimer), this.hoverHideTimer = null);
|
|
2638
|
+
for (const e of this.unsubscribers)
|
|
2639
|
+
e();
|
|
2640
|
+
this.unsubscribers = [], this.hoveredNodeId.value = null, this.selectedNodeId.value = null, this.active = !1;
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
/** 是否已激活 */
|
|
2644
|
+
isActive() {
|
|
2645
|
+
return this.active;
|
|
2646
|
+
}
|
|
2647
|
+
// ── EventBridge ──────────────────────────────────────
|
|
2648
|
+
/**
|
|
2649
|
+
* 设置 EventBridge
|
|
2650
|
+
*
|
|
2651
|
+
* 在 iframe document 上监听 click/mouseover/mouseout。
|
|
2652
|
+
*/
|
|
2653
|
+
setupBridge() {
|
|
2654
|
+
const e = this.simulator.getDocument();
|
|
2655
|
+
if (!e) return;
|
|
2656
|
+
this.bridge = new ft(e), this.bridge.setup();
|
|
2657
|
+
const t = this.bridge.on("nodeClick", this.handleNodeClick), n = this.bridge.on("nodeHover", this.handleNodeHover), i = this.bridge.on("nodeMouseLeave", this.handleNodeMouseLeave);
|
|
2658
|
+
this.unsubscribers.push(t, n, i);
|
|
2659
|
+
}
|
|
2660
|
+
/** 拆除 EventBridge */
|
|
2661
|
+
teardownBridge() {
|
|
2662
|
+
this.bridge?.teardown(), this.bridge = null;
|
|
2663
|
+
}
|
|
2664
|
+
// ── 拖拽(预留)──────────────────────────────────────
|
|
2665
|
+
/** 开始拖拽(预留接口) */
|
|
2666
|
+
startDrag(e) {
|
|
2667
|
+
}
|
|
2668
|
+
/** 取消拖拽(预留接口) */
|
|
2669
|
+
cancelDrag() {
|
|
2670
|
+
}
|
|
2671
|
+
// ── 事件处理器 ──────────────────────────────────────
|
|
2672
|
+
/** 节点点击:清空悬浮,选中节点 */
|
|
2673
|
+
handleNodeClick = (e) => {
|
|
2674
|
+
this.hoveredNodeId.value = null, this.engine.select(e.nodeId);
|
|
2675
|
+
};
|
|
2676
|
+
/** 节点悬浮:更新 hoveredNodeId(已选中节点不显示悬浮框,同一节点不重复) */
|
|
2677
|
+
handleNodeHover = (e) => {
|
|
2678
|
+
if (this.hoverHideTimer && (clearTimeout(this.hoverHideTimer), this.hoverHideTimer = null), this.engine.getSelectedNodeId() === e.nodeId) {
|
|
2679
|
+
this.hoveredNodeId.value = null;
|
|
2680
|
+
return;
|
|
2681
|
+
}
|
|
2682
|
+
this.hoveredNodeId.value !== e.nodeId && (this.hoveredNodeId.value = e.nodeId);
|
|
2683
|
+
};
|
|
2684
|
+
/** 鼠标离开:30ms 延迟清空悬浮(防闪烁) */
|
|
2685
|
+
handleNodeMouseLeave = () => {
|
|
2686
|
+
this.hoverHideTimer = setTimeout(() => {
|
|
2687
|
+
this.hoveredNodeId.value = null;
|
|
2688
|
+
}, 30);
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
function vt(s) {
|
|
2692
|
+
const e = s.simulator, t = A(0);
|
|
2693
|
+
function n() {
|
|
2694
|
+
t.value++;
|
|
2695
|
+
}
|
|
2696
|
+
const i = x(() => {
|
|
2697
|
+
t.value;
|
|
2698
|
+
const d = s.hoveredNodeId.value;
|
|
2699
|
+
return d ? ue(e, d) : null;
|
|
2700
|
+
}), r = x(() => {
|
|
2701
|
+
t.value;
|
|
2702
|
+
const d = s.selectedNodeId.value;
|
|
2703
|
+
return d ? ue(e, d) : null;
|
|
2704
|
+
}), o = x(() => {
|
|
2705
|
+
t.value;
|
|
2706
|
+
const d = s.selectedNodeId.value;
|
|
2707
|
+
return d ? yt(e, d) : null;
|
|
2708
|
+
}), c = x(() => {
|
|
2709
|
+
const d = s.selectedNodeId.value;
|
|
2710
|
+
if (!d) return null;
|
|
2711
|
+
const h = s.engine.getActiveBlock();
|
|
2712
|
+
return h ? h.findNode(d)?.name ?? null : null;
|
|
2713
|
+
}), l = x(() => {
|
|
2714
|
+
const d = s.hoveredNodeId.value;
|
|
2715
|
+
if (!d) return null;
|
|
2716
|
+
const h = s.engine.getActiveBlock();
|
|
2717
|
+
return h ? h.findNode(d)?.name ?? null : null;
|
|
2718
|
+
});
|
|
2719
|
+
return {
|
|
2720
|
+
hoverStyle: i,
|
|
2721
|
+
selectStyle: r,
|
|
2722
|
+
toolbarStyle: o,
|
|
2723
|
+
selectedNodeName: c,
|
|
2724
|
+
hoveredNodeName: l,
|
|
2725
|
+
designer: s,
|
|
2726
|
+
refresh: n
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
function ye(s, e) {
|
|
2730
|
+
const t = s.getIframeRect();
|
|
2731
|
+
if (!t) return null;
|
|
2732
|
+
const n = s.getNodeElement(e);
|
|
2733
|
+
if (!n) return null;
|
|
2734
|
+
const i = n.getBoundingClientRect();
|
|
2735
|
+
return {
|
|
2736
|
+
left: t.left + i.left,
|
|
2737
|
+
top: t.top + i.top,
|
|
2738
|
+
width: i.width,
|
|
2739
|
+
height: i.height
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
function ue(s, e) {
|
|
2743
|
+
const t = ye(s, e);
|
|
2744
|
+
return t ? {
|
|
2745
|
+
left: `${t.left}px`,
|
|
2746
|
+
top: `${t.top}px`,
|
|
2747
|
+
width: `${t.width}px`,
|
|
2748
|
+
height: `${t.height}px`
|
|
2749
|
+
} : null;
|
|
2750
|
+
}
|
|
2751
|
+
function yt(s, e) {
|
|
2752
|
+
const t = ye(s, e);
|
|
2753
|
+
return t ? {
|
|
2754
|
+
left: `${t.left + t.width - 100}px`,
|
|
2755
|
+
top: `${t.top - 28}px`,
|
|
2756
|
+
width: "100px",
|
|
2757
|
+
height: "28px"
|
|
2758
|
+
} : null;
|
|
2759
|
+
}
|
|
2760
|
+
const bt = {
|
|
2761
|
+
key: 0,
|
|
2762
|
+
class: "absolute left-0 -top-5 px-1.5 text-[10px] leading-4 text-white bg-[rgb(var(--primary-6))] rounded-sm whitespace-nowrap"
|
|
2763
|
+
}, xt = ["title"], kt = {
|
|
2764
|
+
key: 1,
|
|
2765
|
+
class: "w-px h-3 bg-[var(--color-border-2)]"
|
|
2766
|
+
}, wt = /* @__PURE__ */ _({
|
|
2767
|
+
name: "DesignerOverlay",
|
|
2768
|
+
__name: "DesignerOverlay",
|
|
2769
|
+
props: {
|
|
2770
|
+
hoverStyle: {},
|
|
2771
|
+
selectStyle: {},
|
|
2772
|
+
toolbarStyle: {},
|
|
2773
|
+
selectedNodeName: {},
|
|
2774
|
+
hoveredNodeName: {},
|
|
2775
|
+
designer: {},
|
|
2776
|
+
dragging: { type: Boolean }
|
|
2777
|
+
},
|
|
2778
|
+
setup(s) {
|
|
2779
|
+
const e = s;
|
|
2780
|
+
function t() {
|
|
2781
|
+
const r = e.designer.selectedNodeId.value;
|
|
2782
|
+
r && e.designer.actions.onNodeMoveUp?.(r);
|
|
2783
|
+
}
|
|
2784
|
+
function n() {
|
|
2785
|
+
const r = e.designer.selectedNodeId.value;
|
|
2786
|
+
r && e.designer.actions.onNodeMoveDown?.(r);
|
|
2787
|
+
}
|
|
2788
|
+
function i() {
|
|
2789
|
+
const r = e.designer.selectedNodeId.value;
|
|
2790
|
+
r && e.designer.actions.onNodeDelete?.(r);
|
|
2791
|
+
}
|
|
2792
|
+
return (r, o) => (a(), m(T, null, [
|
|
2793
|
+
s.hoverStyle ? (a(), m("div", {
|
|
2794
|
+
key: 0,
|
|
2795
|
+
class: "fixed pointer-events-none z-[999]",
|
|
2796
|
+
style: j(s.hoverStyle)
|
|
2797
|
+
}, [
|
|
2798
|
+
s.hoveredNodeName ? (a(), m("span", bt, D(s.hoveredNodeName), 1)) : S("", !0),
|
|
2799
|
+
o[0] || (o[0] = p("div", { class: "w-full h-full border-[2px] border-dashed border-[rgba(var(--primary-6),0.6)] bg-[rgba(var(--primary-6),0.04)]" }, null, -1))
|
|
2800
|
+
], 4)) : S("", !0),
|
|
2801
|
+
s.selectStyle ? (a(), m("div", {
|
|
2802
|
+
key: 1,
|
|
2803
|
+
class: "fixed pointer-events-none z-[999] border-[2px] border-solid border-[rgb(var(--primary-6))] bg-[rgba(var(--primary-6),0.06)]",
|
|
2804
|
+
style: j(s.selectStyle)
|
|
2805
|
+
}, null, 4)) : S("", !0),
|
|
2806
|
+
s.toolbarStyle ? (a(), m("div", {
|
|
2807
|
+
key: 2,
|
|
2808
|
+
class: "fixed pointer-events-none z-[1000]",
|
|
2809
|
+
style: j(s.toolbarStyle)
|
|
2810
|
+
}, [
|
|
2811
|
+
p("div", {
|
|
2812
|
+
class: q([s.dragging ? "pointer-events-none" : "pointer-events-auto", "flex items-center h-full bg-[var(--color-bg-5)] rounded shadow-sm border border-[var(--color-border-2)]"])
|
|
2813
|
+
}, [
|
|
2814
|
+
s.selectedNodeName ? (a(), m("span", {
|
|
2815
|
+
key: 0,
|
|
2816
|
+
class: "px-2 text-xs text-[var(--color-text-2)] truncate max-w-[60px]",
|
|
2817
|
+
title: s.selectedNodeName
|
|
2818
|
+
}, D(s.selectedNodeName), 9, xt)) : S("", !0),
|
|
2819
|
+
s.selectedNodeName ? (a(), m("div", kt)) : S("", !0),
|
|
2820
|
+
p("button", {
|
|
2821
|
+
class: "flex items-center justify-center w-6 h-6 text-[var(--color-text-2)] hover:text-[rgb(var(--primary-6))] hover:bg-[rgba(var(--primary-6),0.1)] rounded transition-colors",
|
|
2822
|
+
title: "上移",
|
|
2823
|
+
onClick: t
|
|
2824
|
+
}, [
|
|
2825
|
+
N(H(Be), { size: 14 })
|
|
2826
|
+
]),
|
|
2827
|
+
p("button", {
|
|
2828
|
+
class: "flex items-center justify-center w-6 h-6 text-[var(--color-text-2)] hover:text-[rgb(var(--primary-6))] hover:bg-[rgba(var(--primary-6),0.1)] rounded transition-colors",
|
|
2829
|
+
title: "下移",
|
|
2830
|
+
onClick: n
|
|
2831
|
+
}, [
|
|
2832
|
+
N(H(Oe), { size: 14 })
|
|
2833
|
+
]),
|
|
2834
|
+
p("button", {
|
|
2835
|
+
class: "flex items-center justify-center w-6 h-6 text-[var(--color-text-3)] hover:text-[rgb(var(--danger-6))] hover:bg-[rgba(var(--danger-6),0.1)] rounded transition-colors",
|
|
2836
|
+
title: "删除",
|
|
2837
|
+
onClick: i
|
|
2838
|
+
}, [
|
|
2839
|
+
N(H(Re), { size: 14 })
|
|
2840
|
+
])
|
|
2841
|
+
], 2)
|
|
2842
|
+
], 4)) : S("", !0)
|
|
2843
|
+
], 64));
|
|
2844
|
+
}
|
|
2845
|
+
}), Ct = /* @__PURE__ */ _({
|
|
2846
|
+
name: "DesignerWorkspace",
|
|
2847
|
+
__name: "Workspace",
|
|
2848
|
+
emits: ["nodeClick", "nodeHover"],
|
|
2849
|
+
setup(s, { emit: e }) {
|
|
2850
|
+
const t = F("designer-engine"), n = A();
|
|
2851
|
+
let i = null, r = [];
|
|
2852
|
+
const o = le(), c = le(null), l = A(!1);
|
|
2853
|
+
se(() => {
|
|
2854
|
+
if (!n.value) return;
|
|
2855
|
+
i = new mt({ materialStore: t.materialStore }), i.mount(n.value);
|
|
2856
|
+
const f = n.value.querySelector("iframe");
|
|
2857
|
+
document.addEventListener("dragstart", () => {
|
|
2858
|
+
f && (f.style.pointerEvents = "none"), l.value = !0;
|
|
2859
|
+
}), document.addEventListener("dragend", () => {
|
|
2860
|
+
f && (f.style.pointerEvents = ""), l.value = !1;
|
|
2861
|
+
});
|
|
2862
|
+
const b = new gt({
|
|
2863
|
+
simulator: i,
|
|
2864
|
+
engine: t,
|
|
2865
|
+
materialStore: t.materialStore,
|
|
2866
|
+
actions: {
|
|
2867
|
+
onNodeDelete: (O) => {
|
|
2868
|
+
t.select(null), t.removeNode(O);
|
|
2869
|
+
},
|
|
2870
|
+
onNodeMoveUp: (O) => t.moveNodeUp(O),
|
|
2871
|
+
onNodeMoveDown: (O) => t.moveNodeDown(O)
|
|
2872
|
+
}
|
|
2873
|
+
});
|
|
2874
|
+
b.activate(), o.value = b, c.value = vt(b), i.onReady(() => {
|
|
2875
|
+
d();
|
|
2876
|
+
});
|
|
2877
|
+
const v = () => {
|
|
2878
|
+
d();
|
|
2879
|
+
};
|
|
2880
|
+
u.on(G, v), r.push(() => u.off(G, v));
|
|
2881
|
+
const C = () => {
|
|
2882
|
+
t.select(null), d();
|
|
2883
|
+
};
|
|
2884
|
+
u.on(J, C), r.push(() => u.off(J, C));
|
|
2885
|
+
const B = (O) => {
|
|
2886
|
+
if (O.action === "props" || O.action === "events" || O.action === "directive") {
|
|
2887
|
+
if (i?.isMounted() && O.nodeId) {
|
|
2888
|
+
const re = t.getActiveBlock();
|
|
2889
|
+
if (re) {
|
|
2890
|
+
const oe = re.findNode(O.nodeId);
|
|
2891
|
+
oe && i.renderNodeUpdate(O.nodeId, oe.toJSON());
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
} else
|
|
2895
|
+
d();
|
|
2896
|
+
};
|
|
2897
|
+
u.on(k, B), r.push(() => u.off(k, B));
|
|
2898
|
+
const g = () => {
|
|
2899
|
+
d();
|
|
2900
|
+
};
|
|
2901
|
+
u.on(U, g), r.push(() => u.off(U, g));
|
|
2902
|
+
const I = () => {
|
|
2903
|
+
d();
|
|
2904
|
+
};
|
|
2905
|
+
u.on(L, I), r.push(() => u.off(L, I));
|
|
2906
|
+
}), pe(() => {
|
|
2907
|
+
for (const f of r) f();
|
|
2908
|
+
r = [], o.value?.deactivate(), i?.destroy(), o.value = void 0, c.value = null, i = null;
|
|
2909
|
+
});
|
|
2910
|
+
function d() {
|
|
2911
|
+
if (!i?.isMounted()) return;
|
|
2912
|
+
const f = t.getActiveBlockJSON()?.rootNode ?? null;
|
|
2913
|
+
i.renderBlock(f);
|
|
2914
|
+
}
|
|
2915
|
+
function h(f) {
|
|
2916
|
+
f.preventDefault(), f.dataTransfer && (f.dataTransfer.dropEffect = "copy");
|
|
2917
|
+
}
|
|
2918
|
+
function y(f) {
|
|
2919
|
+
f.preventDefault();
|
|
2920
|
+
const b = f.dataTransfer?.getData("component-name");
|
|
2921
|
+
if (!b) return;
|
|
2922
|
+
const v = t.materialStore.getMeta(b);
|
|
2923
|
+
if (!v?.snippets?.length) return;
|
|
2924
|
+
const C = v.snippets[0], B = t.getActiveBlock();
|
|
2925
|
+
if (!B?.rootNode) return;
|
|
2926
|
+
const g = new me(b, {
|
|
2927
|
+
props: C.props
|
|
2928
|
+
});
|
|
2929
|
+
t.addNode(B.rootNode.id, g), t.select(g.id);
|
|
2930
|
+
}
|
|
2931
|
+
return (f, b) => (a(), m("div", {
|
|
2932
|
+
ref_key: "containerRef",
|
|
2933
|
+
ref: n,
|
|
2934
|
+
class: "flex-1 overflow-hidden bg-[var(--color-fill-1)] relative",
|
|
2935
|
+
onDragover: h,
|
|
2936
|
+
onDrop: y
|
|
2937
|
+
}, [
|
|
2938
|
+
c.value ? (a(), w(wt, {
|
|
2939
|
+
key: 0,
|
|
2940
|
+
"hover-style": c.value.hoverStyle.value,
|
|
2941
|
+
"select-style": c.value.selectStyle.value,
|
|
2942
|
+
"toolbar-style": c.value.toolbarStyle.value,
|
|
2943
|
+
"selected-node-name": c.value.selectedNodeName.value,
|
|
2944
|
+
"hovered-node-name": c.value.hoveredNodeName.value,
|
|
2945
|
+
designer: c.value.designer,
|
|
2946
|
+
dragging: l.value
|
|
2947
|
+
}, null, 8, ["hover-style", "select-style", "toolbar-style", "selected-node-name", "hovered-node-name", "designer", "dragging"])) : S("", !0)
|
|
2948
|
+
], 544));
|
|
2949
|
+
}
|
|
2950
|
+
});
|
|
2951
|
+
class St {
|
|
2952
|
+
/** Engine 引用(提供选中节点和物料查询) */
|
|
2953
|
+
engine;
|
|
2954
|
+
/**
|
|
2955
|
+
* 创建 Setter 实例
|
|
2956
|
+
*
|
|
2957
|
+
* @param engine - Engine 实例
|
|
2958
|
+
*/
|
|
2959
|
+
constructor(e) {
|
|
2960
|
+
this.engine = e;
|
|
2961
|
+
}
|
|
2962
|
+
// ── 读取 ──────────────────────────────────────────────
|
|
2963
|
+
/**
|
|
2964
|
+
* 获取当前选中节点的属性面板字段列表
|
|
2965
|
+
*
|
|
2966
|
+
* 合并 ComponentMeta 的属性 schema 与节点的实际 props 值。
|
|
2967
|
+
* 没有选中节点或找不到 ComponentMeta 时返回空数组。
|
|
2968
|
+
*
|
|
2969
|
+
* @returns SetterField 数组
|
|
2970
|
+
*/
|
|
2971
|
+
getFields() {
|
|
2972
|
+
const e = this.engine.getSelected();
|
|
2973
|
+
if (!e) return [];
|
|
2974
|
+
const t = this.engine.materialStore.getMeta(e.name);
|
|
2975
|
+
return t ? t.props.map((n) => ({
|
|
2976
|
+
name: n.name,
|
|
2977
|
+
title: n.title,
|
|
2978
|
+
type: n.type,
|
|
2979
|
+
group: n.group,
|
|
2980
|
+
value: e.props[n.name] ?? n.defaultValue,
|
|
2981
|
+
defaultValue: n.defaultValue,
|
|
2982
|
+
description: n.description,
|
|
2983
|
+
required: n.required,
|
|
2984
|
+
options: n.options,
|
|
2985
|
+
order: n.order
|
|
2986
|
+
})) : [];
|
|
2987
|
+
}
|
|
2988
|
+
/**
|
|
2989
|
+
* 获取当前选中节点的事件列表
|
|
2990
|
+
*
|
|
2991
|
+
* 来自 ComponentMeta.events,定义组件支持哪些事件。
|
|
2992
|
+
*
|
|
2993
|
+
* @returns EventMeta 数组
|
|
2994
|
+
*/
|
|
2995
|
+
getEvents() {
|
|
2996
|
+
const e = this.engine.getSelected();
|
|
2997
|
+
return e ? this.engine.materialStore.getMeta(e.name)?.events ?? [] : [];
|
|
2998
|
+
}
|
|
2999
|
+
/**
|
|
3000
|
+
* 获取当前选中节点的插槽列表
|
|
3001
|
+
*
|
|
3002
|
+
* 来自 ComponentMeta.slots,定义组件支持哪些插槽。
|
|
3003
|
+
*
|
|
3004
|
+
* @returns SlotMeta 数组
|
|
3005
|
+
*/
|
|
3006
|
+
getSlots() {
|
|
3007
|
+
const e = this.engine.getSelected();
|
|
3008
|
+
return e ? this.engine.materialStore.getMeta(e.name)?.slots ?? [] : [];
|
|
3009
|
+
}
|
|
3010
|
+
/**
|
|
3011
|
+
* 获取当前选中节点的 ComponentMeta
|
|
3012
|
+
*
|
|
3013
|
+
* @returns ComponentMeta,未选中时返回 null
|
|
3014
|
+
*/
|
|
3015
|
+
getSelectedNodeMeta() {
|
|
3016
|
+
const e = this.engine.getSelected();
|
|
3017
|
+
return e ? this.engine.materialStore.getMeta(e.name) : null;
|
|
3018
|
+
}
|
|
3019
|
+
/**
|
|
3020
|
+
* 获取当前选中节点的组件名称
|
|
3021
|
+
*
|
|
3022
|
+
* @returns 组件名称,未选中时返回 null
|
|
3023
|
+
*/
|
|
3024
|
+
getSelectedNodeName() {
|
|
3025
|
+
return this.engine.getSelected()?.name ?? null;
|
|
3026
|
+
}
|
|
3027
|
+
// ── 写入 ──────────────────────────────────────────────
|
|
3028
|
+
/**
|
|
3029
|
+
* 设置属性值
|
|
3030
|
+
*
|
|
3031
|
+
* 直接委托给 NodeModel.setProp(),触发 EVENT_NODE_CHANGE。
|
|
3032
|
+
*
|
|
3033
|
+
* @param name - 属性名
|
|
3034
|
+
* @param value - 属性值
|
|
3035
|
+
*/
|
|
3036
|
+
setProp(e, t) {
|
|
3037
|
+
const n = this.engine.getSelected();
|
|
3038
|
+
n && n.setProp(e, t);
|
|
3039
|
+
}
|
|
3040
|
+
/**
|
|
3041
|
+
* 设置事件处理函数
|
|
3042
|
+
*
|
|
3043
|
+
* 委托给 NodeModel.setEvent(),触发 EVENT_NODE_CHANGE。
|
|
3044
|
+
*
|
|
3045
|
+
* @param name - 事件名
|
|
3046
|
+
* @param handler - 处理函数名称或表达式
|
|
3047
|
+
*/
|
|
3048
|
+
setEvent(e, t) {
|
|
3049
|
+
const n = this.engine.getSelected();
|
|
3050
|
+
n && n.setEvent(e, t);
|
|
3051
|
+
}
|
|
3052
|
+
/**
|
|
3053
|
+
* 添加指令
|
|
3054
|
+
*
|
|
3055
|
+
* 委托给 NodeModel.addDirective(),触发 EVENT_NODE_CHANGE。
|
|
3056
|
+
*
|
|
3057
|
+
* @param directive - 指令绑定对象
|
|
3058
|
+
*/
|
|
3059
|
+
addDirective(e) {
|
|
3060
|
+
const t = this.engine.getSelected();
|
|
3061
|
+
t && t.addDirective(e);
|
|
3062
|
+
}
|
|
3063
|
+
/**
|
|
3064
|
+
* 移除指令
|
|
3065
|
+
*
|
|
3066
|
+
* 委托给 NodeModel.removeDirective(),触发 EVENT_NODE_CHANGE。
|
|
3067
|
+
*
|
|
3068
|
+
* @param name - 指令名
|
|
3069
|
+
*/
|
|
3070
|
+
removeDirective(e) {
|
|
3071
|
+
const t = this.engine.getSelected();
|
|
3072
|
+
t && t.removeDirective(e);
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
const Nt = { class: "p-3" }, _t = {
|
|
3076
|
+
key: 0,
|
|
3077
|
+
class: "text-center text-[var(--color-text-3)] text-sm py-8"
|
|
3078
|
+
}, It = { class: "mb-4 pb-3 border-b border-[var(--color-border-2)]" }, Bt = { class: "text-sm font-medium text-[var(--color-text-1)]" }, Ot = {
|
|
3079
|
+
key: 0,
|
|
3080
|
+
class: "space-y-3"
|
|
3081
|
+
}, Rt = { class: "text-xs text-[var(--color-text-2)]" }, At = {
|
|
3082
|
+
key: 1,
|
|
3083
|
+
class: "space-y-3 mt-4 pt-3 border-t border-[var(--color-border-2)]"
|
|
3084
|
+
}, Pt = { class: "text-xs text-[var(--color-text-2)]" }, Et = /* @__PURE__ */ _({
|
|
3085
|
+
name: "SetterPanel",
|
|
3086
|
+
__name: "SetterPanel",
|
|
3087
|
+
setup(s) {
|
|
3088
|
+
const e = F("designer-engine"), t = new St(e), n = A(0);
|
|
3089
|
+
e.onSelectionChange = () => {
|
|
3090
|
+
n.value++;
|
|
3091
|
+
};
|
|
3092
|
+
const i = (y) => {
|
|
3093
|
+
(y.action === "props" || y.action === "events" || y.action === "directive") && n.value++;
|
|
3094
|
+
};
|
|
3095
|
+
u.on(k, i);
|
|
3096
|
+
const r = x(() => (n.value, t.getSelectedNodeName())), o = x(() => (n.value, t.getFields())), c = x(() => (n.value, t.getEvents()));
|
|
3097
|
+
function l(y, f) {
|
|
3098
|
+
t.setProp(y, f);
|
|
3099
|
+
}
|
|
3100
|
+
const d = x(() => (n.value, e.getSelected()?.events ?? {}));
|
|
3101
|
+
function h(y, f) {
|
|
3102
|
+
t.setEvent(y, f);
|
|
3103
|
+
}
|
|
3104
|
+
return (y, f) => {
|
|
3105
|
+
const b = R("a-input"), v = R("a-input-number"), C = R("a-switch"), B = R("a-select");
|
|
3106
|
+
return a(), m("div", Nt, [
|
|
3107
|
+
r.value ? (a(), m(T, { key: 1 }, [
|
|
3108
|
+
p("div", It, [
|
|
3109
|
+
p("span", Bt, D(r.value), 1)
|
|
3110
|
+
]),
|
|
3111
|
+
o.value.length ? (a(), m("div", Ot, [
|
|
3112
|
+
f[0] || (f[0] = p("div", { class: "text-xs font-medium text-[var(--color-text-3)] uppercase tracking-wider mb-2" }, "属性", -1)),
|
|
3113
|
+
(a(!0), m(T, null, z(o.value, (g) => (a(), m("div", {
|
|
3114
|
+
key: g.name,
|
|
3115
|
+
class: "flex flex-col gap-1"
|
|
3116
|
+
}, [
|
|
3117
|
+
p("label", Rt, D(g.title), 1),
|
|
3118
|
+
g.type === "string" ? (a(), w(b, {
|
|
3119
|
+
key: 0,
|
|
3120
|
+
"model-value": g.value,
|
|
3121
|
+
size: "small",
|
|
3122
|
+
"onUpdate:modelValue": (I) => l(g.name, I)
|
|
3123
|
+
}, null, 8, ["model-value", "onUpdate:modelValue"])) : g.type === "number" ? (a(), w(v, {
|
|
3124
|
+
key: 1,
|
|
3125
|
+
"model-value": g.value,
|
|
3126
|
+
size: "small",
|
|
3127
|
+
"onUpdate:modelValue": (I) => l(g.name, I)
|
|
3128
|
+
}, null, 8, ["model-value", "onUpdate:modelValue"])) : g.type === "boolean" ? (a(), w(C, {
|
|
3129
|
+
key: 2,
|
|
3130
|
+
"model-value": g.value,
|
|
3131
|
+
size: "small",
|
|
3132
|
+
onChange: (I) => l(g.name, I)
|
|
3133
|
+
}, null, 8, ["model-value", "onChange"])) : g.type === "select" && g.options ? (a(), w(B, {
|
|
3134
|
+
key: 3,
|
|
3135
|
+
"model-value": g.value,
|
|
3136
|
+
size: "small",
|
|
3137
|
+
options: g.options,
|
|
3138
|
+
onChange: (I) => l(g.name, I)
|
|
3139
|
+
}, null, 8, ["model-value", "options", "onChange"])) : (a(), w(b, {
|
|
3140
|
+
key: 4,
|
|
3141
|
+
"model-value": String(g.value ?? ""),
|
|
3142
|
+
size: "small",
|
|
3143
|
+
"onUpdate:modelValue": (I) => l(g.name, I)
|
|
3144
|
+
}, null, 8, ["model-value", "onUpdate:modelValue"]))
|
|
3145
|
+
]))), 128))
|
|
3146
|
+
])) : S("", !0),
|
|
3147
|
+
c.value.length ? (a(), m("div", At, [
|
|
3148
|
+
f[1] || (f[1] = p("div", { class: "text-xs font-medium text-[var(--color-text-3)] uppercase tracking-wider mb-2" }, "事件", -1)),
|
|
3149
|
+
(a(!0), m(T, null, z(c.value, (g) => (a(), m("div", {
|
|
3150
|
+
key: g.name,
|
|
3151
|
+
class: "flex flex-col gap-1"
|
|
3152
|
+
}, [
|
|
3153
|
+
p("label", Pt, D(g.name), 1),
|
|
3154
|
+
N(b, {
|
|
3155
|
+
"model-value": d.value[g.name] ?? "",
|
|
3156
|
+
size: "small",
|
|
3157
|
+
placeholder: g.description ?? "事件处理函数",
|
|
3158
|
+
"onUpdate:modelValue": (I) => h(g.name, I)
|
|
3159
|
+
}, null, 8, ["model-value", "placeholder", "onUpdate:modelValue"])
|
|
3160
|
+
]))), 128))
|
|
3161
|
+
])) : S("", !0)
|
|
3162
|
+
], 64)) : (a(), m("div", _t, " 请选中画布中的组件 "))
|
|
3163
|
+
]);
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3166
|
+
}), Dt = { class: "w-[350px] border-l border-[var(--color-border-2)] bg-[var(--color-bg-2)] shrink-0 flex flex-col overflow-hidden" }, Tt = { class: "flex-1 overflow-y-auto" }, zt = /* @__PURE__ */ _({
|
|
3167
|
+
name: "DesignerSettings",
|
|
3168
|
+
__name: "Settings",
|
|
3169
|
+
setup(s) {
|
|
3170
|
+
return (e, t) => (a(), m("div", Dt, [
|
|
3171
|
+
t[0] || (t[0] = p("div", { class: "flex items-center h-[40px] px-4 border-b border-[var(--color-border-2)] shrink-0" }, [
|
|
3172
|
+
p("span", { class: "text-sm font-medium text-[var(--color-text-1)]" }, "属性设置")
|
|
3173
|
+
], -1)),
|
|
3174
|
+
p("div", Tt, [
|
|
3175
|
+
N(Et)
|
|
3176
|
+
])
|
|
3177
|
+
]));
|
|
3178
|
+
}
|
|
3179
|
+
}), jt = { class: "flex items-center justify-between h-[28px] px-4 border-t border-[var(--color-border-2)] bg-[var(--color-bg-2)] text-xs text-[var(--color-text-3)] shrink-0" }, Lt = { class: "flex items-center gap-1" }, Vt = { class: "flex items-center gap-3" }, Mt = /* @__PURE__ */ _({
|
|
3180
|
+
name: "DesignerFooter",
|
|
3181
|
+
__name: "Footer",
|
|
3182
|
+
setup(s) {
|
|
3183
|
+
return (e, t) => (a(), m("footer", jt, [
|
|
3184
|
+
p("div", Lt, [
|
|
3185
|
+
E(e.$slots, "node-path")
|
|
3186
|
+
]),
|
|
3187
|
+
p("div", Vt, [
|
|
3188
|
+
E(e.$slots, "devtools")
|
|
3189
|
+
])
|
|
3190
|
+
]));
|
|
3191
|
+
}
|
|
3192
|
+
}), $t = { class: "h-screen w-screen flex flex-col overflow-hidden bg-[var(--color-bg-1)]" }, Ht = { class: "flex-1 flex min-h-0" }, Wt = /* @__PURE__ */ _({
|
|
3193
|
+
name: "SunnyDesignerLayout",
|
|
3194
|
+
inheritAttrs: !1,
|
|
3195
|
+
__name: "SunnyDesignerLayout",
|
|
3196
|
+
props: {
|
|
3197
|
+
title: { default: "Designer" },
|
|
3198
|
+
logo: {},
|
|
3199
|
+
widgetRegistry: {},
|
|
3200
|
+
engine: {}
|
|
3201
|
+
},
|
|
3202
|
+
setup(s) {
|
|
3203
|
+
const e = s;
|
|
3204
|
+
return ae("designer-engine", e.engine), ae("designer-material-store", e.engine.materialStore), (t, n) => (a(), m("div", $t, [
|
|
3205
|
+
N(st, {
|
|
3206
|
+
title: s.title,
|
|
3207
|
+
logo: s.logo
|
|
3208
|
+
}, {
|
|
3209
|
+
toolbar: P(() => [
|
|
3210
|
+
E(t.$slots, "toolbar")
|
|
3211
|
+
]),
|
|
3212
|
+
actions: P(() => [
|
|
3213
|
+
E(t.$slots, "actions")
|
|
3214
|
+
]),
|
|
3215
|
+
_: 3
|
|
3216
|
+
}, 8, ["title", "logo"]),
|
|
3217
|
+
p("div", Ht, [
|
|
3218
|
+
N(pt, { "widget-registry": s.widgetRegistry }, null, 8, ["widget-registry"]),
|
|
3219
|
+
N(Ct, null, {
|
|
3220
|
+
default: P(() => [
|
|
3221
|
+
E(t.$slots, "workspace")
|
|
3222
|
+
]),
|
|
3223
|
+
_: 3
|
|
3224
|
+
}),
|
|
3225
|
+
N(zt, null, {
|
|
3226
|
+
default: P(() => [
|
|
3227
|
+
E(t.$slots, "settings")
|
|
3228
|
+
]),
|
|
3229
|
+
_: 3
|
|
3230
|
+
})
|
|
3231
|
+
]),
|
|
3232
|
+
N(Mt, null, {
|
|
3233
|
+
"node-path": P(() => [
|
|
3234
|
+
E(t.$slots, "node-path")
|
|
3235
|
+
]),
|
|
3236
|
+
devtools: P(() => [
|
|
3237
|
+
E(t.$slots, "devtools")
|
|
3238
|
+
]),
|
|
3239
|
+
_: 3
|
|
3240
|
+
})
|
|
3241
|
+
]));
|
|
3242
|
+
}
|
|
3243
|
+
});
|
|
3244
|
+
class Ut {
|
|
3245
|
+
/** Widget 存储(name → Widget) */
|
|
3246
|
+
widgets = /* @__PURE__ */ new Map();
|
|
3247
|
+
/**
|
|
3248
|
+
* 注册 Widget
|
|
3249
|
+
*
|
|
3250
|
+
* 同名 Widget 会覆盖并打印警告。
|
|
3251
|
+
*
|
|
3252
|
+
* @param widget - Widget 定义
|
|
3253
|
+
*/
|
|
3254
|
+
register(e) {
|
|
3255
|
+
this.widgets.has(e.name) && console.warn(`[WidgetRegistry] Overwriting existing widget: ${e.name}`), this.widgets.set(e.name, e);
|
|
3256
|
+
}
|
|
3257
|
+
/**
|
|
3258
|
+
* 批量注册
|
|
3259
|
+
*
|
|
3260
|
+
* @param widgets - Widget 定义数组
|
|
3261
|
+
*/
|
|
3262
|
+
registerAll(e) {
|
|
3263
|
+
for (const t of e)
|
|
3264
|
+
this.register(t);
|
|
3265
|
+
}
|
|
3266
|
+
/**
|
|
3267
|
+
* 取消注册
|
|
3268
|
+
*
|
|
3269
|
+
* @param name - Widget 名称
|
|
3270
|
+
*/
|
|
3271
|
+
unregister(e) {
|
|
3272
|
+
this.widgets.delete(e);
|
|
3273
|
+
}
|
|
3274
|
+
/**
|
|
3275
|
+
* 获取指定 Widget
|
|
3276
|
+
*
|
|
3277
|
+
* @param name - Widget 名称
|
|
3278
|
+
* @returns Widget 定义,不存在时返回 null
|
|
3279
|
+
*/
|
|
3280
|
+
get(e) {
|
|
3281
|
+
return this.widgets.get(e) ?? null;
|
|
3282
|
+
}
|
|
3283
|
+
/**
|
|
3284
|
+
* 获取指定区域的所有 Widget(按 order 排序)
|
|
3285
|
+
*
|
|
3286
|
+
* @param region - 区域名(如 'apps')
|
|
3287
|
+
* @returns 排序后的 Widget 数组
|
|
3288
|
+
*/
|
|
3289
|
+
getByRegion(e) {
|
|
3290
|
+
return this.sorted().filter((t) => t.region === e);
|
|
3291
|
+
}
|
|
3292
|
+
/**
|
|
3293
|
+
* 按 openType 过滤(在指定区域内)
|
|
3294
|
+
*
|
|
3295
|
+
* @param region - 区域名
|
|
3296
|
+
* @param openType - 打开方式
|
|
3297
|
+
* @returns 匹配的 Widget 数组
|
|
3298
|
+
*/
|
|
3299
|
+
getByOpenType(e, t) {
|
|
3300
|
+
return this.getByRegion(e).filter((n) => n.openType === t);
|
|
3301
|
+
}
|
|
3302
|
+
/**
|
|
3303
|
+
* 获取所有 panel 类型 Widget(用于渲染面板)
|
|
3304
|
+
*
|
|
3305
|
+
* @param region - 区域名
|
|
3306
|
+
* @returns panel 类型的 Widget 数组
|
|
3307
|
+
*/
|
|
3308
|
+
getPanelWidgets(e) {
|
|
3309
|
+
return this.getByOpenType(e, "panel");
|
|
3310
|
+
}
|
|
3311
|
+
/**
|
|
3312
|
+
* 获取所有非 panel 类型 Widget(dialog/link)
|
|
3313
|
+
*
|
|
3314
|
+
* @param region - 区域名
|
|
3315
|
+
* @returns 非 panel 类型的 Widget 数组
|
|
3316
|
+
*/
|
|
3317
|
+
getOtherWidgets(e) {
|
|
3318
|
+
return this.getByRegion(e).filter((t) => t.openType !== "panel");
|
|
3319
|
+
}
|
|
3320
|
+
/**
|
|
3321
|
+
* 清空所有 Widget
|
|
3322
|
+
*/
|
|
3323
|
+
clear() {
|
|
3324
|
+
this.widgets.clear();
|
|
3325
|
+
}
|
|
3326
|
+
/**
|
|
3327
|
+
* 已注册数量
|
|
3328
|
+
*/
|
|
3329
|
+
get size() {
|
|
3330
|
+
return this.widgets.size;
|
|
3331
|
+
}
|
|
3332
|
+
/** 按 order 字段升序排序 */
|
|
3333
|
+
sorted() {
|
|
3334
|
+
return Array.from(this.widgets.values()).sort(
|
|
3335
|
+
(e, t) => (e.order ?? 100) - (t.order ?? 100)
|
|
3336
|
+
);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
const Jt = /* @__PURE__ */ Symbol("ArcoConfigProvider"), Ft = "arco", qt = "$arco", be = (s) => {
|
|
3340
|
+
var e, t, n;
|
|
3341
|
+
const i = Ie();
|
|
3342
|
+
return `${(n = (t = F(Jt, void 0)?.prefixCls) != null ? t : (e = i?.appContext.config.globalProperties[qt]) == null ? void 0 : e.classPrefix) != null ? n : Ft}-${s}`;
|
|
3343
|
+
}, Gt = Object.prototype.toString;
|
|
3344
|
+
function xe(s) {
|
|
3345
|
+
return Gt.call(s) === "[object Number]" && s === s;
|
|
3346
|
+
}
|
|
3347
|
+
var ke = (s, e) => {
|
|
3348
|
+
for (const [t, n] of e)
|
|
3349
|
+
s[t] = n;
|
|
3350
|
+
return s;
|
|
3351
|
+
};
|
|
3352
|
+
const Kt = _({
|
|
3353
|
+
name: "IconFile",
|
|
3354
|
+
props: {
|
|
3355
|
+
size: {
|
|
3356
|
+
type: [Number, String]
|
|
3357
|
+
},
|
|
3358
|
+
strokeWidth: {
|
|
3359
|
+
type: Number,
|
|
3360
|
+
default: 4
|
|
3361
|
+
},
|
|
3362
|
+
strokeLinecap: {
|
|
3363
|
+
type: String,
|
|
3364
|
+
default: "butt",
|
|
3365
|
+
validator: (s) => ["butt", "round", "square"].includes(s)
|
|
3366
|
+
},
|
|
3367
|
+
strokeLinejoin: {
|
|
3368
|
+
type: String,
|
|
3369
|
+
default: "miter",
|
|
3370
|
+
validator: (s) => ["arcs", "bevel", "miter", "miter-clip", "round"].includes(s)
|
|
3371
|
+
},
|
|
3372
|
+
rotate: Number,
|
|
3373
|
+
spin: Boolean
|
|
3374
|
+
},
|
|
3375
|
+
emits: {
|
|
3376
|
+
click: (s) => !0
|
|
3377
|
+
},
|
|
3378
|
+
setup(s, { emit: e }) {
|
|
3379
|
+
const t = be("icon"), n = x(() => [t, `${t}-file`, { [`${t}-spin`]: s.spin }]), i = x(() => {
|
|
3380
|
+
const r = {};
|
|
3381
|
+
return s.size && (r.fontSize = xe(s.size) ? `${s.size}px` : s.size), s.rotate && (r.transform = `rotate(${s.rotate}deg)`), r;
|
|
3382
|
+
});
|
|
3383
|
+
return {
|
|
3384
|
+
cls: n,
|
|
3385
|
+
innerStyle: i,
|
|
3386
|
+
onClick: (r) => {
|
|
3387
|
+
e("click", r);
|
|
3388
|
+
}
|
|
3389
|
+
};
|
|
3390
|
+
}
|
|
3391
|
+
}), Yt = ["stroke-width", "stroke-linecap", "stroke-linejoin"];
|
|
3392
|
+
function Zt(s, e, t, n, i, r) {
|
|
3393
|
+
return a(), m("svg", {
|
|
3394
|
+
viewBox: "0 0 48 48",
|
|
3395
|
+
fill: "none",
|
|
3396
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3397
|
+
stroke: "currentColor",
|
|
3398
|
+
class: q(s.cls),
|
|
3399
|
+
style: j(s.innerStyle),
|
|
3400
|
+
"stroke-width": s.strokeWidth,
|
|
3401
|
+
"stroke-linecap": s.strokeLinecap,
|
|
3402
|
+
"stroke-linejoin": s.strokeLinejoin,
|
|
3403
|
+
onClick: e[0] || (e[0] = (...o) => s.onClick && s.onClick(...o))
|
|
3404
|
+
}, e[1] || (e[1] = [
|
|
3405
|
+
p("path", { d: "M16 21h16m-16 8h10m11 13H11a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h21l7 7v27a2 2 0 0 1-2 2Z" }, null, -1)
|
|
3406
|
+
]), 14, Yt);
|
|
3407
|
+
}
|
|
3408
|
+
var X = /* @__PURE__ */ ke(Kt, [["render", Zt]]);
|
|
3409
|
+
const Xt = Object.assign(X, {
|
|
3410
|
+
install: (s, e) => {
|
|
3411
|
+
var t;
|
|
3412
|
+
const n = (t = e?.iconPrefix) != null ? t : "";
|
|
3413
|
+
s.component(n + X.name, X);
|
|
3414
|
+
}
|
|
3415
|
+
}), Qt = _({
|
|
3416
|
+
name: "IconApps",
|
|
3417
|
+
props: {
|
|
3418
|
+
size: {
|
|
3419
|
+
type: [Number, String]
|
|
3420
|
+
},
|
|
3421
|
+
strokeWidth: {
|
|
3422
|
+
type: Number,
|
|
3423
|
+
default: 4
|
|
3424
|
+
},
|
|
3425
|
+
strokeLinecap: {
|
|
3426
|
+
type: String,
|
|
3427
|
+
default: "butt",
|
|
3428
|
+
validator: (s) => ["butt", "round", "square"].includes(s)
|
|
3429
|
+
},
|
|
3430
|
+
strokeLinejoin: {
|
|
3431
|
+
type: String,
|
|
3432
|
+
default: "miter",
|
|
3433
|
+
validator: (s) => ["arcs", "bevel", "miter", "miter-clip", "round"].includes(s)
|
|
3434
|
+
},
|
|
3435
|
+
rotate: Number,
|
|
3436
|
+
spin: Boolean
|
|
3437
|
+
},
|
|
3438
|
+
emits: {
|
|
3439
|
+
click: (s) => !0
|
|
3440
|
+
},
|
|
3441
|
+
setup(s, { emit: e }) {
|
|
3442
|
+
const t = be("icon"), n = x(() => [t, `${t}-apps`, { [`${t}-spin`]: s.spin }]), i = x(() => {
|
|
3443
|
+
const r = {};
|
|
3444
|
+
return s.size && (r.fontSize = xe(s.size) ? `${s.size}px` : s.size), s.rotate && (r.transform = `rotate(${s.rotate}deg)`), r;
|
|
3445
|
+
});
|
|
3446
|
+
return {
|
|
3447
|
+
cls: n,
|
|
3448
|
+
innerStyle: i,
|
|
3449
|
+
onClick: (r) => {
|
|
3450
|
+
e("click", r);
|
|
3451
|
+
}
|
|
3452
|
+
};
|
|
3453
|
+
}
|
|
3454
|
+
}), en = ["stroke-width", "stroke-linecap", "stroke-linejoin"];
|
|
3455
|
+
function tn(s, e, t, n, i, r) {
|
|
3456
|
+
return a(), m("svg", {
|
|
3457
|
+
viewBox: "0 0 48 48",
|
|
3458
|
+
fill: "none",
|
|
3459
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3460
|
+
stroke: "currentColor",
|
|
3461
|
+
class: q(s.cls),
|
|
3462
|
+
style: j(s.innerStyle),
|
|
3463
|
+
"stroke-width": s.strokeWidth,
|
|
3464
|
+
"stroke-linecap": s.strokeLinecap,
|
|
3465
|
+
"stroke-linejoin": s.strokeLinejoin,
|
|
3466
|
+
onClick: e[0] || (e[0] = (...o) => s.onClick && s.onClick(...o))
|
|
3467
|
+
}, e[1] || (e[1] = [
|
|
3468
|
+
p("path", { d: "M7 7h13v13H7zM28 7h13v13H28zM7 28h13v13H7zM28 28h13v13H28z" }, null, -1)
|
|
3469
|
+
]), 14, en);
|
|
3470
|
+
}
|
|
3471
|
+
var Q = /* @__PURE__ */ ke(Qt, [["render", tn]]);
|
|
3472
|
+
const nn = Object.assign(Q, {
|
|
3473
|
+
install: (s, e) => {
|
|
3474
|
+
var t;
|
|
3475
|
+
const n = (t = e?.iconPrefix) != null ? t : "";
|
|
3476
|
+
s.component(n + Q.name, Q);
|
|
3477
|
+
}
|
|
3478
|
+
}), sn = { class: "p-3" }, rn = { class: "space-y-1" }, on = ["onClick"], an = { class: "text-sm truncate" }, ln = {
|
|
3479
|
+
key: 0,
|
|
3480
|
+
class: "text-center text-[var(--color-text-3)] text-sm py-8"
|
|
3481
|
+
}, cn = /* @__PURE__ */ _({
|
|
3482
|
+
name: "PagesWidget",
|
|
3483
|
+
__name: "PagesWidget",
|
|
3484
|
+
setup(s) {
|
|
3485
|
+
const e = F("designer-engine"), t = x(() => e.project.pages ?? []), n = x(() => e.project.activePageId ?? "");
|
|
3486
|
+
function i(r) {
|
|
3487
|
+
e.switchPage(r);
|
|
3488
|
+
}
|
|
3489
|
+
return (r, o) => {
|
|
3490
|
+
const c = R("icon-file");
|
|
3491
|
+
return a(), m("div", sn, [
|
|
3492
|
+
p("div", rn, [
|
|
3493
|
+
(a(!0), m(T, null, z(t.value, (l) => (a(), m("div", {
|
|
3494
|
+
key: l.id,
|
|
3495
|
+
class: q(["flex items-center gap-2 px-2 py-1.5 rounded cursor-pointer transition-colors", [
|
|
3496
|
+
l.id === n.value ? "bg-[rgba(var(--primary-6),0.1)] text-[rgb(var(--primary-6))]" : "text-[var(--color-text-2)] hover:bg-[var(--color-fill-2)] hover:text-[var(--color-text-1)]"
|
|
3497
|
+
]]),
|
|
3498
|
+
onClick: (d) => i(l.id)
|
|
3499
|
+
}, [
|
|
3500
|
+
N(c, { size: 16 }),
|
|
3501
|
+
p("span", an, D(l.name), 1)
|
|
3502
|
+
], 10, on))), 128))
|
|
3503
|
+
]),
|
|
3504
|
+
t.value.length === 0 ? (a(), m("div", ln, " 暂无页面 ")) : S("", !0)
|
|
3505
|
+
]);
|
|
3506
|
+
};
|
|
3507
|
+
}
|
|
3508
|
+
}), dn = { class: "flex flex-col h-full" }, un = { class: "px-3 py-2 border-b border-[var(--color-border-2)] shrink-0" }, hn = { class: "flex-1 overflow-y-auto p-3" }, pn = { class: "grid grid-cols-2 gap-2" }, mn = ["onDragstart"], fn = { class: "flex items-center justify-center w-8 h-8 rounded bg-[var(--color-fill-2)]" }, gn = { class: "text-xs text-[var(--color-text-2)] text-center truncate w-full" }, vn = {
|
|
3509
|
+
key: 0,
|
|
3510
|
+
class: "text-center text-[var(--color-text-3)] text-sm py-8"
|
|
3511
|
+
}, yn = /* @__PURE__ */ _({
|
|
3512
|
+
name: "ComponentsWidget",
|
|
3513
|
+
__name: "ComponentsWidget",
|
|
3514
|
+
setup(s) {
|
|
3515
|
+
const e = F("designer-material-store"), t = A(""), n = x(() => e?.getGroups() ?? []), i = A(n.value);
|
|
3516
|
+
function r(l) {
|
|
3517
|
+
const d = e?.getByGroup(l) ?? [];
|
|
3518
|
+
if (!t.value) return d;
|
|
3519
|
+
const h = t.value.toLowerCase();
|
|
3520
|
+
return d.filter(
|
|
3521
|
+
(y) => y.name.toLowerCase().includes(h) || y.title.toLowerCase().includes(h)
|
|
3522
|
+
);
|
|
3523
|
+
}
|
|
3524
|
+
const o = x(() => t.value ? n.value.filter((l) => r(l).length > 0) : n.value);
|
|
3525
|
+
function c(l, d) {
|
|
3526
|
+
l.dataTransfer?.setData("component-name", d.name), l.dataTransfer.effectAllowed = "copy";
|
|
3527
|
+
}
|
|
3528
|
+
return (l, d) => {
|
|
3529
|
+
const h = R("icon-search"), y = R("a-input"), f = R("icon-apps"), b = R("a-collapse-item"), v = R("a-collapse");
|
|
3530
|
+
return a(), m("div", dn, [
|
|
3531
|
+
p("div", un, [
|
|
3532
|
+
N(y, {
|
|
3533
|
+
modelValue: t.value,
|
|
3534
|
+
"onUpdate:modelValue": d[0] || (d[0] = (C) => t.value = C),
|
|
3535
|
+
placeholder: "搜索组件",
|
|
3536
|
+
"allow-clear": "",
|
|
3537
|
+
size: "small"
|
|
3538
|
+
}, {
|
|
3539
|
+
prefix: P(() => [
|
|
3540
|
+
N(h, { size: 14 })
|
|
3541
|
+
]),
|
|
3542
|
+
_: 1
|
|
3543
|
+
}, 8, ["modelValue"])
|
|
3544
|
+
]),
|
|
3545
|
+
p("div", hn, [
|
|
3546
|
+
N(v, {
|
|
3547
|
+
"default-active-key": i.value,
|
|
3548
|
+
bordered: !1,
|
|
3549
|
+
"expand-icon-position": "right"
|
|
3550
|
+
}, {
|
|
3551
|
+
default: P(() => [
|
|
3552
|
+
(a(!0), m(T, null, z(o.value, (C) => (a(), w(b, {
|
|
3553
|
+
key: C,
|
|
3554
|
+
header: C
|
|
3555
|
+
}, {
|
|
3556
|
+
default: P(() => [
|
|
3557
|
+
p("div", pn, [
|
|
3558
|
+
(a(!0), m(T, null, z(r(C), (B) => (a(), m("div", {
|
|
3559
|
+
key: B.name,
|
|
3560
|
+
class: "flex flex-col items-center gap-1 p-2 rounded cursor-grab border border-[var(--color-border-1)] hover:border-[rgb(var(--primary-6))] hover:bg-[rgba(var(--primary-6),0.04)] transition-colors active:cursor-grabbing",
|
|
3561
|
+
draggable: "true",
|
|
3562
|
+
onDragstart: (g) => c(g, B)
|
|
3563
|
+
}, [
|
|
3564
|
+
p("div", fn, [
|
|
3565
|
+
B.icon ? (a(), w($(B.icon), {
|
|
3566
|
+
key: 0,
|
|
3567
|
+
size: 18
|
|
3568
|
+
})) : (a(), w(f, {
|
|
3569
|
+
key: 1,
|
|
3570
|
+
size: 18
|
|
3571
|
+
}))
|
|
3572
|
+
]),
|
|
3573
|
+
p("span", gn, D(B.title), 1)
|
|
3574
|
+
], 40, mn))), 128))
|
|
3575
|
+
])
|
|
3576
|
+
]),
|
|
3577
|
+
_: 2
|
|
3578
|
+
}, 1032, ["header"]))), 128))
|
|
3579
|
+
]),
|
|
3580
|
+
_: 1
|
|
3581
|
+
}, 8, ["default-active-key"]),
|
|
3582
|
+
o.value.length === 0 ? (a(), m("div", vn, D(t.value ? "未找到匹配的组件" : "暂无注册组件"), 1)) : S("", !0)
|
|
3583
|
+
])
|
|
3584
|
+
]);
|
|
3585
|
+
};
|
|
3586
|
+
}
|
|
3587
|
+
});
|
|
3588
|
+
function bn() {
|
|
3589
|
+
return [
|
|
3590
|
+
{
|
|
3591
|
+
name: "pages",
|
|
3592
|
+
icon: Xt,
|
|
3593
|
+
title: "页面管理",
|
|
3594
|
+
component: cn,
|
|
3595
|
+
openType: "panel",
|
|
3596
|
+
order: 1,
|
|
3597
|
+
region: "apps"
|
|
3598
|
+
},
|
|
3599
|
+
{
|
|
3600
|
+
name: "components",
|
|
3601
|
+
icon: nn,
|
|
3602
|
+
title: "组件库",
|
|
3603
|
+
component: yn,
|
|
3604
|
+
openType: "panel",
|
|
3605
|
+
order: 2,
|
|
3606
|
+
region: "apps"
|
|
3607
|
+
}
|
|
3608
|
+
];
|
|
3609
|
+
}
|
|
3610
|
+
const xn = [
|
|
3611
|
+
{ name: "event", type: "MouseEvent", description: "鼠标事件" }
|
|
3612
|
+
], kn = [
|
|
3613
|
+
{ tag: "div", title: "容器", isContainer: !0 },
|
|
3614
|
+
{ tag: "span", title: "行内文本", isContainer: !0 },
|
|
3615
|
+
{ tag: "p", title: "段落", isContainer: !0 },
|
|
3616
|
+
{ tag: "h1", title: "标题 1", isContainer: !1 },
|
|
3617
|
+
{ tag: "h2", title: "标题 2", isContainer: !1 },
|
|
3618
|
+
{ tag: "h3", title: "标题 3", isContainer: !1 },
|
|
3619
|
+
{ tag: "h4", title: "标题 4", isContainer: !1 },
|
|
3620
|
+
{ tag: "h5", title: "标题 5", isContainer: !1 },
|
|
3621
|
+
{ tag: "h6", title: "标题 6", isContainer: !1 }
|
|
3622
|
+
];
|
|
3623
|
+
function wn() {
|
|
3624
|
+
return kn.map(({ tag: s, title: e, isContainer: t }) => ({
|
|
3625
|
+
name: s,
|
|
3626
|
+
title: e,
|
|
3627
|
+
component: V(s),
|
|
3628
|
+
isNativeElement: !0,
|
|
3629
|
+
category: "layout",
|
|
3630
|
+
group: "原生元素",
|
|
3631
|
+
order: 100,
|
|
3632
|
+
props: [
|
|
3633
|
+
{ name: "class", title: "类名", type: "string", group: "基础属性" },
|
|
3634
|
+
{ name: "style", title: "样式", type: "json", group: "基础属性" }
|
|
3635
|
+
],
|
|
3636
|
+
events: [
|
|
3637
|
+
{ name: "click", title: "点击", params: xn }
|
|
3638
|
+
],
|
|
3639
|
+
slots: [
|
|
3640
|
+
{ name: "default", title: "默认插槽" }
|
|
3641
|
+
],
|
|
3642
|
+
nestingRules: { isContainer: t },
|
|
3643
|
+
snippets: [
|
|
3644
|
+
{ title: e, props: {} }
|
|
3645
|
+
]
|
|
3646
|
+
}));
|
|
3647
|
+
}
|
|
3648
|
+
function Cn() {
|
|
3649
|
+
return {
|
|
3650
|
+
name: "SunnyForm",
|
|
3651
|
+
title: "表单",
|
|
3652
|
+
component: V(Ae),
|
|
3653
|
+
category: "entry",
|
|
3654
|
+
group: "表单组件",
|
|
3655
|
+
order: 1,
|
|
3656
|
+
props: [
|
|
3657
|
+
{
|
|
3658
|
+
name: "layout",
|
|
3659
|
+
title: "布局",
|
|
3660
|
+
type: "select",
|
|
3661
|
+
options: [
|
|
3662
|
+
{ label: "水平", value: "horizontal" },
|
|
3663
|
+
{ label: "垂直", value: "vertical" },
|
|
3664
|
+
{ label: "行内", value: "inline" }
|
|
3665
|
+
],
|
|
3666
|
+
defaultValue: "horizontal",
|
|
3667
|
+
group: "基础属性"
|
|
3668
|
+
},
|
|
3669
|
+
{
|
|
3670
|
+
name: "size",
|
|
3671
|
+
title: "尺寸",
|
|
3672
|
+
type: "select",
|
|
3673
|
+
options: [
|
|
3674
|
+
{ label: "迷你", value: "mini" },
|
|
3675
|
+
{ label: "小", value: "small" },
|
|
3676
|
+
{ label: "中", value: "medium" },
|
|
3677
|
+
{ label: "大", value: "large" }
|
|
3678
|
+
],
|
|
3679
|
+
defaultValue: "small",
|
|
3680
|
+
group: "基础属性"
|
|
3681
|
+
},
|
|
3682
|
+
{
|
|
3683
|
+
name: "showDefaultActions",
|
|
3684
|
+
title: "显示操作按钮",
|
|
3685
|
+
type: "boolean",
|
|
3686
|
+
defaultValue: !1,
|
|
3687
|
+
group: "基础属性"
|
|
3688
|
+
},
|
|
3689
|
+
{
|
|
3690
|
+
name: "labelWidth",
|
|
3691
|
+
title: "标签宽度",
|
|
3692
|
+
type: "string",
|
|
3693
|
+
group: "基础属性",
|
|
3694
|
+
description: '表单标签宽度,如 "100px"'
|
|
3695
|
+
},
|
|
3696
|
+
{
|
|
3697
|
+
name: "schema",
|
|
3698
|
+
title: "表单配置",
|
|
3699
|
+
type: "json",
|
|
3700
|
+
group: "高级属性",
|
|
3701
|
+
description: "FormSchema[] JSON 配置"
|
|
3702
|
+
}
|
|
3703
|
+
],
|
|
3704
|
+
events: [
|
|
3705
|
+
{ name: "handleSubmit", title: "提交", description: "表单提交时触发", params: [
|
|
3706
|
+
{ name: "values", type: "Record<string, any>", description: "表单数据" }
|
|
3707
|
+
] },
|
|
3708
|
+
{ name: "handleReset", title: "重置", description: "表单重置时触发", params: [
|
|
3709
|
+
{ name: "values", type: "Record<string, any>", description: "重置后的表单数据" }
|
|
3710
|
+
] }
|
|
3711
|
+
],
|
|
3712
|
+
slots: [
|
|
3713
|
+
{ name: "default", title: "默认插槽", description: "表单内容" }
|
|
3714
|
+
],
|
|
3715
|
+
nestingRules: { isContainer: !0 },
|
|
3716
|
+
snippets: [
|
|
3717
|
+
{
|
|
3718
|
+
title: "基础表单",
|
|
3719
|
+
description: "包含默认配置的空表单",
|
|
3720
|
+
props: { layout: "vertical", showDefaultActions: !0 }
|
|
3721
|
+
}
|
|
3722
|
+
]
|
|
3723
|
+
};
|
|
3724
|
+
}
|
|
3725
|
+
function Sn() {
|
|
3726
|
+
return {
|
|
3727
|
+
name: "SunnySelect",
|
|
3728
|
+
title: "选择器",
|
|
3729
|
+
component: V(Pe),
|
|
3730
|
+
category: "entry",
|
|
3731
|
+
group: "表单组件",
|
|
3732
|
+
order: 2,
|
|
3733
|
+
props: [
|
|
3734
|
+
{
|
|
3735
|
+
name: "modelValue",
|
|
3736
|
+
title: "值",
|
|
3737
|
+
type: "string",
|
|
3738
|
+
group: "基础属性"
|
|
3739
|
+
},
|
|
3740
|
+
{
|
|
3741
|
+
name: "placeholder",
|
|
3742
|
+
title: "占位文本",
|
|
3743
|
+
type: "string",
|
|
3744
|
+
defaultValue: "请选择",
|
|
3745
|
+
group: "基础属性"
|
|
3746
|
+
},
|
|
3747
|
+
{
|
|
3748
|
+
name: "disabled",
|
|
3749
|
+
title: "禁用",
|
|
3750
|
+
type: "boolean",
|
|
3751
|
+
defaultValue: !1,
|
|
3752
|
+
group: "基础属性"
|
|
3753
|
+
},
|
|
3754
|
+
{
|
|
3755
|
+
name: "allowClear",
|
|
3756
|
+
title: "允许清除",
|
|
3757
|
+
type: "boolean",
|
|
3758
|
+
defaultValue: !1,
|
|
3759
|
+
group: "基础属性"
|
|
3760
|
+
},
|
|
3761
|
+
{
|
|
3762
|
+
name: "multiple",
|
|
3763
|
+
title: "多选",
|
|
3764
|
+
type: "boolean",
|
|
3765
|
+
defaultValue: !1,
|
|
3766
|
+
group: "基础属性"
|
|
3767
|
+
},
|
|
3768
|
+
{
|
|
3769
|
+
name: "options",
|
|
3770
|
+
title: "选项列表",
|
|
3771
|
+
type: "json",
|
|
3772
|
+
group: "高级属性",
|
|
3773
|
+
description: "下拉选项数据"
|
|
3774
|
+
}
|
|
3775
|
+
],
|
|
3776
|
+
events: [
|
|
3777
|
+
{ name: "change", title: "变化", description: "选中值变化时触发", params: [
|
|
3778
|
+
{ name: "value", type: "string | number | array", description: "当前选中值" }
|
|
3779
|
+
] }
|
|
3780
|
+
],
|
|
3781
|
+
slots: [],
|
|
3782
|
+
nestingRules: { isContainer: !1 },
|
|
3783
|
+
snippets: [
|
|
3784
|
+
{ title: "基础选择器", props: { placeholder: "请选择" } }
|
|
3785
|
+
]
|
|
3786
|
+
};
|
|
3787
|
+
}
|
|
3788
|
+
function Nn() {
|
|
3789
|
+
return {
|
|
3790
|
+
name: "a-button",
|
|
3791
|
+
title: "Arco 按钮",
|
|
3792
|
+
component: V(Ee),
|
|
3793
|
+
category: "basic",
|
|
3794
|
+
group: "Arco 组件",
|
|
3795
|
+
order: 10,
|
|
3796
|
+
props: [
|
|
3797
|
+
{
|
|
3798
|
+
name: "type",
|
|
3799
|
+
title: "类型",
|
|
3800
|
+
type: "select",
|
|
3801
|
+
options: [
|
|
3802
|
+
{ label: "主要", value: "primary" },
|
|
3803
|
+
{ label: "次要", value: "secondary" },
|
|
3804
|
+
{ label: "轮廓", value: "outline" },
|
|
3805
|
+
{ label: "虚线", value: "dashed" },
|
|
3806
|
+
{ label: "文字", value: "text" }
|
|
3807
|
+
],
|
|
3808
|
+
defaultValue: "primary",
|
|
3809
|
+
group: "基础属性"
|
|
3810
|
+
},
|
|
3811
|
+
{
|
|
3812
|
+
name: "size",
|
|
3813
|
+
title: "尺寸",
|
|
3814
|
+
type: "select",
|
|
3815
|
+
options: [
|
|
3816
|
+
{ label: "迷你", value: "mini" },
|
|
3817
|
+
{ label: "小", value: "small" },
|
|
3818
|
+
{ label: "中", value: "medium" },
|
|
3819
|
+
{ label: "大", value: "large" }
|
|
3820
|
+
],
|
|
3821
|
+
defaultValue: "medium",
|
|
3822
|
+
group: "基础属性"
|
|
3823
|
+
},
|
|
3824
|
+
{
|
|
3825
|
+
name: "status",
|
|
3826
|
+
title: "状态",
|
|
3827
|
+
type: "select",
|
|
3828
|
+
options: [
|
|
3829
|
+
{ label: "正常", value: "normal" },
|
|
3830
|
+
{ label: "警告", value: "warning" },
|
|
3831
|
+
{ label: "危险", value: "danger" },
|
|
3832
|
+
{ label: "成功", value: "success" }
|
|
3833
|
+
],
|
|
3834
|
+
group: "基础属性"
|
|
3835
|
+
},
|
|
3836
|
+
{
|
|
3837
|
+
name: "disabled",
|
|
3838
|
+
title: "禁用",
|
|
3839
|
+
type: "boolean",
|
|
3840
|
+
defaultValue: !1,
|
|
3841
|
+
group: "基础属性"
|
|
3842
|
+
},
|
|
3843
|
+
{
|
|
3844
|
+
name: "long",
|
|
3845
|
+
title: "撑满父容器",
|
|
3846
|
+
type: "boolean",
|
|
3847
|
+
defaultValue: !1,
|
|
3848
|
+
group: "基础属性"
|
|
3849
|
+
}
|
|
3850
|
+
],
|
|
3851
|
+
events: [
|
|
3852
|
+
{ name: "click", title: "点击", params: [
|
|
3853
|
+
{ name: "event", type: "MouseEvent", description: "鼠标事件" }
|
|
3854
|
+
] }
|
|
3855
|
+
],
|
|
3856
|
+
slots: [
|
|
3857
|
+
{ name: "default", title: "默认插槽", description: "按钮内容" },
|
|
3858
|
+
{ name: "icon", title: "图标插槽", description: "按钮图标" }
|
|
3859
|
+
],
|
|
3860
|
+
nestingRules: { isContainer: !1 },
|
|
3861
|
+
snippets: [
|
|
3862
|
+
{ title: "主要按钮", props: { type: "primary" }, children: [{ name: "span", props: {} }] },
|
|
3863
|
+
{ title: "危险按钮", props: { type: "primary", status: "danger" }, children: [{ name: "span", props: {} }] }
|
|
3864
|
+
]
|
|
3865
|
+
};
|
|
3866
|
+
}
|
|
3867
|
+
function _n() {
|
|
3868
|
+
return {
|
|
3869
|
+
name: "a-input",
|
|
3870
|
+
title: "Arco 输入框",
|
|
3871
|
+
component: V(De),
|
|
3872
|
+
category: "entry",
|
|
3873
|
+
group: "Arco 组件",
|
|
3874
|
+
order: 11,
|
|
3875
|
+
props: [
|
|
3876
|
+
{
|
|
3877
|
+
name: "modelValue",
|
|
3878
|
+
title: "值",
|
|
3879
|
+
type: "string",
|
|
3880
|
+
group: "基础属性"
|
|
3881
|
+
},
|
|
3882
|
+
{
|
|
3883
|
+
name: "placeholder",
|
|
3884
|
+
title: "占位文本",
|
|
3885
|
+
type: "string",
|
|
3886
|
+
defaultValue: "请输入",
|
|
3887
|
+
group: "基础属性"
|
|
3888
|
+
},
|
|
3889
|
+
{
|
|
3890
|
+
name: "disabled",
|
|
3891
|
+
title: "禁用",
|
|
3892
|
+
type: "boolean",
|
|
3893
|
+
defaultValue: !1,
|
|
3894
|
+
group: "基础属性"
|
|
3895
|
+
},
|
|
3896
|
+
{
|
|
3897
|
+
name: "allowClear",
|
|
3898
|
+
title: "允许清除",
|
|
3899
|
+
type: "boolean",
|
|
3900
|
+
defaultValue: !1,
|
|
3901
|
+
group: "基础属性"
|
|
3902
|
+
},
|
|
3903
|
+
{
|
|
3904
|
+
name: "maxLength",
|
|
3905
|
+
title: "最大长度",
|
|
3906
|
+
type: "number",
|
|
3907
|
+
group: "基础属性"
|
|
3908
|
+
},
|
|
3909
|
+
{
|
|
3910
|
+
name: "size",
|
|
3911
|
+
title: "尺寸",
|
|
3912
|
+
type: "select",
|
|
3913
|
+
options: [
|
|
3914
|
+
{ label: "迷你", value: "mini" },
|
|
3915
|
+
{ label: "小", value: "small" },
|
|
3916
|
+
{ label: "中", value: "medium" },
|
|
3917
|
+
{ label: "大", value: "large" }
|
|
3918
|
+
],
|
|
3919
|
+
defaultValue: "medium",
|
|
3920
|
+
group: "基础属性"
|
|
3921
|
+
}
|
|
3922
|
+
],
|
|
3923
|
+
events: [
|
|
3924
|
+
{ name: "input", title: "输入", params: [
|
|
3925
|
+
{ name: "value", type: "string", description: "输入值" },
|
|
3926
|
+
{ name: "event", type: "Event", description: "原生事件" }
|
|
3927
|
+
] },
|
|
3928
|
+
{ name: "change", title: "变化", params: [
|
|
3929
|
+
{ name: "value", type: "string", description: "当前值" }
|
|
3930
|
+
] }
|
|
3931
|
+
],
|
|
3932
|
+
slots: [
|
|
3933
|
+
{ name: "prepend", title: "前缀", description: "输入框前缀内容" },
|
|
3934
|
+
{ name: "append", title: "后缀", description: "输入框后缀内容" }
|
|
3935
|
+
],
|
|
3936
|
+
nestingRules: { isContainer: !1 },
|
|
3937
|
+
snippets: [
|
|
3938
|
+
{ title: "基础输入框", props: { placeholder: "请输入" } }
|
|
3939
|
+
]
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
function In() {
|
|
3943
|
+
return {
|
|
3944
|
+
name: "a-textarea",
|
|
3945
|
+
title: "Arco 文本域",
|
|
3946
|
+
component: V(Te),
|
|
3947
|
+
category: "entry",
|
|
3948
|
+
group: "Arco 组件",
|
|
3949
|
+
order: 12,
|
|
3950
|
+
props: [
|
|
3951
|
+
{
|
|
3952
|
+
name: "modelValue",
|
|
3953
|
+
title: "值",
|
|
3954
|
+
type: "string",
|
|
3955
|
+
group: "基础属性"
|
|
3956
|
+
},
|
|
3957
|
+
{
|
|
3958
|
+
name: "placeholder",
|
|
3959
|
+
title: "占位文本",
|
|
3960
|
+
type: "string",
|
|
3961
|
+
defaultValue: "请输入",
|
|
3962
|
+
group: "基础属性"
|
|
3963
|
+
},
|
|
3964
|
+
{
|
|
3965
|
+
name: "disabled",
|
|
3966
|
+
title: "禁用",
|
|
3967
|
+
type: "boolean",
|
|
3968
|
+
defaultValue: !1,
|
|
3969
|
+
group: "基础属性"
|
|
3970
|
+
},
|
|
3971
|
+
{
|
|
3972
|
+
name: "autoSize",
|
|
3973
|
+
title: "自适应高度",
|
|
3974
|
+
type: "boolean",
|
|
3975
|
+
defaultValue: !1,
|
|
3976
|
+
group: "基础属性"
|
|
3977
|
+
},
|
|
3978
|
+
{
|
|
3979
|
+
name: "maxLength",
|
|
3980
|
+
title: "最大长度",
|
|
3981
|
+
type: "number",
|
|
3982
|
+
group: "基础属性"
|
|
3983
|
+
},
|
|
3984
|
+
{
|
|
3985
|
+
name: "showWordLimit",
|
|
3986
|
+
title: "显示字数",
|
|
3987
|
+
type: "boolean",
|
|
3988
|
+
defaultValue: !1,
|
|
3989
|
+
group: "基础属性"
|
|
3990
|
+
}
|
|
3991
|
+
],
|
|
3992
|
+
events: [
|
|
3993
|
+
{ name: "input", title: "输入", params: [
|
|
3994
|
+
{ name: "value", type: "string", description: "输入值" }
|
|
3995
|
+
] },
|
|
3996
|
+
{ name: "change", title: "变化", params: [
|
|
3997
|
+
{ name: "value", type: "string", description: "当前值" }
|
|
3998
|
+
] }
|
|
3999
|
+
],
|
|
4000
|
+
slots: [],
|
|
4001
|
+
nestingRules: { isContainer: !1 },
|
|
4002
|
+
snippets: [
|
|
4003
|
+
{ title: "基础文本域", props: { placeholder: "请输入", autoSize: !0 } }
|
|
4004
|
+
]
|
|
4005
|
+
};
|
|
4006
|
+
}
|
|
4007
|
+
function Bn() {
|
|
4008
|
+
return [
|
|
4009
|
+
// Sunny 表单组件
|
|
4010
|
+
Cn(),
|
|
4011
|
+
Sn(),
|
|
4012
|
+
// Arco Design 组件
|
|
4013
|
+
Nn(),
|
|
4014
|
+
_n(),
|
|
4015
|
+
In(),
|
|
4016
|
+
// 原生 HTML 元素
|
|
4017
|
+
...wn()
|
|
4018
|
+
];
|
|
4019
|
+
}
|
|
4020
|
+
const En = /* @__PURE__ */ _({
|
|
4021
|
+
name: "DesignPage",
|
|
4022
|
+
__name: "Design",
|
|
4023
|
+
setup(s) {
|
|
4024
|
+
const e = new Ut();
|
|
4025
|
+
e.registerAll(bn());
|
|
4026
|
+
const t = new Ye();
|
|
4027
|
+
t.registerAll(Bn());
|
|
4028
|
+
const n = A(null);
|
|
4029
|
+
return se(() => {
|
|
4030
|
+
const i = new Ze({ materialStore: t });
|
|
4031
|
+
n.value = i;
|
|
4032
|
+
const r = {
|
|
4033
|
+
id: "project-1",
|
|
4034
|
+
name: "示例项目",
|
|
4035
|
+
activePageId: "page-1",
|
|
4036
|
+
dependencies: [],
|
|
4037
|
+
apis: [],
|
|
4038
|
+
pages: [
|
|
4039
|
+
{
|
|
4040
|
+
id: "page-1",
|
|
4041
|
+
name: "首页",
|
|
4042
|
+
route: "/home",
|
|
4043
|
+
rootNode: {
|
|
4044
|
+
id: "root-1",
|
|
4045
|
+
name: "div",
|
|
4046
|
+
props: {},
|
|
4047
|
+
events: {},
|
|
4048
|
+
directives: [],
|
|
4049
|
+
children: [],
|
|
4050
|
+
slots: {}
|
|
4051
|
+
},
|
|
4052
|
+
state: [],
|
|
4053
|
+
computed: [],
|
|
4054
|
+
methods: [],
|
|
4055
|
+
watch: [],
|
|
4056
|
+
css: [],
|
|
4057
|
+
props: [],
|
|
4058
|
+
emits: [],
|
|
4059
|
+
expose: [],
|
|
4060
|
+
slots: [],
|
|
4061
|
+
lifecycleHooks: [],
|
|
4062
|
+
inject: []
|
|
4063
|
+
}
|
|
4064
|
+
]
|
|
4065
|
+
};
|
|
4066
|
+
i.loadProject(r);
|
|
4067
|
+
}), pe(() => {
|
|
4068
|
+
n.value?.destroy(), n.value = null;
|
|
4069
|
+
}), (i, r) => n.value ? (a(), w(H(Wt), {
|
|
4070
|
+
key: 0,
|
|
4071
|
+
title: "Sunny Designer",
|
|
4072
|
+
"widget-registry": H(e),
|
|
4073
|
+
engine: n.value
|
|
4074
|
+
}, null, 8, ["widget-registry", "engine"])) : S("", !0);
|
|
4075
|
+
}
|
|
4076
|
+
});
|
|
4077
|
+
export {
|
|
4078
|
+
En as default
|
|
4079
|
+
};
|