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