@arronqzy/vue-view 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/package.json +49 -0
- package/src/env.d.ts +62 -0
- package/src/index.ts +4 -0
- package/src/panel/VueViewOnlinePreview.vue +276 -0
- package/src/panel/VueViewPanel.vue +871 -0
- package/src/panel/components/ConfigHintIcon.vue +34 -0
- package/src/panel/components/ElementsLayer.vue +165 -0
- package/src/panel/components/MaterialPreview.vue +135 -0
- package/src/panel/components/MaterialSidebar.vue +526 -0
- package/src/panel/components/MaterialSidebarTreeNode.vue +305 -0
- package/src/panel/components/MoveableLayer.vue +859 -0
- package/src/panel/components/PanelCanvas.vue +630 -0
- package/src/panel/components/PanelConfigSidebar.vue +397 -0
- package/src/panel/components/PanelRulers.vue +177 -0
- package/src/panel/components/SelectLayer.vue +115 -0
- package/src/panel/components/ViewElementScopePanel.vue +76 -0
- package/src/panel/components/WorkspaceConfigSidebar.vue +147 -0
- package/src/panel/components/WorkspaceProjectNav.vue +192 -0
- package/src/panel/components/WorkspaceStageSplit.vue +258 -0
- package/src/panel/components/config/ConfigColorField.vue +52 -0
- package/src/panel/components/config/ConfigFieldGroup.vue +20 -0
- package/src/panel/components/config/ConfigSection.vue +50 -0
- package/src/panel/components/config/PanelConfigAudioSection.vue +256 -0
- package/src/panel/components/config/PanelConfigChartSection.vue +650 -0
- package/src/panel/components/config/PanelConfigGeometrySection.vue +209 -0
- package/src/panel/components/config/PanelConfigGridChildSpan.vue +68 -0
- package/src/panel/components/config/PanelConfigGridSection.vue +103 -0
- package/src/panel/components/config/PanelConfigImageSection.vue +136 -0
- package/src/panel/components/config/PanelConfigMultiSelect.vue +434 -0
- package/src/panel/components/config/PanelConfigNodeInfo.vue +165 -0
- package/src/panel/components/config/PanelConfigReferenceSection.vue +77 -0
- package/src/panel/components/config/PanelConfigStyleSections.vue +208 -0
- package/src/panel/components/config/PanelConfigTextSection.vue +195 -0
- package/src/panel/components/config/PanelConfigVideoSection.vue +107 -0
- package/src/panel/components/config/shared.ts +74 -0
- package/src/panel/components/elementsLayerNodes.ts +830 -0
- package/src/panel/components/materialSidebarData.ts +85 -0
- package/src/panel/components/scope-config/ScopeConfigProvider.vue +153 -0
- package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.vue +234 -0
- package/src/panel/components/scope-config/ScopeTemplatePreviewHost.vue +192 -0
- package/src/panel/components/scope-config/ScopeTemplatePreviewPanel.vue +42 -0
- package/src/panel/components/scope-config/ScopeTemplateUsageHint.vue +20 -0
- package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.vue +63 -0
- package/src/panel/components/scope-config/scopeConfigContext.ts +17 -0
- package/src/panel/components/scope-config/useScopeConfig.ts +11 -0
- package/src/panel/constants/messages.ts +34 -0
- package/src/panel/constants/zIndex.ts +6 -0
- package/src/panel/hooks/usePanelElements.ts +1075 -0
- package/src/panel/hooks/useRafThrottledScroll.ts +25 -0
- package/src/panel/hooks/useWorkspaceProjects.ts +240 -0
- package/src/panel/lib/panel-ruler-canvas.ts +139 -0
- package/src/panel/library/workspace-project-cache.ts +23 -0
- package/src/panel/library/workspace-project-db.ts +111 -0
- package/src/panel/library/workspace-project-sync.ts +41 -0
- package/src/panel/library/workspace-snapshot.ts +30 -0
- package/src/panel/parseOnlinePreviewSearchParams.ts +13 -0
- package/src/panel/scope/view-scope-store.ts +82 -0
- package/src/panel/types.ts +127 -0
- package/src/panel/utils/chartOptionBuilder.ts +327 -0
- package/src/panel/utils/gridPlacement.ts +189 -0
- package/src/panel/utils/mappingLayerOps.ts +142 -0
- package/src/panel/utils/panelElementDefaults.ts +161 -0
- package/src/panel/utils/panelElementNodes.ts +35 -0
- package/src/panel/utils/panelStateIO.ts +124 -0
- package/src/panel/utils/scope-autocomplete.ts +114 -0
- package/src/panel/utils/scope-field-labels.ts +46 -0
- package/src/panel/utils/scope-template-chart.ts +92 -0
- package/src/panel/utils/scope-template-preview.ts +124 -0
- package/src/panel/utils/scope-template-spread.ts +229 -0
- package/src/panel/utils/scope-template-warnings.ts +243 -0
- package/src/panel/utils/scope-template.ts +97 -0
- package/src/panel/utils/updateElementDraft.ts +221 -0
- package/src/panel/viewportZoom.ts +26 -0
- package/src/tailwind.css +43 -0
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
defineComponent,
|
|
4
|
+
h,
|
|
5
|
+
onMounted,
|
|
6
|
+
onUnmounted,
|
|
7
|
+
ref,
|
|
8
|
+
watch,
|
|
9
|
+
type PropType,
|
|
10
|
+
type VNode,
|
|
11
|
+
type Component,
|
|
12
|
+
} from "vue";
|
|
13
|
+
import * as echarts from "echarts";
|
|
14
|
+
import type { PanelElement } from "../types";
|
|
15
|
+
import { buildChartOption, CHART_TYPES } from "../utils/chartOptionBuilder";
|
|
16
|
+
import { PREVIEW_LAYOUT_EVENT } from "../utils/panelStateIO";
|
|
17
|
+
|
|
18
|
+
export { CHART_TYPES };
|
|
19
|
+
|
|
20
|
+
export function hasBackgroundImage(element: PanelElement) {
|
|
21
|
+
const style = element.style ?? {};
|
|
22
|
+
return Boolean(style.backgroundImage || style.backgroundImageRemoteUrl);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getNodeVisualStyle(element: PanelElement) {
|
|
26
|
+
const style = element.style ?? {};
|
|
27
|
+
return {
|
|
28
|
+
backgroundColor: style.backgroundColor,
|
|
29
|
+
backgroundImage: style.backgroundImage,
|
|
30
|
+
backgroundSize: style.backgroundSize,
|
|
31
|
+
backgroundPosition: style.backgroundPosition,
|
|
32
|
+
borderWidth: style.borderWidth,
|
|
33
|
+
borderStyle: style.borderStyle,
|
|
34
|
+
borderColor: style.borderColor,
|
|
35
|
+
borderRadius: style.borderRadius,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getNodeAABB(element: PanelElement) {
|
|
40
|
+
const rotate = ((element.rotate ?? 0) * Math.PI) / 180;
|
|
41
|
+
const cos = Math.cos(rotate);
|
|
42
|
+
const sin = Math.sin(rotate);
|
|
43
|
+
const absCos = Math.abs(cos);
|
|
44
|
+
const absSin = Math.abs(sin);
|
|
45
|
+
const bboxWidth = element.width * absCos + element.height * absSin;
|
|
46
|
+
const bboxHeight = element.width * absSin + element.height * absCos;
|
|
47
|
+
const cx = element.x + element.width / 2;
|
|
48
|
+
const cy = element.y + element.height / 2;
|
|
49
|
+
return {
|
|
50
|
+
left: cx - bboxWidth / 2,
|
|
51
|
+
top: cy - bboxHeight / 2,
|
|
52
|
+
right: cx + bboxWidth / 2,
|
|
53
|
+
bottom: cy + bboxHeight / 2,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const TextNodeContent = defineComponent({
|
|
58
|
+
name: "TextNodeContent",
|
|
59
|
+
props: {
|
|
60
|
+
element: { type: Object as PropType<PanelElement>, required: true },
|
|
61
|
+
editable: { type: Boolean, default: false },
|
|
62
|
+
},
|
|
63
|
+
emits: ["change"],
|
|
64
|
+
setup(p, { emit }) {
|
|
65
|
+
const nodeRef = ref<HTMLDivElement | null>(null);
|
|
66
|
+
const html = computed(() => p.element.textHtml ?? "<p>双击输入文本</p>");
|
|
67
|
+
const textStyle = computed(() => ({
|
|
68
|
+
fontFamily: p.element.textFontFamily || undefined,
|
|
69
|
+
fontSize: p.element.textFontSize ? `${p.element.textFontSize}px` : undefined,
|
|
70
|
+
fontWeight: p.element.textFontWeight || undefined,
|
|
71
|
+
color: p.element.textColor || undefined,
|
|
72
|
+
lineHeight: p.element.textLineHeight ? String(p.element.textLineHeight) : undefined,
|
|
73
|
+
textAlign: p.element.textAlign ?? "left",
|
|
74
|
+
}));
|
|
75
|
+
watch(
|
|
76
|
+
html,
|
|
77
|
+
(next) => {
|
|
78
|
+
const el = nodeRef.value;
|
|
79
|
+
if (!el || el.innerHTML === next) return;
|
|
80
|
+
el.innerHTML = next;
|
|
81
|
+
},
|
|
82
|
+
{ immediate: true }
|
|
83
|
+
);
|
|
84
|
+
return () =>
|
|
85
|
+
h("div", {
|
|
86
|
+
ref: nodeRef,
|
|
87
|
+
class: "h-full w-full overflow-auto break-words p-2 text-sm leading-relaxed outline-none",
|
|
88
|
+
style: textStyle.value,
|
|
89
|
+
contentEditable: p.editable,
|
|
90
|
+
onInput: (e: Event) => emit("change", (e.currentTarget as HTMLDivElement).innerHTML),
|
|
91
|
+
onBlur: (e: Event) => emit("change", (e.currentTarget as HTMLDivElement).innerHTML),
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export const GridNodeContent = defineComponent({
|
|
97
|
+
name: "GridNodeContent",
|
|
98
|
+
props: {
|
|
99
|
+
element: { type: Object as PropType<PanelElement>, required: true },
|
|
100
|
+
allElements: { type: Array as PropType<PanelElement[]>, required: true },
|
|
101
|
+
previewMode: { type: Boolean, default: false },
|
|
102
|
+
},
|
|
103
|
+
setup(p) {
|
|
104
|
+
const rows = computed(() => Math.max(1, Math.floor(p.element.gridRows ?? 2)));
|
|
105
|
+
const cols = computed(() => Math.max(1, Math.floor(p.element.gridCols ?? 3)));
|
|
106
|
+
const gap = computed(() => Math.max(0, p.element.gridGap ?? 8));
|
|
107
|
+
const padding = computed(() => Math.max(0, p.element.gridPadding ?? 10));
|
|
108
|
+
const occupied = computed(() => {
|
|
109
|
+
const set = new Set<number>();
|
|
110
|
+
p.allElements.forEach((el) => {
|
|
111
|
+
if (el.parentGridId !== p.element.id || el.layerId !== p.element.layerId) return;
|
|
112
|
+
if (el.gridSlotIndex === undefined) return;
|
|
113
|
+
const start = Math.max(0, Math.floor(el.gridSlotIndex));
|
|
114
|
+
const baseRow = Math.floor(start / cols.value);
|
|
115
|
+
const baseCol = start % cols.value;
|
|
116
|
+
const rowSpan = Math.max(1, Math.min(rows.value - baseRow, Math.floor(el.gridRowSpan ?? 1)));
|
|
117
|
+
const colSpan = Math.max(1, Math.min(cols.value - baseCol, Math.floor(el.gridColSpan ?? 1)));
|
|
118
|
+
for (let r = baseRow; r < baseRow + rowSpan; r++) {
|
|
119
|
+
for (let c = baseCol; c < baseCol + colSpan; c++) set.add(r * cols.value + c);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return set;
|
|
123
|
+
});
|
|
124
|
+
const occupiedBlocks = computed(() =>
|
|
125
|
+
p.allElements
|
|
126
|
+
.filter(
|
|
127
|
+
(el) =>
|
|
128
|
+
el.parentGridId === p.element.id &&
|
|
129
|
+
el.layerId === p.element.layerId &&
|
|
130
|
+
el.gridSlotIndex !== undefined
|
|
131
|
+
)
|
|
132
|
+
.map((el) => {
|
|
133
|
+
const start = Math.max(0, Math.floor(el.gridSlotIndex ?? 0));
|
|
134
|
+
const baseRow = Math.floor(start / cols.value);
|
|
135
|
+
const baseCol = start % cols.value;
|
|
136
|
+
const rowSpan = Math.max(1, Math.min(rows.value - baseRow, Math.floor(el.gridRowSpan ?? 1)));
|
|
137
|
+
const colSpan = Math.max(1, Math.min(cols.value - baseCol, Math.floor(el.gridColSpan ?? 1)));
|
|
138
|
+
return {
|
|
139
|
+
id: el.id,
|
|
140
|
+
rowStart: baseRow + 1,
|
|
141
|
+
rowEnd: baseRow + rowSpan + 1,
|
|
142
|
+
colStart: baseCol + 1,
|
|
143
|
+
colEnd: baseCol + colSpan + 1,
|
|
144
|
+
};
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
return () => {
|
|
148
|
+
if (p.previewMode) return h("div", { class: "relative h-full w-full" });
|
|
149
|
+
const gridStyle = {
|
|
150
|
+
display: "grid",
|
|
151
|
+
gridTemplateColumns: `repeat(${cols.value}, minmax(0, 1fr))`,
|
|
152
|
+
gridTemplateRows: `repeat(${rows.value}, minmax(0, 1fr))`,
|
|
153
|
+
gap: `${gap.value}px`,
|
|
154
|
+
padding: `${padding.value}px`,
|
|
155
|
+
boxSizing: "border-box" as const,
|
|
156
|
+
};
|
|
157
|
+
return h("div", { class: "relative h-full w-full" }, [
|
|
158
|
+
h(
|
|
159
|
+
"div",
|
|
160
|
+
{ class: "h-full w-full", style: gridStyle },
|
|
161
|
+
Array.from({ length: rows.value * cols.value }).map((_, idx) =>
|
|
162
|
+
h("div", {
|
|
163
|
+
key: idx,
|
|
164
|
+
class: [
|
|
165
|
+
"rounded border border-dashed",
|
|
166
|
+
occupied.value.has(idx)
|
|
167
|
+
? "border-primary/70 bg-primary/10"
|
|
168
|
+
: "border-border/60 bg-muted/20",
|
|
169
|
+
].join(" "),
|
|
170
|
+
title: `槽位 ${idx + 1}${occupied.value.has(idx) ? "(已占用)" : "(空)"}`,
|
|
171
|
+
})
|
|
172
|
+
)
|
|
173
|
+
),
|
|
174
|
+
h(
|
|
175
|
+
"div",
|
|
176
|
+
{ class: "pointer-events-none absolute inset-0", style: gridStyle },
|
|
177
|
+
occupiedBlocks.value.map((block) =>
|
|
178
|
+
h("div", {
|
|
179
|
+
key: block.id,
|
|
180
|
+
class: "rounded border border-primary/70 bg-primary/20",
|
|
181
|
+
style: {
|
|
182
|
+
gridColumn: `${block.colStart} / ${block.colEnd}`,
|
|
183
|
+
gridRow: `${block.rowStart} / ${block.rowEnd}`,
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
)
|
|
187
|
+
),
|
|
188
|
+
]);
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
export const ChartNodeContent = defineComponent({
|
|
194
|
+
name: "ChartNodeContent",
|
|
195
|
+
props: {
|
|
196
|
+
element: { type: Object as PropType<PanelElement>, required: true },
|
|
197
|
+
previewLayoutKey: { type: Number, default: undefined },
|
|
198
|
+
previewMode: { type: Boolean, default: false },
|
|
199
|
+
},
|
|
200
|
+
setup(p) {
|
|
201
|
+
const hostRef = ref<HTMLDivElement | null>(null);
|
|
202
|
+
const chartRef = ref<echarts.ECharts | null>(null);
|
|
203
|
+
const rendererRef = ref<"canvas" | "svg">("canvas");
|
|
204
|
+
const option = computed(() => buildChartOption(p.element));
|
|
205
|
+
const renderer = computed(() => (p.element.chart?.renderMode ?? "canvas") as "canvas" | "svg");
|
|
206
|
+
|
|
207
|
+
function resizeChart() {
|
|
208
|
+
chartRef.value?.resize();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
watch(
|
|
212
|
+
[() => p.element.width, () => p.element.height, option, () => p.previewLayoutKey, renderer],
|
|
213
|
+
() => {
|
|
214
|
+
const host = hostRef.value;
|
|
215
|
+
if (!host) return;
|
|
216
|
+
if (!chartRef.value || rendererRef.value !== renderer.value) {
|
|
217
|
+
chartRef.value?.dispose();
|
|
218
|
+
chartRef.value = echarts.init(host, undefined, { renderer: renderer.value });
|
|
219
|
+
rendererRef.value = renderer.value;
|
|
220
|
+
}
|
|
221
|
+
chartRef.value.setOption(option.value as echarts.EChartsOption, true);
|
|
222
|
+
resizeChart();
|
|
223
|
+
},
|
|
224
|
+
{ immediate: true, deep: true }
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
let cleanup: (() => void) | null = null;
|
|
228
|
+
onMounted(() => {
|
|
229
|
+
const host = hostRef.value;
|
|
230
|
+
if (!host) return;
|
|
231
|
+
const onLayout = () => resizeChart();
|
|
232
|
+
window.addEventListener(PREVIEW_LAYOUT_EVENT, onLayout);
|
|
233
|
+
const obs = new ResizeObserver(onLayout);
|
|
234
|
+
obs.observe(host);
|
|
235
|
+
cleanup = () => {
|
|
236
|
+
window.removeEventListener(PREVIEW_LAYOUT_EVENT, onLayout);
|
|
237
|
+
obs.disconnect();
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
onUnmounted(() => {
|
|
241
|
+
cleanup?.();
|
|
242
|
+
chartRef.value?.dispose();
|
|
243
|
+
chartRef.value = null;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
return () =>
|
|
247
|
+
h("div", {
|
|
248
|
+
ref: hostRef,
|
|
249
|
+
class: "h-full w-full",
|
|
250
|
+
style: p.previewMode
|
|
251
|
+
? {
|
|
252
|
+
width: Math.max(1, p.element.width),
|
|
253
|
+
height: Math.max(1, p.element.height),
|
|
254
|
+
minWidth: 1,
|
|
255
|
+
minHeight: 1,
|
|
256
|
+
}
|
|
257
|
+
: undefined,
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
export const AudioNodeContent = defineComponent({
|
|
263
|
+
name: "AudioNodeContent",
|
|
264
|
+
props: {
|
|
265
|
+
element: { type: Object as PropType<PanelElement>, required: true },
|
|
266
|
+
selected: { type: Boolean, default: false },
|
|
267
|
+
},
|
|
268
|
+
setup(p) {
|
|
269
|
+
const audioRef = ref<HTMLAudioElement | null>(null);
|
|
270
|
+
const playing = ref(false);
|
|
271
|
+
const src = computed(() => p.element.audioSrc || p.element.audioRemoteUrl || "");
|
|
272
|
+
const poster = computed(() => p.element.audioPosterImage);
|
|
273
|
+
const iconPreset = computed(() => p.element.audioIconPreset);
|
|
274
|
+
const iconMode = computed(() => Boolean(poster.value || iconPreset.value));
|
|
275
|
+
const visualEffect = computed(() => p.element.audioVisualEffect ?? "pulse");
|
|
276
|
+
const visualSpeed = computed(() => p.element.audioVisualSpeed ?? "normal");
|
|
277
|
+
const speedMs = computed(() =>
|
|
278
|
+
visualSpeed.value === "fast" ? 900 : visualSpeed.value === "slow" ? 1800 : 1300
|
|
279
|
+
);
|
|
280
|
+
const shouldAnimate = computed(
|
|
281
|
+
() => Boolean(src.value) && playing.value && visualEffect.value !== "none"
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
onMounted(() => {
|
|
285
|
+
const el = audioRef.value;
|
|
286
|
+
if (!el) return;
|
|
287
|
+
const onPlay = () => (playing.value = true);
|
|
288
|
+
const onPause = () => (playing.value = false);
|
|
289
|
+
const onEnded = () => (playing.value = false);
|
|
290
|
+
el.addEventListener("play", onPlay);
|
|
291
|
+
el.addEventListener("pause", onPause);
|
|
292
|
+
el.addEventListener("ended", onEnded);
|
|
293
|
+
onUnmounted(() => {
|
|
294
|
+
el.removeEventListener("play", onPlay);
|
|
295
|
+
el.removeEventListener("pause", onPause);
|
|
296
|
+
el.removeEventListener("ended", onEnded);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
watch(
|
|
301
|
+
() => [p.selected, p.element.mediaAutoPauseOnEdit] as const,
|
|
302
|
+
() => {
|
|
303
|
+
if (!p.selected || p.element.mediaAutoPauseOnEdit === false) return;
|
|
304
|
+
const el = audioRef.value;
|
|
305
|
+
if (el && !el.paused) el.pause();
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
function togglePlay(e: Event) {
|
|
310
|
+
e.stopPropagation();
|
|
311
|
+
if (!src.value) return;
|
|
312
|
+
const el = audioRef.value;
|
|
313
|
+
if (!el) return;
|
|
314
|
+
if (el.paused) void el.play();
|
|
315
|
+
else el.pause();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function renderIcon() {
|
|
319
|
+
const common = "h-8 w-8 text-foreground/90";
|
|
320
|
+
const preset = iconPreset.value;
|
|
321
|
+
if (preset === "music") {
|
|
322
|
+
return h("svg", { viewBox: "0 0 24 24", class: common, fill: "none", stroke: "currentColor", "stroke-width": "1.8" }, [
|
|
323
|
+
h("path", { d: "M9 18a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Zm11-2a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z" }),
|
|
324
|
+
h("path", { d: "M9 18V7l11-2v11" }),
|
|
325
|
+
]);
|
|
326
|
+
}
|
|
327
|
+
if (preset === "headphone") {
|
|
328
|
+
return h("svg", { viewBox: "0 0 24 24", class: common, fill: "none", stroke: "currentColor", "stroke-width": "1.8" }, [
|
|
329
|
+
h("path", { d: "M4 12a8 8 0 1 1 16 0" }),
|
|
330
|
+
h("rect", { x: "3", y: "12", width: "4", height: "7", rx: "2" }),
|
|
331
|
+
h("rect", { x: "17", y: "12", width: "4", height: "7", rx: "2" }),
|
|
332
|
+
]);
|
|
333
|
+
}
|
|
334
|
+
if (preset === "wave") {
|
|
335
|
+
return h("svg", { viewBox: "0 0 24 24", class: common, fill: "none", stroke: "currentColor", "stroke-width": "1.8" }, [
|
|
336
|
+
h("path", { d: "M4 14v-4M8 17V7M12 20V4M16 17V7M20 14v-4" }),
|
|
337
|
+
]);
|
|
338
|
+
}
|
|
339
|
+
return h("svg", { viewBox: "0 0 24 24", class: common, fill: "none", stroke: "currentColor", "stroke-width": "1.8" }, [
|
|
340
|
+
h("path", { d: "M11 5 6 9H3v6h3l5 4V5Z" }),
|
|
341
|
+
h("path", { d: "M15 9a4 4 0 0 1 0 6" }),
|
|
342
|
+
h("path", { d: "M17.5 6.5a7 7 0 0 1 0 11" }),
|
|
343
|
+
]);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return () => {
|
|
347
|
+
if (iconMode.value) {
|
|
348
|
+
return h(
|
|
349
|
+
"div",
|
|
350
|
+
{
|
|
351
|
+
class: [
|
|
352
|
+
"relative flex h-full w-full items-center justify-center overflow-hidden rounded border border-border/60 bg-muted/10",
|
|
353
|
+
shouldAnimate.value && visualEffect.value === "pulse"
|
|
354
|
+
? "animate-pulse ring-2 ring-primary/50"
|
|
355
|
+
: "",
|
|
356
|
+
].join(" "),
|
|
357
|
+
style:
|
|
358
|
+
shouldAnimate.value && visualEffect.value === "pulse"
|
|
359
|
+
? { animationDuration: `${speedMs.value}ms` }
|
|
360
|
+
: undefined,
|
|
361
|
+
},
|
|
362
|
+
[
|
|
363
|
+
poster.value
|
|
364
|
+
? h(
|
|
365
|
+
"button",
|
|
366
|
+
{
|
|
367
|
+
type: "button",
|
|
368
|
+
class: "h-full w-full",
|
|
369
|
+
onMousedown: (e: Event) => e.stopPropagation(),
|
|
370
|
+
onPointerdown: (e: Event) => e.stopPropagation(),
|
|
371
|
+
onClick: togglePlay,
|
|
372
|
+
title: !src.value ? "请先配置音频源" : playing.value ? "点击暂停" : "点击播放",
|
|
373
|
+
},
|
|
374
|
+
[
|
|
375
|
+
h("img", {
|
|
376
|
+
src: poster.value,
|
|
377
|
+
alt: "音频占位图",
|
|
378
|
+
class: "h-full w-full object-cover",
|
|
379
|
+
draggable: false,
|
|
380
|
+
onDragstart: (e: Event) => e.preventDefault(),
|
|
381
|
+
}),
|
|
382
|
+
]
|
|
383
|
+
)
|
|
384
|
+
: h(
|
|
385
|
+
"button",
|
|
386
|
+
{
|
|
387
|
+
type: "button",
|
|
388
|
+
class: "flex h-full w-full items-center justify-center",
|
|
389
|
+
onMousedown: (e: Event) => e.stopPropagation(),
|
|
390
|
+
onPointerdown: (e: Event) => e.stopPropagation(),
|
|
391
|
+
onClick: togglePlay,
|
|
392
|
+
title: !src.value ? "请先配置音频源" : playing.value ? "点击暂停" : "点击播放",
|
|
393
|
+
},
|
|
394
|
+
[renderIcon()]
|
|
395
|
+
),
|
|
396
|
+
shouldAnimate.value && visualEffect.value === "ripple"
|
|
397
|
+
? [
|
|
398
|
+
h("span", {
|
|
399
|
+
class:
|
|
400
|
+
"pointer-events-none absolute h-16 w-16 rounded-full border border-primary/70 animate-ping",
|
|
401
|
+
style: { animationDuration: `${speedMs.value}ms` },
|
|
402
|
+
}),
|
|
403
|
+
h("span", {
|
|
404
|
+
class:
|
|
405
|
+
"pointer-events-none absolute h-24 w-24 rounded-full border border-primary/40 animate-ping",
|
|
406
|
+
style: {
|
|
407
|
+
animationDuration: `${speedMs.value}ms`,
|
|
408
|
+
animationDelay: `${Math.round(speedMs.value / 3)}ms`,
|
|
409
|
+
},
|
|
410
|
+
}),
|
|
411
|
+
]
|
|
412
|
+
: null,
|
|
413
|
+
h(
|
|
414
|
+
"span",
|
|
415
|
+
{
|
|
416
|
+
class:
|
|
417
|
+
"pointer-events-none absolute right-2 top-2 rounded bg-black/55 px-1.5 py-0.5 text-[10px] text-white",
|
|
418
|
+
},
|
|
419
|
+
!src.value ? "未配置音频" : playing.value ? "暂停" : "播放"
|
|
420
|
+
),
|
|
421
|
+
h("audio", { ref: audioRef, src: src.value, preload: "metadata", class: "hidden" }),
|
|
422
|
+
]
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
if (!src.value) {
|
|
426
|
+
return h(
|
|
427
|
+
"div",
|
|
428
|
+
{
|
|
429
|
+
class:
|
|
430
|
+
"flex h-full w-full items-center justify-center rounded border border-dashed border-border/70 bg-muted/15 px-2 text-[11px] text-muted-foreground",
|
|
431
|
+
},
|
|
432
|
+
"音频占位"
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
return h("div", { class: "relative flex h-full w-full items-center justify-center rounded border border-border/60 bg-muted/10 px-2" }, [
|
|
436
|
+
h("div", { class: "pointer-events-none text-[11px] text-muted-foreground" }, playing.value ? "音频播放中" : "音频已就绪"),
|
|
437
|
+
h(
|
|
438
|
+
"button",
|
|
439
|
+
{
|
|
440
|
+
type: "button",
|
|
441
|
+
class: "absolute right-2 top-2 rounded bg-black/55 px-1.5 py-0.5 text-[10px] text-white",
|
|
442
|
+
onMousedown: (e: Event) => e.stopPropagation(),
|
|
443
|
+
onPointerdown: (e: Event) => e.stopPropagation(),
|
|
444
|
+
onClick: togglePlay,
|
|
445
|
+
title: playing.value ? "点击暂停" : "点击播放",
|
|
446
|
+
},
|
|
447
|
+
playing.value ? "暂停" : "播放"
|
|
448
|
+
),
|
|
449
|
+
h("audio", { ref: audioRef, src: src.value, preload: "metadata", class: "hidden" }),
|
|
450
|
+
]);
|
|
451
|
+
};
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
export const VideoNodeContent = defineComponent({
|
|
456
|
+
name: "VideoNodeContent",
|
|
457
|
+
props: {
|
|
458
|
+
element: { type: Object as PropType<PanelElement>, required: true },
|
|
459
|
+
selected: { type: Boolean, default: false },
|
|
460
|
+
},
|
|
461
|
+
setup(p) {
|
|
462
|
+
const videoRef = ref<HTMLVideoElement | null>(null);
|
|
463
|
+
const playing = ref(false);
|
|
464
|
+
const src = computed(() => p.element.videoSrc || p.element.videoRemoteUrl || "");
|
|
465
|
+
|
|
466
|
+
onMounted(() => {
|
|
467
|
+
const el = videoRef.value;
|
|
468
|
+
if (!el) return;
|
|
469
|
+
const onPlay = () => (playing.value = true);
|
|
470
|
+
const onPause = () => (playing.value = false);
|
|
471
|
+
const onEnded = () => (playing.value = false);
|
|
472
|
+
el.addEventListener("play", onPlay);
|
|
473
|
+
el.addEventListener("pause", onPause);
|
|
474
|
+
el.addEventListener("ended", onEnded);
|
|
475
|
+
onUnmounted(() => {
|
|
476
|
+
el.removeEventListener("play", onPlay);
|
|
477
|
+
el.removeEventListener("pause", onPause);
|
|
478
|
+
el.removeEventListener("ended", onEnded);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
watch(
|
|
483
|
+
() => [p.selected, p.element.mediaAutoPauseOnEdit] as const,
|
|
484
|
+
() => {
|
|
485
|
+
if (!p.selected || p.element.mediaAutoPauseOnEdit === false) return;
|
|
486
|
+
const el = videoRef.value;
|
|
487
|
+
if (el && !el.paused) el.pause();
|
|
488
|
+
}
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
function togglePlay(e: Event) {
|
|
492
|
+
e.stopPropagation();
|
|
493
|
+
const el = videoRef.value;
|
|
494
|
+
if (!el) return;
|
|
495
|
+
if (el.paused) void el.play();
|
|
496
|
+
else el.pause();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return () => {
|
|
500
|
+
if (!src.value) {
|
|
501
|
+
return h(
|
|
502
|
+
"div",
|
|
503
|
+
{
|
|
504
|
+
class:
|
|
505
|
+
"flex h-full w-full items-center justify-center rounded border border-dashed border-border/70 bg-muted/15 px-2 text-[11px] text-muted-foreground",
|
|
506
|
+
},
|
|
507
|
+
"视频占位"
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
return h("div", { class: "relative h-full w-full p-1" }, [
|
|
511
|
+
h("video", {
|
|
512
|
+
ref: videoRef,
|
|
513
|
+
src: src.value,
|
|
514
|
+
class: "h-full w-full rounded object-contain pointer-events-none",
|
|
515
|
+
}),
|
|
516
|
+
h(
|
|
517
|
+
"button",
|
|
518
|
+
{
|
|
519
|
+
type: "button",
|
|
520
|
+
class: "absolute right-2 top-2 rounded bg-black/55 px-1.5 py-0.5 text-[10px] text-white",
|
|
521
|
+
onMousedown: (e: Event) => e.stopPropagation(),
|
|
522
|
+
onPointerdown: (e: Event) => e.stopPropagation(),
|
|
523
|
+
onClick: togglePlay,
|
|
524
|
+
title: playing.value ? "点击暂停" : "点击播放",
|
|
525
|
+
},
|
|
526
|
+
playing.value ? "暂停" : "播放"
|
|
527
|
+
),
|
|
528
|
+
]);
|
|
529
|
+
};
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
export const GeometryNodeContent = defineComponent({
|
|
534
|
+
name: "GeometryNodeContent",
|
|
535
|
+
props: {
|
|
536
|
+
element: { type: Object as PropType<PanelElement>, required: true },
|
|
537
|
+
},
|
|
538
|
+
setup(p) {
|
|
539
|
+
const canvasRef = ref<HTMLCanvasElement | null>(null);
|
|
540
|
+
const elementRef = ref(p.element);
|
|
541
|
+
elementRef.value = p.element;
|
|
542
|
+
watch(
|
|
543
|
+
() => p.element,
|
|
544
|
+
(next) => {
|
|
545
|
+
elementRef.value = next;
|
|
546
|
+
}
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
const shape = computed(() => p.element.geometryShape ?? "rect");
|
|
550
|
+
const color = computed(() => p.element.geometryColor ?? "#3b82f6");
|
|
551
|
+
const script = computed(() => p.element.geometryScript ?? "");
|
|
552
|
+
const sketch = computed(() => p.element.geometrySketchDataUrl ?? "");
|
|
553
|
+
|
|
554
|
+
function drawCanvas() {
|
|
555
|
+
const canvas = canvasRef.value;
|
|
556
|
+
if (!canvas) return;
|
|
557
|
+
const ctx = canvas.getContext("2d");
|
|
558
|
+
if (!ctx) return;
|
|
559
|
+
|
|
560
|
+
const dpr = window.devicePixelRatio || 1;
|
|
561
|
+
const width = Math.max(1, canvas.clientWidth);
|
|
562
|
+
const height = Math.max(1, canvas.clientHeight);
|
|
563
|
+
canvas.width = Math.round(width * dpr);
|
|
564
|
+
canvas.height = Math.round(height * dpr);
|
|
565
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
566
|
+
ctx.clearRect(0, 0, width, height);
|
|
567
|
+
|
|
568
|
+
const drawShapePath = () => {
|
|
569
|
+
const cx = width / 2;
|
|
570
|
+
const cy = height / 2;
|
|
571
|
+
const r = Math.max(10, Math.min(width, height) * 0.42);
|
|
572
|
+
ctx.beginPath();
|
|
573
|
+
const s = shape.value;
|
|
574
|
+
if (s === "circle") {
|
|
575
|
+
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
576
|
+
} else if (s === "triangle") {
|
|
577
|
+
ctx.moveTo(cx, cy - r);
|
|
578
|
+
ctx.lineTo(cx + r * 0.9, cy + r * 0.8);
|
|
579
|
+
ctx.lineTo(cx - r * 0.9, cy + r * 0.8);
|
|
580
|
+
ctx.closePath();
|
|
581
|
+
} else if (s === "diamond") {
|
|
582
|
+
ctx.moveTo(cx, cy - r);
|
|
583
|
+
ctx.lineTo(cx + r, cy);
|
|
584
|
+
ctx.lineTo(cx, cy + r);
|
|
585
|
+
ctx.lineTo(cx - r, cy);
|
|
586
|
+
ctx.closePath();
|
|
587
|
+
} else if (s === "hexagon") {
|
|
588
|
+
for (let i = 0; i < 6; i += 1) {
|
|
589
|
+
const a = (Math.PI / 3) * i - Math.PI / 6;
|
|
590
|
+
const x = cx + r * Math.cos(a);
|
|
591
|
+
const y = cy + r * Math.sin(a);
|
|
592
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
593
|
+
else ctx.lineTo(x, y);
|
|
594
|
+
}
|
|
595
|
+
ctx.closePath();
|
|
596
|
+
} else if (s === "star") {
|
|
597
|
+
const inner = r * 0.45;
|
|
598
|
+
for (let i = 0; i < 10; i += 1) {
|
|
599
|
+
const rr = i % 2 === 0 ? r : inner;
|
|
600
|
+
const a = (Math.PI / 5) * i - Math.PI / 2;
|
|
601
|
+
const x = cx + rr * Math.cos(a);
|
|
602
|
+
const y = cy + rr * Math.sin(a);
|
|
603
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
604
|
+
else ctx.lineTo(x, y);
|
|
605
|
+
}
|
|
606
|
+
ctx.closePath();
|
|
607
|
+
} else if (s === "heart") {
|
|
608
|
+
const top = cy - r * 0.2;
|
|
609
|
+
ctx.moveTo(cx, cy + r * 0.9);
|
|
610
|
+
ctx.bezierCurveTo(cx - r * 1.2, cy + r * 0.25, cx - r * 0.9, top - r * 0.8, cx, top);
|
|
611
|
+
ctx.bezierCurveTo(cx + r * 0.9, top - r * 0.8, cx + r * 1.2, cy + r * 0.25, cx, cy + r * 0.9);
|
|
612
|
+
ctx.closePath();
|
|
613
|
+
} else {
|
|
614
|
+
const rr = Math.max(4, Math.min(width, height) * 0.08);
|
|
615
|
+
const x = width * 0.08;
|
|
616
|
+
const y = height * 0.08;
|
|
617
|
+
const w = width * 0.84;
|
|
618
|
+
const hh = height * 0.84;
|
|
619
|
+
ctx.moveTo(x + rr, y);
|
|
620
|
+
ctx.lineTo(x + w - rr, y);
|
|
621
|
+
ctx.quadraticCurveTo(x + w, y, x + w, y + rr);
|
|
622
|
+
ctx.lineTo(x + w, y + hh - rr);
|
|
623
|
+
ctx.quadraticCurveTo(x + w, y + hh, x + w - rr, y + hh);
|
|
624
|
+
ctx.lineTo(x + rr, y + hh);
|
|
625
|
+
ctx.quadraticCurveTo(x, y + hh, x, y + hh - rr);
|
|
626
|
+
ctx.lineTo(x, y + rr);
|
|
627
|
+
ctx.quadraticCurveTo(x, y, x + rr, y);
|
|
628
|
+
ctx.closePath();
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
drawShapePath();
|
|
633
|
+
ctx.fillStyle = color.value;
|
|
634
|
+
ctx.fill();
|
|
635
|
+
|
|
636
|
+
if (sketch.value) {
|
|
637
|
+
const img = new Image();
|
|
638
|
+
img.onload = () => {
|
|
639
|
+
ctx.save();
|
|
640
|
+
drawShapePath();
|
|
641
|
+
ctx.clip();
|
|
642
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
643
|
+
ctx.restore();
|
|
644
|
+
};
|
|
645
|
+
img.src = sketch.value;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (script.value.trim()) {
|
|
649
|
+
try {
|
|
650
|
+
const fn = new Function("ctx", "width", "height", "element", script.value);
|
|
651
|
+
fn(ctx, width, height, elementRef.value);
|
|
652
|
+
} catch {
|
|
653
|
+
// ignore invalid script
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
watch([canvasRef, shape, color, script, sketch], drawCanvas, { immediate: true, flush: "post" });
|
|
659
|
+
|
|
660
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
661
|
+
onMounted(() => {
|
|
662
|
+
const canvas = canvasRef.value;
|
|
663
|
+
if (!canvas) return;
|
|
664
|
+
resizeObserver = new ResizeObserver(drawCanvas);
|
|
665
|
+
resizeObserver.observe(canvas);
|
|
666
|
+
});
|
|
667
|
+
onUnmounted(() => resizeObserver?.disconnect());
|
|
668
|
+
|
|
669
|
+
return () => h("canvas", { ref: canvasRef, class: "h-full w-full" });
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
export const ReferenceNodeContent = defineComponent({
|
|
674
|
+
name: "ReferenceNodeContent",
|
|
675
|
+
props: {
|
|
676
|
+
element: { type: Object as PropType<PanelElement>, required: true },
|
|
677
|
+
allElements: { type: Array as PropType<PanelElement[]>, required: true },
|
|
678
|
+
snapshotSource: { type: Array as PropType<PanelElement[]>, default: undefined },
|
|
679
|
+
visitedIds: { type: Array as PropType<string[]>, default: () => [] },
|
|
680
|
+
previewLayoutKey: { type: Number, default: undefined },
|
|
681
|
+
previewMode: { type: Boolean, default: false },
|
|
682
|
+
},
|
|
683
|
+
setup(p) {
|
|
684
|
+
const sourceNodes = computed(() => {
|
|
685
|
+
const fromDeep =
|
|
686
|
+
p.element.refCopyMode === "deep"
|
|
687
|
+
? p.element.refSnapshot ?? p.snapshotSource ?? []
|
|
688
|
+
: null;
|
|
689
|
+
const base = fromDeep ?? p.allElements.filter((n) => n.layerId === p.element.refLayerId);
|
|
690
|
+
return base.filter((n) => n.id !== p.element.id && !p.visitedIds.includes(n.id));
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
const layout = computed(() => {
|
|
694
|
+
if (sourceNodes.value.length === 0) return null;
|
|
695
|
+
const boxes = sourceNodes.value.map(getNodeAABB);
|
|
696
|
+
const minX = Math.min(...boxes.map((b) => b.left));
|
|
697
|
+
const minY = Math.min(...boxes.map((b) => b.top));
|
|
698
|
+
const maxX = Math.max(...boxes.map((b) => b.right));
|
|
699
|
+
const maxY = Math.max(...boxes.map((b) => b.bottom));
|
|
700
|
+
const sourceWidth = Math.max(1, maxX - minX);
|
|
701
|
+
const sourceHeight = Math.max(1, maxY - minY);
|
|
702
|
+
const innerW = Math.max(1, p.element.width);
|
|
703
|
+
const innerH = Math.max(1, p.element.height);
|
|
704
|
+
const scale = Math.max(0.05, Math.min(innerW / sourceWidth, innerH / sourceHeight));
|
|
705
|
+
const mappedW = sourceWidth * scale;
|
|
706
|
+
const mappedH = sourceHeight * scale;
|
|
707
|
+
return {
|
|
708
|
+
minX,
|
|
709
|
+
minY,
|
|
710
|
+
scale,
|
|
711
|
+
offsetX: (innerW - mappedW) / 2,
|
|
712
|
+
offsetY: (innerH - mappedH) / 2,
|
|
713
|
+
};
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
return () => {
|
|
717
|
+
if (!layout.value || sourceNodes.value.length === 0) {
|
|
718
|
+
const hintText = p.element.refLayerId ? "引用图层暂无节点" : "请在右侧选择引用图层";
|
|
719
|
+
return h("div", { class: "flex h-full w-full items-center justify-center" }, [
|
|
720
|
+
h(
|
|
721
|
+
"div",
|
|
722
|
+
{
|
|
723
|
+
class:
|
|
724
|
+
"rounded border border-dashed border-border/70 px-2 py-1 text-[10px] text-muted-foreground",
|
|
725
|
+
},
|
|
726
|
+
hintText
|
|
727
|
+
),
|
|
728
|
+
]);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return h("div", { class: "pointer-events-none relative h-full w-full overflow-hidden" }, [
|
|
732
|
+
...sourceNodes.value.map((node) => {
|
|
733
|
+
const box = getNodeAABB(node);
|
|
734
|
+
const left = layout.value!.offsetX + (box.left - layout.value!.minX) * layout.value!.scale;
|
|
735
|
+
const top = layout.value!.offsetY + (box.top - layout.value!.minY) * layout.value!.scale;
|
|
736
|
+
const width = Math.max(12, node.width * layout.value!.scale);
|
|
737
|
+
const height = Math.max(10, node.height * layout.value!.scale);
|
|
738
|
+
const boxWidth = Math.max(12, (box.right - box.left) * layout.value!.scale);
|
|
739
|
+
const boxHeight = Math.max(10, (box.bottom - box.top) * layout.value!.scale);
|
|
740
|
+
return h(
|
|
741
|
+
"div",
|
|
742
|
+
{
|
|
743
|
+
key: node.id,
|
|
744
|
+
class: "absolute overflow-visible",
|
|
745
|
+
style: { left, top, width: boxWidth, height: boxHeight },
|
|
746
|
+
},
|
|
747
|
+
[
|
|
748
|
+
h(
|
|
749
|
+
"div",
|
|
750
|
+
{
|
|
751
|
+
class: "absolute",
|
|
752
|
+
style: {
|
|
753
|
+
left: (boxWidth - width) / 2,
|
|
754
|
+
top: (boxHeight - height) / 2,
|
|
755
|
+
width,
|
|
756
|
+
height,
|
|
757
|
+
transform: `rotate(${node.rotate ?? 0}deg)`,
|
|
758
|
+
transformOrigin: "center center",
|
|
759
|
+
...getNodeVisualStyle(node),
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
[
|
|
763
|
+
CHART_TYPES.has(node.materialType ?? "")
|
|
764
|
+
? h(ChartNodeContent, {
|
|
765
|
+
element: node,
|
|
766
|
+
previewLayoutKey: p.previewLayoutKey,
|
|
767
|
+
previewMode: p.previewMode,
|
|
768
|
+
})
|
|
769
|
+
: node.materialType === "reference"
|
|
770
|
+
? h(ReferenceNodeContent as Component, {
|
|
771
|
+
element: node,
|
|
772
|
+
allElements: p.allElements,
|
|
773
|
+
snapshotSource: node.refSnapshot,
|
|
774
|
+
visitedIds: [...p.visitedIds, p.element.id],
|
|
775
|
+
previewLayoutKey: p.previewLayoutKey,
|
|
776
|
+
previewMode: p.previewMode,
|
|
777
|
+
})
|
|
778
|
+
: node.materialType === "geometry"
|
|
779
|
+
? h(GeometryNodeContent, { element: node })
|
|
780
|
+
: h("div", { class: "h-full w-full" }),
|
|
781
|
+
]
|
|
782
|
+
),
|
|
783
|
+
]
|
|
784
|
+
);
|
|
785
|
+
}),
|
|
786
|
+
]);
|
|
787
|
+
};
|
|
788
|
+
},
|
|
789
|
+
}) as Component;
|
|
790
|
+
|
|
791
|
+
export const ImageNodeContent = defineComponent({
|
|
792
|
+
name: "ImageNodeContent",
|
|
793
|
+
props: {
|
|
794
|
+
element: { type: Object as PropType<PanelElement>, required: true },
|
|
795
|
+
},
|
|
796
|
+
setup(p) {
|
|
797
|
+
return () => {
|
|
798
|
+
if (hasBackgroundImage(p.element)) return null;
|
|
799
|
+
return h(
|
|
800
|
+
"div",
|
|
801
|
+
{
|
|
802
|
+
class:
|
|
803
|
+
"flex h-full w-full items-center justify-center rounded border border-dashed border-border/70 bg-muted/15 px-2 text-[11px] text-muted-foreground",
|
|
804
|
+
},
|
|
805
|
+
"图片占位"
|
|
806
|
+
);
|
|
807
|
+
};
|
|
808
|
+
},
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
export const EmptyNodePlaceholder = defineComponent({
|
|
812
|
+
name: "EmptyNodePlaceholder",
|
|
813
|
+
props: {
|
|
814
|
+
element: { type: Object as PropType<PanelElement>, required: true },
|
|
815
|
+
},
|
|
816
|
+
setup(p) {
|
|
817
|
+
return () => {
|
|
818
|
+
const labelMap: Record<string, string> = { video: "视频占位", audio: "音频占位" };
|
|
819
|
+
const label = labelMap[p.element.materialType ?? ""] ?? `${p.element.materialType ?? "节点"} 占位`;
|
|
820
|
+
return h(
|
|
821
|
+
"div",
|
|
822
|
+
{
|
|
823
|
+
class:
|
|
824
|
+
"flex h-full w-full items-center justify-center rounded border border-dashed border-border/70 bg-muted/15 px-2 text-[11px] text-muted-foreground",
|
|
825
|
+
},
|
|
826
|
+
label
|
|
827
|
+
);
|
|
828
|
+
};
|
|
829
|
+
},
|
|
830
|
+
});
|