|
|
@@ -39,60 +39,64 @@
|
|
|
<!-- 可扩展更多组件 -->
|
|
|
</div>
|
|
|
<div class="editor-canvas" ref="editorCanvasRef" @dragover.prevent @drop="onCanvasDrop">
|
|
|
- <template v-for="item in editorContent.elements.sort((a, b) => a.depth - b.depth)"
|
|
|
- :key="item.depth + '-' + item.type">
|
|
|
- <CanvasBoard v-if="item.type === 'canvas'" :width="item.width" :height="item.height" :bg="item.bg"
|
|
|
- :scale="canvasScale" @click.stop="selectComponent(item)"
|
|
|
- :class="{ selected: selectedComponent === item }" />
|
|
|
- <div v-else :style="{
|
|
|
- position: 'absolute',
|
|
|
- left: (item.x || 0) + 'px',
|
|
|
- top: (item.y || 0) + 'px',
|
|
|
- width: (item.width || 200) + 'px',
|
|
|
- height: (item.height || 40) + 'px',
|
|
|
- cursor: draggingId === item.depth ? 'grabbing' : 'move',
|
|
|
- zIndex: 10
|
|
|
- }" @mousedown="onElementMouseDown($event, item)">
|
|
|
- <TextBoard v-if="item.type === 'text'" :text="item.text" :color="item.color" :font-size="item.fontSize"
|
|
|
- :font-weight="item.fontWeight" :align="item.align" :width="item.width" :height="item.height"
|
|
|
- :selected="selectedComponent === item" @resize="
|
|
|
- ({ width, height }) => {
|
|
|
- item.width = width;
|
|
|
- item.height = height;
|
|
|
- }
|
|
|
- " @click.stop="selectComponent(item)" :class="{ selected: selectedComponent === item }" />
|
|
|
- <ScrollingTextBoard v-if="item.type === 'scrollingText'" :text="item.text" :color="item.color"
|
|
|
- :font-size="item.fontSize" :font-weight="item.fontWeight" :align="item.align" :width="item.width"
|
|
|
- :height="item.height" :speed="item.speed" :selected="selectedComponent === item" @resize="
|
|
|
- ({ width, height }) => {
|
|
|
- item.width = width;
|
|
|
- item.height = height;
|
|
|
- }
|
|
|
- " @click.stop="selectComponent(item)" :class="{ selected: selectedComponent === item }" />
|
|
|
- <MediaAssetBoard v-if="item.type === 'mediaAsset'" :width="item.width" :height="item.height"
|
|
|
- :media-id="item.mediaId" :selected="selectedComponent === item" @resize="
|
|
|
- ({ width, height }) => {
|
|
|
- item.width = width;
|
|
|
- item.height = height;
|
|
|
- }
|
|
|
- " @click.stop="selectComponent(item)" :class="{ selected: selectedComponent === item }" />
|
|
|
- <LiveBoard v-if="item.type === 'live'" :width="item.width" :height="item.height" :live-url="item.liveUrl"
|
|
|
- :play-audio="item.playAudio" :selected="selectedComponent === item" @resize="
|
|
|
- ({ width, height }) => {
|
|
|
- item.width = width;
|
|
|
- item.height = height;
|
|
|
- }
|
|
|
- " @click.stop="selectComponent(item)" :class="{ selected: selectedComponent === item }" />
|
|
|
- <WebPageBoard v-if="item.type === 'webPage'" :width="item.width" :height="item.height" :url="item.url"
|
|
|
- :selected="selectedComponent === item" @resize="
|
|
|
- ({ width, height }) => {
|
|
|
- item.width = width;
|
|
|
- item.height = height;
|
|
|
- }
|
|
|
- " @click.stop="selectComponent(item)" :class="{ selected: selectedComponent === item }" />
|
|
|
- <!-- 未来可扩展更多类型 -->
|
|
|
- </div>
|
|
|
- </template>
|
|
|
+ <CanvasBoard v-if="canvasItem" :width="canvasItem.width" :height="canvasItem.height" :bg="canvasItem.bg"
|
|
|
+ :scale="canvasScale" @click.stop="selectComponent(canvasItem)"
|
|
|
+ :class="{ selected: selectedComponent === canvasItem }">
|
|
|
+ <template #default>
|
|
|
+ <template
|
|
|
+ v-for="item in editorContent.elements.filter((el) => el.type !== 'canvas').sort((a, b) => a.depth - b.depth)"
|
|
|
+ :key="item.depth + '-' + item.type">
|
|
|
+ <div :style="{
|
|
|
+ position: 'absolute',
|
|
|
+ left: (item.x || 0) * canvasScale + 'px',
|
|
|
+ top: (item.y || 0) * canvasScale + 'px',
|
|
|
+ width: (item.width || 200) * canvasScale + 'px',
|
|
|
+ height: (item.height || 40) * canvasScale + 'px',
|
|
|
+ cursor: draggingId === item.depth ? 'grabbing' : 'move',
|
|
|
+ zIndex: 10
|
|
|
+ }" @mousedown="onElementMouseDown($event, item)">
|
|
|
+ <TextBoard v-if="item.type === 'text'" :text="item.text" :color="item.color" :font-size="item.fontSize"
|
|
|
+ :font-weight="item.fontWeight" :align="item.align" :width="item.width" :height="item.height"
|
|
|
+ :selected="selectedComponent === item" @resize="
|
|
|
+ ({ width, height }) => {
|
|
|
+ item.width = width;
|
|
|
+ item.height = height;
|
|
|
+ }
|
|
|
+ " @click.stop="selectComponent(item)" :class="{ selected: selectedComponent === item }" />
|
|
|
+ <ScrollingTextBoard v-if="item.type === 'scrollingText'" :text="item.text" :color="item.color"
|
|
|
+ :font-size="item.fontSize" :font-weight="item.fontWeight" :align="item.align" :width="item.width"
|
|
|
+ :height="item.height" :speed="item.speed" :selected="selectedComponent === item" @resize="
|
|
|
+ ({ width, height }) => {
|
|
|
+ item.width = width;
|
|
|
+ item.height = height;
|
|
|
+ }
|
|
|
+ " @click.stop="selectComponent(item)" :class="{ selected: selectedComponent === item }" />
|
|
|
+ <MediaAssetBoard v-if="item.type === 'mediaAsset'" :width="item.width" :height="item.height"
|
|
|
+ :media-id="item.mediaId" :selected="selectedComponent === item" @resize="
|
|
|
+ ({ width, height }) => {
|
|
|
+ item.width = width;
|
|
|
+ item.height = height;
|
|
|
+ }
|
|
|
+ " @click.stop="selectComponent(item)" :class="{ selected: selectedComponent === item }" />
|
|
|
+ <LiveBoard v-if="item.type === 'live'" :width="item.width" :height="item.height"
|
|
|
+ :live-url="item.liveUrl" :play-audio="item.playAudio" :selected="selectedComponent === item" @resize="
|
|
|
+ ({ width, height }) => {
|
|
|
+ item.width = width;
|
|
|
+ item.height = height;
|
|
|
+ }
|
|
|
+ " @click.stop="selectComponent(item)" :class="{ selected: selectedComponent === item }" />
|
|
|
+ <WebPageBoard v-if="item.type === 'webPage'" :width="item.width" :height="item.height" :url="item.url"
|
|
|
+ :selected="selectedComponent === item" @resize="
|
|
|
+ ({ width, height }) => {
|
|
|
+ item.width = width;
|
|
|
+ item.height = height;
|
|
|
+ }
|
|
|
+ " @click.stop="selectComponent(item)" :class="{ selected: selectedComponent === item }" />
|
|
|
+ <!-- 未来可扩展更多类型 -->
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </CanvasBoard>
|
|
|
</div>
|
|
|
<el-button type="primary" class="save-btn" :loading="saveLoading" :disabled="saveLoading"
|
|
|
@click="handleSave">保存</el-button>
|
|
|
@@ -119,6 +123,29 @@
|
|
|
</template>
|
|
|
</el-form-item>
|
|
|
</template>
|
|
|
+
|
|
|
+ <!-- 对齐尺寸操作区 -->
|
|
|
+ <div style="margin: 12px 0">
|
|
|
+ <div style="font-weight: bold; margin-bottom: 4px">对齐尺寸</div>
|
|
|
+ <div style="display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px">
|
|
|
+ <el-button size="small" @click="alignComponent('left')">水平靠左</el-button>
|
|
|
+ <el-button size="small" @click="alignComponent('right')">水平靠右</el-button>
|
|
|
+ <el-button size="small" @click="alignComponent('top')">垂直靠上</el-button>
|
|
|
+ <el-button size="small" @click="alignComponent('bottom')">垂直靠下</el-button>
|
|
|
+ </div>
|
|
|
+ <div style="display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px">
|
|
|
+ <el-button size="small" @click="alignComponent('width-full')">宽铺满</el-button>
|
|
|
+ <el-button size="small" @click="alignComponent('width-half')">宽半屏</el-button>
|
|
|
+ <el-button size="small" @click="alignComponent('width-third')">宽1/3屏</el-button>
|
|
|
+ <el-button size="small" @click="alignComponent('width-quarter')">宽1/4屏</el-button>
|
|
|
+ </div>
|
|
|
+ <div style="display: flex; flex-wrap: wrap; gap: 4px">
|
|
|
+ <el-button size="small" @click="alignComponent('height-full')">高铺满</el-button>
|
|
|
+ <el-button size="small" @click="alignComponent('height-half')">高半屏</el-button>
|
|
|
+ <el-button size="small" @click="alignComponent('height-third')">高1/3屏</el-button>
|
|
|
+ <el-button size="small" @click="alignComponent('height-quarter')">高1/4屏</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
<template v-else>
|
|
|
<div style="color: #bbb">请点击编辑区中的组件以编辑属性</div>
|
|
|
@@ -134,6 +161,64 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
+// 对齐尺寸操作
|
|
|
+function alignComponent(type: string) {
|
|
|
+ if (!selectedComponent.value || selectedComponent.value.type === 'canvas') return;
|
|
|
+ // 找到canvas尺寸
|
|
|
+ const canvas = editorContent.value.elements.find((el: any) => el.type === 'canvas');
|
|
|
+ if (!canvas) return;
|
|
|
+ const cW = Number(canvas.width) || 600;
|
|
|
+ const cH = Number(canvas.height) || 400;
|
|
|
+ // 只操作当前选中组件
|
|
|
+ const comp = selectedComponent.value;
|
|
|
+ switch (type) {
|
|
|
+ case 'left':
|
|
|
+ comp.x = 0;
|
|
|
+ break;
|
|
|
+ case 'right':
|
|
|
+ comp.x = cW - (Number(comp.width) || 0);
|
|
|
+ break;
|
|
|
+ case 'top':
|
|
|
+ comp.y = 0;
|
|
|
+ break;
|
|
|
+ case 'bottom':
|
|
|
+ comp.y = cH - (Number(comp.height) || 0);
|
|
|
+ break;
|
|
|
+ case 'width-full':
|
|
|
+ comp.x = 0;
|
|
|
+ comp.width = cW;
|
|
|
+ break;
|
|
|
+ case 'width-half':
|
|
|
+ comp.x = 0;
|
|
|
+ comp.width = Math.round(cW / 2);
|
|
|
+ break;
|
|
|
+ case 'width-third':
|
|
|
+ comp.x = 0;
|
|
|
+ comp.width = Math.round(cW / 3);
|
|
|
+ break;
|
|
|
+ case 'width-quarter':
|
|
|
+ comp.x = 0;
|
|
|
+ comp.width = Math.round(cW / 4);
|
|
|
+ break;
|
|
|
+ case 'height-full':
|
|
|
+ comp.y = 0;
|
|
|
+ comp.height = cH;
|
|
|
+ break;
|
|
|
+ case 'height-half':
|
|
|
+ comp.y = 0;
|
|
|
+ comp.height = Math.round(cH / 2);
|
|
|
+ break;
|
|
|
+ case 'height-third':
|
|
|
+ comp.y = 0;
|
|
|
+ comp.height = Math.round(cH / 3);
|
|
|
+ break;
|
|
|
+ case 'height-quarter':
|
|
|
+ comp.y = 0;
|
|
|
+ comp.height = Math.round(cH / 4);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// 拖拽排序相关
|
|
|
const sidebarDrag = ref<{ item: any; idx: number } | null>(null);
|
|
|
|
|
|
@@ -345,8 +430,9 @@ function onElementMouseMove(e: MouseEvent) {
|
|
|
if (draggingId.value === null) return;
|
|
|
const item = editorContent.value.elements.find((el) => el.depth === draggingId.value);
|
|
|
if (!item) return;
|
|
|
- item.x = dragStart.offsetX + (e.clientX - dragStart.x);
|
|
|
- item.y = dragStart.offsetY + (e.clientY - dragStart.y);
|
|
|
+ // 拖拽时坐标除以缩放比例,保证拖拽速度和鼠标一致
|
|
|
+ item.x = dragStart.offsetX + (e.clientX - dragStart.x) / canvasScale.value;
|
|
|
+ item.y = dragStart.offsetY + (e.clientY - dragStart.y) / canvasScale.value;
|
|
|
}
|
|
|
|
|
|
function onElementMouseUp() {
|
|
|
@@ -458,12 +544,13 @@ const canvasScale = computed(() => {
|
|
|
const cH = Number(canvas.value.height) || 400;
|
|
|
const boxW = containerSize.value.width;
|
|
|
const boxH = containerSize.value.height;
|
|
|
- // console.log('[EditProgram] canvasScale computed:', cW, cH, boxW, boxH);
|
|
|
if (!boxW || !boxH) return 1;
|
|
|
- // console.log('[EditProgram] canvasScale computed:', Math.min(boxW / cW, boxH / cH, 1));
|
|
|
return Math.min(boxW / cW, boxH / cH, 1);
|
|
|
});
|
|
|
|
|
|
+// 修复:为模板提供 canvasItem 变量
|
|
|
+const canvasItem = computed(() => editorContent.value.elements.find((el: any) => el.type === 'canvas'));
|
|
|
+
|
|
|
const saveLoading = ref(false);
|
|
|
const handleSave = async () => {
|
|
|
saveLoading.value = true;
|