Bläddra i källkod

1. 初步给定尺寸对齐功能;2. 修复画布坐标逻辑有误的bug

Shinohara Haruna 5 månader sedan
förälder
incheckning
146371db0a

+ 145 - 58
smsb-plus-ui/src/views/smsb/itemProgram/EditProgram.vue

@@ -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;

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

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