Browse Source

feature: 拖拽

xiongxt 1 year ago
parent
commit
5d808380f5

+ 50 - 0
src/views/svg/components/bar.vue

@@ -0,0 +1,50 @@
+<template>
+  <div class="svg-bar">
+    <div class="item">
+      <div class="rect"></div>
+    </div>
+    <div class="item">
+      <div class="circle"></div>
+    </div>
+  </div>
+</template>
+
+<script setup></script>
+
+<style lang="less" scoped>
+.svg-bar {
+  width: 400px;
+  height: 70px;
+  position: absolute;
+  left: 50%;
+  top: 20px;
+  transform: translateX(-50%);
+  background-color: rgba(255, 255, 255, 0.8);
+  border: 1px solid #ccc;
+  border-radius: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  .item {
+    width: 70px;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    .rect {
+      width: 40px;
+      height: 40px;
+      border: 2px solid #666;
+      border-radius: 5px;
+      cursor: pointer;
+    }
+    .circle {
+      width: 40px;
+      height: 40px;
+      border: 2px solid #666;
+      border-radius: 22px;
+      cursor: pointer;
+    }
+  }
+}
+</style>

+ 53 - 100
src/views/svg/components/svgRect.vue

@@ -1,34 +1,17 @@
 <template>
 <template>
-  <g
-    :id="id"
-    class="wrap"
-    @mousedown="handleMousedown"
-    @mouseenter="boxMouseEnter"
-    @mouseleave="boxMouseLeave"
-  >
-    <rect v-bind="outlineRectAttr" />
-    <rect v-bind="attr" />
-    <marker
-      id="markerArrow"
-      markerUnits="strokeWidth"
-      markerWidth="5"
-      markerHeight="4"
-      refX="6"
-      refY="2"
-      orient="auto"
-    >
-      <path d="M 0 0 L 5 2 L 0 4 z" stroke="#5ba8ff" fill="#5ba8ff" />
-    </marker>
-    <line
-      v-for="i in linesAttr"
-      :key="i.id"
-      v-bind="i"
-      marker-end="url(#markerArrow)"
+  <g :id="id" class="node-wrap" @mousedown="handleMousedown">
+    <rect
+      class="node-outline"
+      v-bind="outlineRectAttr"
+      :class="{ focus: isFocus }"
     />
     />
+    <rect class="node" v-bind="attr" />
     <circle
     <circle
       v-for="i in pointsAttr"
       v-for="i in pointsAttr"
       v-bind="i"
       v-bind="i"
       :key="i.pos"
       :key="i.pos"
+      class="point"
+      :class="{ focus: isFocus }"
       @mouseenter="pointMouseEnter(i)"
       @mouseenter="pointMouseEnter(i)"
       @mouseleave="pointMouseLeave(i)"
       @mouseleave="pointMouseLeave(i)"
       @mousedown="($event) => startLine($event, i)"
       @mousedown="($event) => startLine($event, i)"
@@ -49,26 +32,21 @@ import { sticky, countPointByPos } from "../utils/index";
 const props = defineProps([
 const props = defineProps([
   "pos",
   "pos",
   "size",
   "size",
-  "lines",
-  "allItems",
-  "mousePos",
   "startPoint",
   "startPoint",
   "id",
   "id",
-  "draggingId",
+  "curNodeId",
   "isDragging",
   "isDragging",
 ]);
 ]);
 const emits = defineEmits([
 const emits = defineEmits([
   "startDrag",
   "startDrag",
   "startLine",
   "startLine",
-  "updatePos",
   "endLine",
   "endLine",
   "cancelEndLine",
   "cancelEndLine",
 ]);
 ]);
-const boxPos = ref(props.pos);
-const boxSize = ref(props.size);
+const nodePos = ref(props.pos);
+const nodeSize = ref(props.size);
 const innerPos = ref({ x: 0, y: 0 });
 const innerPos = ref({ x: 0, y: 0 });
