|
@@ -1,5 +1,6 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="edit-program-layout">
|
|
<div class="edit-program-layout">
|
|
|
|
|
+ <!-- 左侧组件栏及返回按钮 -->
|
|
|
<!-- 左侧组件栏及返回按钮 -->
|
|
<!-- 左侧组件栏及返回按钮 -->
|
|
|
<div class="sidebar">
|
|
<div class="sidebar">
|
|
|
<el-button class="back-btn" type="default" @click="goBack" circle>
|
|
<el-button class="back-btn" type="default" @click="goBack" circle>
|
|
@@ -17,13 +18,27 @@
|
|
|
|
|
|
|
|
<!-- 中间编辑区 -->
|
|
<!-- 中间编辑区 -->
|
|
|
<div class="main-editor">
|
|
<div class="main-editor">
|
|
|
- <div class="editor-canvas" ref="editorCanvasRef">
|
|
|
|
|
|
|
+ <div class="toolbar">
|
|
|
|
|
+ <div class="toolbar-item" draggable="true" @dragstart="onToolbarDragStart('text')">文本</div>
|
|
|
|
|
+ <!-- 可扩展更多组件 -->
|
|
|
|
|
+ </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)"
|
|
<template v-for="item in editorContent.elements.sort((a, b) => a.depth - b.depth)"
|
|
|
:key="item.depth + '-' + item.type">
|
|
:key="item.depth + '-' + item.type">
|
|
|
<CanvasBoard v-if="item.type === 'canvas'" :width="item.width" :height="item.height" :bg="item.bg"
|
|
<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 }" />
|
|
|
|
|
- <!-- 未来可扩展其他组件类型的渲染 -->
|
|
|
|
|
|
|
+ :scale="canvasScale" @click.stop="selectComponent(item)"
|
|
|
|
|
+ :class="{ selected: selectedComponent === item }" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template v-for="item in editorContent.elements.sort((a, b) => a.depth - b.depth)"
|
|
|
|
|
+ :key="'text-' + item.depth + '-' + item.type">
|
|
|
|
|
+ <TextBoard v-if="item.type === 'text'" :text="item.text" :color="item.color" :font-size="item.fontSize"
|
|
|
|
|
+ :font-weight="item.fontWeight" :align="item.align" :style="{
|
|
|
|
|
+ position: 'absolute',
|
|
|
|
|
+ left: (item.x || 0) + 'px',
|
|
|
|
|
+ top: (item.y || 0) + 'px',
|
|
|
|
|
+ width: (item.width || 200) + 'px',
|
|
|
|
|
+ height: (item.height || 40) + 'px'
|
|
|
|
|
+ }" @click.stop="selectComponent(item)" :class="{ selected: selectedComponent === item }" />
|
|
|
</template>
|
|
</template>
|
|
|
</div>
|
|
</div>
|
|
|
<el-button type="primary" class="save-btn" @click="handleSave">保存</el-button>
|
|
<el-button type="primary" class="save-btn" @click="handleSave">保存</el-button>
|
|
@@ -62,6 +77,7 @@
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { ref, onMounted, computed, nextTick } from 'vue';
|
|
import { ref, onMounted, computed, nextTick } from 'vue';
|
|
|
import CanvasBoard from './component/CanvasBoard.vue';
|
|
import CanvasBoard from './component/CanvasBoard.vue';
|
|
|
|
|
+import TextBoard from './component/TextBoard.vue';
|
|
|
import { canvasPropNameMap } from './component/propNameMaps';
|
|
import { canvasPropNameMap } from './component/propNameMaps';
|
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
import { ElMessage } from 'element-plus';
|
|
import { ElMessage } from 'element-plus';
|
|
@@ -181,6 +197,43 @@ const editorContent = ref({ elements: [] });
|
|
|
|
|
|
|
|
// editor-canvas 缩放逻辑
|
|
// editor-canvas 缩放逻辑
|
|
|
const editorCanvasRef = ref<HTMLElement | null>(null);
|
|
const editorCanvasRef = ref<HTMLElement | null>(null);
|
|
|
|
|
+const dragComponentType = ref<string | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+function onToolbarDragStart(type: string) {
|
|
|
|
|
+ dragComponentType.value = type;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onCanvasDrop(e: DragEvent) {
|
|
|
|
|
+ if (!dragComponentType.value) return;
|
|
|
|
|
+ // 计算相对画布的坐标(此处简单居中,后续可完善为鼠标点)
|
|
|
|
|
+ const canvas = editorContent.value.elements.find((el: any) => el.type === 'canvas');
|
|
|
|
|
+ let x = 50,
|
|
|
|
|
+ y = 50;
|
|
|
|
|
+ if (canvas && editorCanvasRef.value) {
|
|
|
|
|
+ // 计算鼠标在容器中的位置,考虑缩放
|
|
|
|
|
+ const rect = editorCanvasRef.value.getBoundingClientRect();
|
|
|
|
|
+ const scale = canvasScale.value || 1;
|
|
|
|
|
+ x = (e.clientX - rect.left) / scale - (canvas.x || 0);
|
|
|
|
|
+ y = (e.clientY - rect.top) / scale - (canvas.y || 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dragComponentType.value === 'text') {
|
|
|
|
|
+ editorContent.value.elements.push({
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ text: '新文本',
|
|
|
|
|
+ color: '#222',
|
|
|
|
|
+ fontSize: 24,
|
|
|
|
|
+ fontWeight: 'normal',
|
|
|
|
|
+ align: 'center',
|
|
|
|
|
+ x,
|
|
|
|
|
+ y,
|
|
|
|
|
+ width: 200,
|
|
|
|
|
+ height: 40,
|
|
|
|
|
+ depth: editorContent.value.elements.length
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ dragComponentType.value = null;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const containerSize = ref({ width: 0, height: 0 });
|
|
const containerSize = ref({ width: 0, height: 0 });
|
|
|
|
|
|
|
|
function updateContainerSize() {
|
|
function updateContainerSize() {
|
|
@@ -195,9 +248,7 @@ onMounted(() => {
|
|
|
window.addEventListener('resize', updateContainerSize);
|
|
window.addEventListener('resize', updateContainerSize);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-const canvas = computed(() =>
|
|
|
|
|
- editorContent.value.elements.find((el: any) => el.type === 'canvas')
|
|
|
|
|
-);
|
|
|
|
|
|
|
+const canvas = computed(() => editorContent.value.elements.find((el: any) => el.type === 'canvas'));
|
|
|
|
|
|
|
|
const canvasScale = computed(() => {
|
|
const canvasScale = computed(() => {
|
|
|
if (!canvas.value) return 1;
|
|
if (!canvas.value) return 1;
|
|
@@ -305,6 +356,38 @@ const goBack = () => {
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
margin-bottom: 24px;
|
|
margin-bottom: 24px;
|
|
|
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.06);
|
|
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.06);
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.toolbar {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 48px;
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 0 24px;
|
|
|
|
|
+ border-bottom: 1px solid #e0e3e8;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.toolbar-item {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid #e0e3e8;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ padding: 6px 20px;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ cursor: grab;
|
|
|
|
|
+ user-select: none;
|
|
|
|
|
+ transition:
|
|
|
|
|
+ box-shadow 0.2s,
|
|
|
|
|
+ border-color 0.2s;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.toolbar-item:active {
|
|
|
|
|
+ box-shadow: 0 2px 8px rgba(64, 158, 255, 0.13);
|
|
|
|
|
+ border-color: #409eff;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.canvas-content {
|
|
.canvas-content {
|