Browse Source

添加文本组件

Shinohara Haruna 5 tháng trước cách đây
mục cha
commit
44ecf7b940

+ 90 - 7
smsb-plus-ui/src/views/smsb/itemProgram/EditProgram.vue

@@ -1,5 +1,6 @@
 <template>
   <div class="edit-program-layout">
+    <!-- 左侧组件栏及返回按钮 -->
     <!-- 左侧组件栏及返回按钮 -->
     <div class="sidebar">
       <el-button class="back-btn" type="default" @click="goBack" circle>
@@ -17,13 +18,27 @@
 
     <!-- 中间编辑区 -->
     <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)"
           :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 }" />
-          <!-- 未来可扩展其他组件类型的渲染 -->
+            :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>
       </div>
       <el-button type="primary" class="save-btn" @click="handleSave">保存</el-button>
@@ -62,6 +77,7 @@
 <script setup lang="ts">
 import { ref, onMounted, computed, nextTick } from 'vue';
 import CanvasBoard from './component/CanvasBoard.vue';
+import TextBoard from './component/TextBoard.vue';
 import { canvasPropNameMap } from './component/propNameMaps';
 import { useRoute, useRouter } from 'vue-router';
 import { ElMessage } from 'element-plus';
@@ -181,6 +197,43 @@ const editorContent = ref({ elements: [] });
 
 // editor-canvas 缩放逻辑
 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 });
 
 function updateContainerSize() {
@@ -195,9 +248,7 @@ onMounted(() => {
   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(() => {
   if (!canvas.value) return 1;
@@ -305,6 +356,38 @@ const goBack = () => {
   justify-content: center;
   margin-bottom: 24px;
   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 {

+ 1 - 1
smsb-plus-ui/src/views/smsb/itemProgram/component/CanvasBoard.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="canvas-board-wrapper">
-    <div class="canvas-board" :style="canvasStyle">
+    <div class="canvas-board" :style="canvasStyle" style="pointer-events: none;">
       <slot />
     </div>
   </div>

+ 43 - 0
smsb-plus-ui/src/views/smsb/itemProgram/component/TextBoard.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="text-board" :style="textStyle">
+    {{ text }}
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue';
+interface Props {
+  text?: string;
+  color?: string;
+  fontSize?: string | number;
+  fontWeight?: string | number;
+  align?: 'left' | 'center' | 'right';
+}
+const props = defineProps<Props>();
+
+const textStyle = computed(() => ({
+  color: props.color || '#222',
+  fontSize: typeof props.fontSize === 'number' ? props.fontSize + 'px' : props.fontSize || '24px',
+  fontWeight: props.fontWeight || 'normal',
+  textAlign: props.align || 'center',
+  width: '100%',
+  height: '100%',
+  display: 'flex',
+  alignItems: 'center',
+  justifyContent: props.align === 'left' ? 'flex-start' : props.align === 'right' ? 'flex-end' : 'center',
+  wordBreak: 'break-all',
+}));
+
+const text = computed(() => props.text || '双击编辑文本');
+</script>
+
+<style scoped>
+.text-board {
+  width: 100%;
+  height: 100%;
+  background: transparent;
+  outline: none;
+  cursor: text;
+  user-select: text;
+}
+</style>