Przeglądaj źródła

feat: 添加框选功能

liaojiaxing 10 miesięcy temu
rodzic
commit
4ffa9baee3

+ 25 - 0
src/store/modules/action.ts

@@ -0,0 +1,25 @@
+import { defineStore } from "pinia";
+
+export const useAcionStore = defineStore({
+  id: 'action',
+  state() {
+    return {
+      // 操作记录
+      records: []
+    }
+  },
+  actions: {
+    addRecord(record) {
+
+    },
+    undo() {
+
+    },
+    redo() {
+
+    },
+    clear() {
+
+    }
+  }
+})

+ 2 - 2
src/views/designer/component/ComponentWrapper.vue

@@ -4,7 +4,7 @@
     ref="componentWrapperRef"
     :style="warpperStyle"
   >
-    <Container v-bind="componentData.container" @click="handleSelectComponent">
+    <Container v-bind="componentData.container" @click.stop="handleSelectComponent">
       <component
         :is="component"
         v-bind="componentData.props"
@@ -141,7 +141,7 @@ useDraggable(componentWrapperRef, {
     showNameTip.value = true;
   },
 });
-const handleSelectComponent = () => {
+const handleSelectComponent = (e: Event) => {
   projectStore.setSelectedElementKeys([componentData.key]);
 };
 

+ 12 - 11
src/views/designer/component/Stage.vue

@@ -27,10 +27,8 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, onBeforeUnmount, computed, nextTick, watch } from "vue";
+import { ref, onMounted, onBeforeUnmount, computed, nextTick, watch, defineExpose } from "vue";
 import type { Ref } from "vue";
-import { useRouter } from "vue-router";
-import { message } from "ant-design-vue";
 import { useStageStore } from "@/store/modules/stage";
 import { useProjectStore } from "@/store/modules/project";
 import { useScroll } from "@vueuse/core";
@@ -39,7 +37,6 @@ import ComponentWrapper from "./ComponentWrapper.vue";
 const stageWrapperRef: Ref<HTMLElement | null> = ref(null);
 const stageRef: Ref<HTMLElement | null> = ref(null);
 const canvasRef: Ref<HTMLElement | null> = ref(null);
-const router = useRouter();
 const stageStore = useStageStore();
 const projectStore = useProjectStore();
 
@@ -170,13 +167,13 @@ const handleDrop = (e: DragEvent) => {
 
 // 点击画布,移除所有组件选中状态
 const handleCanvasClick = (e: MouseEvent) => {
-  if(
-    !e?.target?.closest(".edit-box") 
-    && !e?.target?.closest(".component-content")
-    && !e?.target?.closest(".component-wrapper")
-  ) {
-    projectStore.clearAllSelectedElement();
-  }
+  // if(
+  //   !e?.target?.closest(".edit-box") 
+  //   && !e?.target?.closest(".component-content")
+  //   && !e?.target?.closest(".component-wrapper")
+  // ) {
+  //   projectStore.clearAllSelectedElement();
+  // }
 };
 
 /* 适应大小设置 */
@@ -192,6 +189,10 @@ watch(
   }
 );
 