-const isFocus = computed(() => props.id === props.draggingId);
-const isHover = ref(false);
+const isFocus = computed(() => props.id === props.curNodeId);
 
 
 const attr = computed(() => {
 const attr = computed(() => {
   return {
   return {
@@ -76,9 +54,6 @@ const attr = computed(() => {
     y: props.pos.y,
     y: props.pos.y,
     width: props.size.width,
     width: props.size.width,
     height: props.size.height,
     height: props.size.height,
-    fill: "rgba(255,255,255,0.6)",
-    stroke: "#666",
-    rx: "15",
   };
   };
 });
 });
 
 
@@ -89,67 +64,38 @@ const outlineRectAttr = computed(() => {
     y: props.pos.y - space,
     y: props.pos.y - space,
     width: props.size.width + space * 2,
     width: props.size.width + space * 2,
     height: props.size.height + space * 2,
     height: props.size.height + space * 2,
-    fill: "rgba(255,255,255,0)",
-    "stroke-dasharray": "3 4",
-    stroke: isFocus.value ? "#000" : "rgba(0,0,0,0)",
-    rx: "18",
   };
   };
 });
 });
 
 
 const pointsAttr = computed(() => {
 const pointsAttr = computed(() => {
-  const visible = isHover.value || isFocus.value;
-  const defaults = {
-    r: 6,
-    stroke: visible ? "#000" : "rgba(0,0,0,0)",
-    fill: visible ? "rgba(255,255,255,1)" : "rgba(255,255,255,0)",
-  };
   return [
   return [
     {
     {
       pos: "top",
       pos: "top",
       ...countPointByPos("top", props.pos, props.size),
       ...countPointByPos("top", props.pos, props.size),
-      ...defaults,
     },
     },
     {
     {
       pos: "right",
       pos: "right",
       ...countPointByPos("right", props.pos, props.size),
       ...countPointByPos("right", props.pos, props.size),
-      ...defaults,
     },
     },
     {
     {
       pos: "bottom",
       pos: "bottom",
       ...countPointByPos("bottom", props.pos, props.size),
       ...countPointByPos("bottom", props.pos, props.size),
-      ...defaults,
     },
     },
     {
     {
       pos: "left",
       pos: "left",
       ...countPointByPos("left", props.pos, props.size),
       ...countPointByPos("left", props.pos, props.size),
-      ...defaults,
     },
     },
   ];
   ];
 });
 });
 
 