+defineExpose({
+  getPosition: () => canvasRef.value?.getBoundingClientRect(),
+});
+
 onMounted(() => {
   initStagePosition();
   initScale();

+ 153 - 42
src/views/designer/component/Workspace.vue

@@ -1,52 +1,62 @@
 <template>
-  <Flex class="workspace" vertical>
-    <div class="workspace-top">
-      <Stage />
-      <Scaleplate />
-    </div>
-    <Flex class="workspace-bottom" justify="space-between" align="center">
-      <div class="bottom-left">
-        <span style="margin-right: 12px"
-          >画布尺寸:{{ projectStore.projectInfo.width }} *
-          {{ projectStore.projectInfo.height }}px</span
-        >
-        <span
-          >画布自适应:<Select
+  <div
+    ref="boxRef"
+    style="width: 100%; height: 100%; position: relative;"
+    @mousedown="handleMouseDown"
+    @mousemove="handleMouseMove"
+    @mouseup="handleMouseUp"
+  >
+    <Flex class="workspace" vertical>
+      <div class="workspace-top">
+        <Stage ref="stageRef" />
+        <Scaleplate />
+      </div>
+      <Flex class="workspace-bottom" justify="space-between" align="center">
+        <div class="bottom-left">
+          <span style="margin-right: 12px"
+            >画布尺寸:{{ projectStore.projectInfo.width }} *
+            {{ projectStore.projectInfo.height }}px</span
+          >
+          <span
+            >画布自适应:<Select
+              size="small"
+              style="width: 120px"
+              :value="projectStore.projectInfo.fillType"
+              :options="fillOptions"
+              @change="projectStore.setFillType"
+          /></span>
+        </div>
+        <div class="bottom-right">
+          <Button
+            size="small"
+            type="text"
+            :disabled="stageStore.scale <= 0.1"
+            @click="handleSizeChange(Number(stageStore.scale) - 0.1)"
+            ><MinusCircleOutlined
+          /></Button>
+          <AutoComplete
             size="small"
             style="width: 120px"
-            :value="projectStore.projectInfo.fillType"
-            :options="fillOptions"
-            @change="projectStore.setFillType"
-        /></span>
-      </div>
-      <div class="bottom-right">
-        <Button
-          size="small"
-          type="text"
-          :disabled="stageStore.scale <= 0.1"
-          @click="handleSizeChange(Number(stageStore.scale) - 0.1)"
-          ><MinusCircleOutlined
-        /></Button>
-        <AutoComplete
-          size="small"
-          style="width: 120px"
-          :options="sizeOptions"
-          :value="(stageStore.scale * 100).toFixed(0) + '%'"
-          @change="handleSizeChange"
-        />
-        <Button
-          size="small"
-          type="text"
-          :disabled="stageStore.scale >= 4"
-          @click="handleSizeChange(Number(stageStore.scale) + 0.1)"
-          ><PlusCircleOutlined
-        /></Button>
-      </div>
+            :options="sizeOptions"
+            :value="(stageStore.scale * 100).toFixed(0) + '%'"
+            @change="handleSizeChange"
+          />
+          <Button
+            size="small"
+            type="text"
+            :disabled="stageStore.scale >= 4"
+            @click="handleSizeChange(Number(stageStore.scale) + 0.1)"
+            ><PlusCircleOutlined
+          /></Button>
+        </div>
+      </Flex>
     </Flex>
-  </Flex>
+    <div class="selectBox" ref="selectBoxRef"></div>
+  </div>
 </template>
 
 <script setup lang="ts">
+import { ref } from "vue";
 import { Button, Select, AutoComplete, Flex } from "ant-design-vue";
 import { MinusCircleOutlined, PlusCircleOutlined } from "@ant-design/icons-vue";
 import Scaleplate from "./Scaleplate.vue";
@@ -54,9 +64,11 @@ import Stage from "./Stage.vue";
 import { useProjectStore } from "@/store/modules/project";
 import { useStageStore } from "@/store/modules/stage";
 import { ScreenFillEnum } from "@/enum/screenFillEnum";
+import { throttle } from "lodash";
 
 const projectStore = useProjectStore();
 const stageStore = useStageStore();
+const stageRef = ref<{getPosition: HTMLElement['getBoundingClientRect']} | null>();
 
 const sizeOptions = [
   { value: 0.1, label: "10%" },
@@ -93,6 +105,93 @@ const handleSizeChange = (val: any) => {
     }
   }
 };
+
+/* ====================处理框选多个组件======================== */
+const selectBoxRef = ref<HTMLElement | null>(null);
+const boxRef = ref<HTMLElement | null>(null);
+let isMouseDown = ref(false);
+let startX = 0; // 框选起始x坐标
+let startY = 0; // 框选起始y坐标
+let workspaceLeft = 0;
+let workspaceTop = 0;
+// 鼠标按下
+const handleMouseDown = (e: MouseEvent) => {
+  if (
+    e?.target?.closest(".edit-box") ||
+    e?.target?.closest(".component-content") ||
+    e?.target?.closest(".component-wrapper") ||
+    e?.target?.closest(".scaleplate-horizontal") ||
+    e?.target?.closest(".scaleplate-vertical") ||
+    e?.target?.closest(".refer-line-img") ||
+    e?.target?.closest(".workspace-bottom") ||
+    e?.target?.closest(".refer-line")
+  ) {
+    return;
+  }
+  
+  const { top, left } = boxRef.value!.getBoundingClientRect();
+  const { clientX, clientY } = e;
+  isMouseDown.value = true;
+  workspaceLeft = left;
+  workspaceTop = top;
+  startX = clientX - left;
+  startY = clientY - top;
+  selectBoxRef.value!.style.display = "block";
+  selectBoxRef.value!.style.left = `${startX}px`;
+  selectBoxRef.value!.style.top = `${startY}px`;
+};
+/* 鼠标移动 */
+const handleMouseMove = throttle((e: MouseEvent) => {
+  if (!isMouseDown.value) return;
+  const { clientX, clientY } = e;
+  const width = clientX - workspaceLeft - startX;
+  const height = clientY - workspaceTop - startY;
+
+  const left = width > 0 ? startX : clientX - workspaceLeft;
+  const top = height > 0 ? startY : clientY - workspaceTop;
+
+  selectBoxRef.value!.style.width = `${Math.abs(width)}px`;
+  selectBoxRef.value!.style.height = `${Math.abs(height)}px`;
+  selectBoxRef.value!.style.left = `${left}px`;
+  selectBoxRef.value!.style.top = `${top}px`;
+}, 50);
+/* 鼠标抬起 */
+const handleMouseUp = (e: MouseEvent) => {
+  if (!isMouseDown || (startX === 0 && startY === 0)) return;
+
+  isMouseDown.value = false;
+  selectBoxRef.value!.style.display = "none";
+  selectBoxRef.value!.style.width = "0";
+  selectBoxRef.value!.style.height = "0";
+
+  const { clientX, clientY } = e;
+  const { left: stageLeft = 0, top: stageTop = 0} = stageRef.value?.getPosition() || {};
+
+  // 框选的起始位置
+  const x1 = (Math.min(startX + workspaceLeft, clientX) - stageLeft) / stageStore.scale;
+  const y1 = (Math.min(startY + workspaceTop, clientY) - stageTop) / stageStore.scale;
+  // 框选的结束位置
+  const x2 = (Math.max(startX + workspaceLeft, clientX) - stageLeft) / stageStore.scale;
+  const y2 = (Math.max(startY + workspaceTop, clientY) - stageTop) / stageStore.scale;
+  handleSelectComponent(x1, y1, x2, y2);
+
+  // 清空临时数据
+  startX = startY = workspaceLeft = workspaceTop = 0;
+};
+/* 处理框选的组件 */
+const handleSelectComponent = (startX: number, startY: number, endX: number, endY: number) => {
+  const selectKeys = projectStore.currentPage.elements.filter((item) => {
+    const { x, y, width, height } = item.container.props;
+    const x1 = Math.min(startX, endX);
+    const x2 = Math.max(startX, endX);
+    const y1 = Math.min(startY, endY);
+    const y2 = Math.max(startY, endY);
+    // 返回判断完全包裹组件
+    return x >= x1 && y >= y1 && x + width <= x2 && y + height <= y2;
+  }).map((item) => item.key);
+  projectStore.setSelectedElementKeys(selectKeys);
+};
+/* ====================处理框选多个组件======================== */
 </script>
 
 <style lang="less" scoped>
@@ -112,4 +211,16 @@ const handleSizeChange = (val: any) => {
     color: #666;
   }
 }
+.selectBox {
+  display: none;
+  position: absolute;
+  left: 0;
+  top: 0;
+  z-index: 9999;
+  border: solid 1px #2c76bd;
+  box-sizing: border-box;
+  background-color: #2c76bd90;
+  width: 0;
+  height: 0;
+}
 </style>