-const linesAttr = computed(() =>
-  props.lines.map((line) => {
-    const startPos = line.start.pos;
-    const { targetId, pos: endPos } = line.end;
-    const startPoint = countPointByPos(startPos, props.pos, props.size);
-    const endBox = props.allItems.find((i) => i.id === targetId);
-    const endPoint = countPointByPos(endPos, endBox.pos, endBox.size);
-    return {
-      id: `${startPos}-${targetId}-${endPos}`,
-      x1: startPoint.cx,
-      y1: startPoint.cy,
-      x2: endPoint.cx,
-      y2: endPoint.cy,
-      stroke: "#5ba8ff",
-      "stroke-width": "2",
-    };
-  })
-);
-
 function handleMousedown(e) {
 function handleMousedown(e) {
   emits("startDrag", {
   emits("startDrag", {
     id: props.id,
     id: props.id,
     event: e,
     event: e,
+    innerPos: {
+      x: e.offsetX - props.pos.x,
+      y: e.offsetY - props.pos.y,
+    },
   });
   });
   innerPos.value = {
   innerPos.value = {
     x: e.offsetX - props.pos.x,
     x: e.offsetX - props.pos.x,
@@ -177,41 +123,48 @@ function pointMouseEnter(p) {
 function pointMouseLeave() {
 function pointMouseLeave() {
   emits("cancelEndLine");
   emits("cancelEndLine");
 }
 }
+</script>
 
 
-function boxMouseEnter() {
-  emits("hover", props.id);
-  isHover.value = true;
-}
-
-function boxMouseLeave() {
-  isHover.value = false;
-}
-
-function setPos(pos) {
-  emits("updatePos", {
-    id: props.id,
-    pos: {
-      x: sticky(pos.x - innerPos.value.x),
-      y: sticky(pos.y - innerPos.value.y),
-    },
-  });
-}
+<style lang="less" scoped>
+.node-wrap {
+  &:hover {
+    .node-outline {
+      display: block;
+    }
+    .point {
+      stroke: rgba(0, 0, 0, 1);
+      fill: rgba(255, 255, 255, 1);
+    }
+  }
 
 
-watch(
-  () => props.mousePos,
-  (pos) => {
-    if (props.isDragging && isFocus.value) {
-      setPos(pos);
+  .node-outline {
+    fill: rgba(255, 255, 255, 0);
+    stroke-dasharray: 3 4;
+    stroke: rgba(0, 0, 0, 1);
+    display: none;
+    rx: 18;
+    &.focus {
+      display: block;
     }
     }
   }
   }
-);
-</script>
 
 
-<style lang="less" scoped>
-.wrap {
-  &:hover {
-    rect {
-      fill: red;
+  .node {
+    fill: rgba(255, 255, 255, 0.6);
+    stroke: #666;
+    rx: 15;
+  }
+  .point {
+    r: 6;
+    stroke: rgba(255, 255, 255, 0);
+    fill: rgba(255, 255, 255, 0);
+    transform: all 0.3s;
+    &.focus {
+      stroke: rgba(0, 0, 0, 1);
+      fill: rgba(255, 255, 255, 1);
+    }
+    &:hover {
+      stroke: #5ba8ff;
+      stroke-width: 4;
     }
     }
   }
   }
 }
 }

+ 116 - 72
src/views/svg/index.vue

@@ -2,6 +2,7 @@
 <!-- eslint-disable vue/valid-v-for -->
 <!-- eslint-disable vue/valid-v-for -->
 <template>
 <template>
   <div ref="wrapRef" class="svg-wrap" @mousemove="handleMouseMove">
   <div ref="wrapRef" class="svg-wrap" @mousemove="handleMouseMove">
+    <Bar />
     <svg
     <svg
       version="1.1"
       version="1.1"
       baseProfile="full"
       baseProfile="full"
@@ -11,27 +12,38 @@
       :width="wrapSize.width"
       :width="wrapSize.width"
       :height="wrapSize.height"
       :height="wrapSize.height"
     >
     >
+      <marker
+        id="markerArrow"
+        markerUnits="strokeWidth"
+        markerWidth="5"
+        markerHeight="4"
+        refX="6"
+        refY="2"
+        orient="auto"
+      >
+        <path d="M 0 0 L 5 2 L 0 4 z" class="trangle" />
+      </marker>
+      <line
+        v-for="i in linesAttr"
+        :key="i.id"
+        v-bind="i"
+        class="line"
+        marker-end="url(#markerArrow)"
+      />
       <SvgRect
       <SvgRect
         v-for="i in children"
         v-for="i in children"
         :key="i.id"
         :key="i.id"
         v-bind="i"
         v-bind="i"
         :isDragging="isDragging"
         :isDragging="isDragging"
-        :draggingId="draggingId"
-        :mousePos="curPos"
+        :curNodeId="curNodeId"
         :startPoint="lineStartPoint"
         :startPoint="lineStartPoint"
-        :allItems="children"
+        @click="nodeClick"
         @startLine="startLine"
         @startLine="startLine"
         @endLine="endLine"
         @endLine="endLine"
         @cancelEndLine="cancelEndLine"
         @cancelEndLine="cancelEndLine"
         @startDrag="startDrag"
         @startDrag="startDrag"
-        @hover="hover"
-        @updatePos="updatePos"
       />
       />
-      <line
-        v-if="lineStartPoint"
-        v-bind="dashLineAttr"
-        class="dash-line"
-      ></line>
+      <line v-if="lineStartPoint" v-bind="dashLineAttr" class="dash-line" />
     </svg>
     </svg>
   </div>
   </div>
 </template>
 </template>
@@ -39,77 +51,95 @@
 <script setup>
 <script setup>
 import { reactive, ref, onBeforeUnmount, onMounted, computed } from "vue";
 import { reactive, ref, onBeforeUnmount, onMounted, computed } from "vue";
 import SvgRect from "./components/svgRect.vue";
 import SvgRect from "./components/svgRect.vue";
-import { findFromArray, getPosFromEvent } from "./utils/index";
+import Bar from "./components/bar.vue";
+import {
+  findFromArray,
+  getPosFromEvent,
+  countPointByPos,
+  twoObjIsEqual,
+  sticky,
+} from "./utils/index";
 
 
 const wrapRef = ref(null);
 const wrapRef = ref(null);
 const childrenRefs = ref(null);
 const childrenRefs = ref(null);
-let curPos = ref({ x: 0, y: 0 });
+let curMousePos = ref({ x: 0, y: 0 });
 const wrapSize = reactive({ width: 0, height: 0 });
 const wrapSize = reactive({ width: 0, height: 0 });
-const draggingId = ref(0);
+const curNodeId = ref(0);
+const curNodeConfig = ref({});
 const isDragging = ref(false);
 const isDragging = ref(false);
 const lineStartPoint = ref(null);
 const lineStartPoint = ref(null);
 const lineEndPoint = ref(null);
 const lineEndPoint = ref(null);
+const lines = reactive([]);
 const children = ref([
 const children = ref([
   {
   {
     id: 1,
     id: 1,
-    pos: { x: 10, y: 10 },
+    pos: { x: 160, y: 110 },
     size: { width: 100, height: 100 },
     size: { width: 100, height: 100 },
-    lines: [],
   },
   },
   {
   {
     id: 2,
     id: 2,
-    pos: { x: 20, y: 20 },
+    pos: { x: 170, y: 120 },
     size: { width: 100, height: 100 },
     size: { width: 100, height: 100 },
-    lines: [
-      {
-        start: {
-          pos: "right",
-        },
-        end: {
-          targetId: 3,
-          pos: "left",
-        },
-      },
-    ],
   },
   },
   {
   {
     id: 3,
     id: 3,
-    pos: { x: 30, y: 30 },
+    pos: { x: 180, y: 130 },
     size: { width: 100, height: 100 },
     size: { width: 100, height: 100 },
-    lines: [],
   },
   },
 ]);
 ]);
 
 
+const curNode = computed(() => {
+  return curNodeId.value !== 0
+    ? children.value.find((i) => i.id === curNodeId.value)
+    : null;
+});
+
 const dashLineAttr = computed(() => {
 const dashLineAttr = computed(() => {
-  if (lineStartPoint.value) {
+  return lineStartPoint.value
+    ? {
+        x1: lineStartPoint.value?.cx,
+        y1: lineStartPoint.value?.cy,
+        x2: lineEndPoint.value?.cx ?? curMousePos.value.x,
+        y2: lineEndPoint.value?.cy ?? curMousePos.value.y,
+      }
+    : {};
+});
+
+const linesAttr = computed(() =>
+  lines.map((line) => {
+    const { targetId: startTargetId, pos: startPos } = line.start;
+    const { targetId: endTargetId, pos: endPos } = line.end;
+    const startNode = children.value.find((i) => i.id === startTargetId);
+    const endNode = children.value.find((i) => i.id === endTargetId);
+    const endPoint = countPointByPos(endPos, endNode.pos, endNode.size);
+    const startPoint = countPointByPos(startPos, startNode.pos, startNode.size);
     return {
     return {
-      x1: lineStartPoint.value?.cx,
-      y1: lineStartPoint.value?.cy,
-      x2: lineEndPoint.value?.cx ?? curPos.value.x,
-      y2: lineEndPoint.value?.cy ?? curPos.value.y,
+      id: `${startTargetId}-${startPos}-${endTargetId}-${endPos}`,
+      x1: startPoint.cx,
+      y1: startPoint.cy,
+      x2: endPoint.cx,
+      y2: endPoint.cy,
     };
     };
-  }
-  return {};
-});
+  })
+);
 
 
-function startDrag({ id, event }) {
+function startDrag({ id, event, innerPos }) {
   isDragging.value = true;
   isDragging.value = true;
-  draggingId.value = id;
+  curNodeId.value = id;
+  curNodeConfig.value = {
+    innerPos,
+  };
   lineStartPoint.value = null;
   lineStartPoint.value = null;
   lineEndPoint.value = null;
   lineEndPoint.value = null;
-  curPos.value = getPosFromEvent(event);
+  // 移动当前的节点最后渲染
+  curMousePos.value = getPosFromEvent(event, wrapRef.value);
   const { index, result } = findFromArray(children.value, (i) => i.id === id);
   const { index, result } = findFromArray(children.value, (i) => i.id === id);
   children.value.splice(index, 1);
   children.value.splice(index, 1);
   children.value.push(result);
   children.value.push(result);
 }
 }
 
 
-function hover(id) {
-  // const { index, result } = findFromArray(children.value, (i) => i.id === id);
-  // children.value.splice(index, 1);
-  // children.value.push(result);
-}
-
 function startLine({ id, point }) {
 function startLine({ id, point }) {
+  curNodeId.value = id;
   isDragging.value = false;
   isDragging.value = false;
   lineStartPoint.value = {
   lineStartPoint.value = {
     targetId: id,
     targetId: id,
@@ -132,39 +162,33 @@ function cancelEndLine() {
   }
   }
 }
 }
 
 
-function updatePos({ id, pos }) {
-  const { result } = findFromArray(children.value, (i) => i.id === id);
-  result.pos = pos;
-}
-
 function handleMouseMove(e) {
 function handleMouseMove(e) {
-  curPos.value = getPosFromEvent(e);
+  curMousePos.value = getPosFromEvent(e, wrapRef.value);
+  console.log(curMousePos.value);
+  if (curNode.value && isDragging.value) {
+    curNode.value.pos = {
+      x: sticky(curMousePos.value.x - curNodeConfig.value.innerPos.x),
+      y: sticky(curMousePos.value.y - curNodeConfig.value.innerPos.y),
+    };
+  }
 }
 }
 
 
 function handleMouseUp() {
 function handleMouseUp() {
-  if (isDragging.value === false) {
-    draggingId.value = 0;
-  }
   isDragging.value = false;
   isDragging.value = false;
   if (lineStartPoint.value && lineEndPoint.value) {
   if (lineStartPoint.value && lineEndPoint.value) {
-    const startId = lineStartPoint.value.targetId;
-    const { result } = findFromArray(children.value, (i) => i.id === startId);
-    const exist = result.lines.find(
-      (line) =>
-        line.start.pos === lineStartPoint.value.pos &&
-        line.end.targetId === lineEndPoint.value.targetId &&
-        line.end.pos === lineEndPoint.value.pos
-    );
+    const line = {
+      start: {
+        targetId: lineStartPoint.value.targetId,
+        pos: lineStartPoint.value.pos,
+      },
+      end: {
+        targetId: lineEndPoint.value.targetId,
+        pos: lineEndPoint.value.pos,
+      },
+    };
+    const exist = lines.find((_line) => twoObjIsEqual(_line, line));
     if (!exist) {
     if (!exist) {
-      result.lines.push({
-        start: {
-          pos: lineStartPoint.value.pos,
-        },
-        end: {
-          targetId: lineEndPoint.value.targetId,
-          pos: lineEndPoint.value.pos,
-        },
-      });
+      lines.push(line);
     }
     }
   }
   }
   lineStartPoint.value = null;
   lineStartPoint.value = null;
@@ -177,8 +201,17 @@ function initSvgSize() {
   wrapSize.height = wrapRef.value.offsetHeight - 1;
   wrapSize.height = wrapRef.value.offsetHeight - 1;
 }
 }
 
 
+function catchClick() {
+  curNodeId.value = 0;
+}
+
+function nodeClick(e) {
+  e.stopPropagation();
+}
+
 window.addEventListener("mouseup", handleMouseUp);
 window.addEventListener("mouseup", handleMouseUp);
 window.addEventListener("resize", initSvgSize);
 window.addEventListener("resize", initSvgSize);
+window.addEventListener("click", catchClick);
 
 
 onMounted(() => {
 onMounted(() => {
   initSvgSize();
   initSvgSize();
@@ -187,6 +220,7 @@ onMounted(() => {
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {
   window.removeEventListener("mouseup", handleMouseUp);
   window.removeEventListener("mouseup", handleMouseUp);
   window.removeEventListener("resize", initSvgSize);
   window.removeEventListener("resize", initSvgSize);
+  window.removeEventListener("click", catchClick);
 });
 });
 </script>
 </script>
 
 
@@ -212,5 +246,15 @@ onBeforeUnmount(() => {
     stroke-dasharray: 3 4;
     stroke-dasharray: 3 4;
     stroke-width: 2;
     stroke-width: 2;
   }
   }
+
+  .line {
+    stroke: #5ba8ff;
+    stroke-width: 2;
+  }
+
+  .trangle {
+    stroke: #5ba8ff;
+    fill: #5ba8ff;
+  }
 }
 }
 </style>
 </style>

+ 17 - 3
src/views/svg/utils/index.js

@@ -2,10 +2,20 @@ export function sticky(number, space = 5) {
   return Math.round(number / space) * space;
   return Math.round(number / space) * space;
 }
 }
 
 
-export function getPosFromEvent(e) {
+export function getPosFromEvent(e, offsetParent) {
+  const parentRect = offsetParent.getBoundingClientRect();
+  const curRect = e.target.getBoundingClientRect();
+  console.log(e.target);
+  // console.log(e.target.getBoundingClientRect());
+  // if (dom.classList.contains("svg-bar")) {
+  //   return {
+  //     x: e.offsetX + dom.offsetLeft - dom.offsetWidth / 2,
+  //     y: e.offsetY + dom.offsetTop,
+  //   };
+  // }
   return {
   return {
-    x: e.offsetX,
-    y: e.offsetY,
+    x: curRect.left - parentRect.left + e.offsetX,
+    y: curRect.top - parentRect.top + e.offsetY,
   };
   };
 }
 }
 
 
@@ -46,3 +56,7 @@ export function countPointByPos(pos, boxPos, boxSize) {
       };
       };
   }
   }
 }
 }
+
+export function twoObjIsEqual(obj1, obj2) {
+  return JSON.stringify(obj1) === JSON.stringify(obj2);
+}

+ 1 - 1
vue.config.js

@@ -32,7 +32,7 @@ module.exports = defineConfig({
               rootValue: 140, //设计稿宽度%10 比如 1920
               rootValue: 140, //设计稿宽度%10 比如 1920
               exclude: /(node_module)/, //默认false,可以(reg)利用正则表达式排除某些文件夹的方法,例如/(node_module|src)/
               exclude: /(node_module)/, //默认false,可以(reg)利用正则表达式排除某些文件夹的方法,例如/(node_module|src)/
               propList: ["*"], //是一个存储哪些将被转换的属性列表,这里设置为["*"]全部,假设需要仅对边框进行设置,可以写]['*','!border*']
               propList: ["*"], //是一个存储哪些将被转换的属性列表,这里设置为["*"]全部,假设需要仅对边框进行设置,可以写]['*','!border*']
-              selectorBlackList: [/svg/], //,那例如fs-xl类名,里面有关px的样式将不被转换,这里也支持正则写法。
+              selectorBlackList: [/svg/, /ori-/], //,那例如fs-xl类名,里面有关px的样式将不被转换,这里也支持正则写法。
               replace: true, //替换包含rems的规则。
               replace: true, //替换包含rems的规则。
               mediaQuery: false, //(布尔值)允许在媒体查询中转换px。
               mediaQuery: false, //(布尔值)允许在媒体查询中转换px。
               minPixelValue: 0, //设置要替换的最小像素值(3px会被转rem)。 默认 0
               minPixelValue: 0, //设置要替换的最小像素值(3px会被转rem)。 默认 0