Browse Source

feat: 添加辅助功能

bill 5 months ago
parent
commit
bdcccf67fe

+ 3 - 1
src/constant/index.ts

@@ -1,3 +1,5 @@
 
 
 export const DomMountId =  'dom-mount'
 export const DomMountId =  'dom-mount'
-export const DomOutMountId =  'dom-out-mount'
+export const DomOutMountId =  'dom-out-mount'
+export const rendererMap = new WeakMap<any, { unmounteds: (() => void)[] }>()
+export const rendererName = 'draw-renderer'

+ 22 - 2
src/core/components/arrow/arrow.vue

@@ -1,9 +1,10 @@
 <template>
 <template>
-  <TempArrow
+  <TempComponent
     :data="tData"
     :data="tData"
     :ref="(v: any) => shape = v?.shape"
     :ref="(v: any) => shape = v?.shape"
     :id="data.id"
     :id="data.id"
     @update:position="updatePosition"
     @update:position="updatePosition"
+    canEdit
     @update="emit('updateShape', { ...data })"
     @update="emit('updateShape', { ...data })"
   />
   />
   <PropertyUpdate
   <PropertyUpdate
@@ -18,13 +19,16 @@
 <script lang="ts" setup>
 <script lang="ts" setup>
 import { PropertyUpdate, Operate } from "../../propertys";
 import { PropertyUpdate, Operate } from "../../propertys";
 import { ArrowData, getMouseStyle, defaultStyle } from "./index.ts";
 import { ArrowData, getMouseStyle, defaultStyle } from "./index.ts";
-import TempArrow from "./temp-arrow.vue";
+import { TempComponent } from "./";
 import { useComponentStatus } from "@/core/hook/use-component.ts";
 import { useComponentStatus } from "@/core/hook/use-component.ts";
 import { Line } from "konva/lib/shapes/Line";
 import { Line } from "konva/lib/shapes/Line";
 import { Transform } from "konva/lib/Util";
 import { Transform } from "konva/lib/Util";
 import { Pos } from "@/utils/math.ts";
 import { Pos } from "@/utils/math.ts";
 import { Group } from "konva/lib/Group";
 import { Group } from "konva/lib/Group";
 import { flatPositions } from "@/utils/shared.ts";
 import { flatPositions } from "@/utils/shared.ts";
+import { useInteractiveDrawShapeAPI } from "@/core/hook/use-draw.ts";
+import { useStore } from "@/core/store/index.ts";
+import { EditPen } from "@element-plus/icons-vue";
 
 
 const props = defineProps<{ data: ArrowData }>();
 const props = defineProps<{ data: ArrowData }>();
 const emit = defineEmits<{
 const emit = defineEmits<{
@@ -74,4 +78,20 @@ const { shape, tData, operateMenus, describes, data } = useComponentStatus<
     // "zIndex",
     // "zIndex",
   ],
   ],
 });
 });
+
+const draw = useInteractiveDrawShapeAPI();
+const store = useStore();
+operateMenus.push({
+  label: "钢笔编辑",
+  icon: EditPen,
+  handler() {
+    draw.enterDrawShape("arrow", {
+      ...props.data,
+      getMessages: () => {
+        const line = store.getItemById(props.data.id) as ArrowData;
+        return line ? line.points : [];
+      },
+    });
+  },
+});
 </script>
 </script>

+ 11 - 50
src/core/components/arrow/temp-arrow.vue

@@ -18,38 +18,30 @@
       }"
       }"
     />
     />
 
 
-    <template v-if="status.hover && !status.active">
-      <Point
-        v-for="(p, ndx) in data.points"
-        :id="data.id + ndx"
-        :shapeId="data.id"
-        :position="p"
-        :size="Math.max(data.pointerLength + 4, data.strokeWidth + 6)"
-        :color="data.fill"
-        @update:position="(p) => emit('update:position', { ndx, val: p })"
-        @dragend="endHandler()"
-        @dragstart="startHandler(ndx)"
-      />
-    </template>
+    <EditPolygon
+      :data="{ ...data, stroke: data.fill, strokeWidth: data.strokeWidth + 5 }"
+      :shape="shape"
+      :addMode="addMode"
+      :canEdit="canEdit"
+      @update:position="(data) => emit('update:position', data)"
+      @update="emit('update')"
+      v-if="shape"
+    />
   </v-group>
   </v-group>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import Point from "../share/point.vue";
+import EditPolygon from "../share/edit-polygon.vue";
 import { ArrowData, defaultStyle, PointerPosition } from "./index.ts";
 import { ArrowData, defaultStyle, PointerPosition } from "./index.ts";
 import { DC } from "@/deconstruction.js";
 import { DC } from "@/deconstruction.js";
 import { computed, ref, watchEffect } from "vue";
 import { computed, ref, watchEffect } from "vue";
 import { flatPositions } from "@/utils/shared.ts";
 import { flatPositions } from "@/utils/shared.ts";
 import { Arrow } from "konva/lib/shapes/Arrow";
 import { Arrow } from "konva/lib/shapes/Arrow";
-import { useMouseShapeStatus } from "@/core/hook/use-mouse-status.ts";
-import { useCustomSnapInfos } from "@/core/hook/use-snap.ts";
-import { ComponentSnapInfo } from "../index.ts";
-import { generateSnapInfos } from "../util.ts";
 import { Pos } from "@/utils/math.ts";
 import { Pos } from "@/utils/math.ts";
 import { LineConfig } from "konva/lib/shapes/Line";
 import { LineConfig } from "konva/lib/shapes/Line";
 import { Group } from "konva/lib/Group";
 import { Group } from "konva/lib/Group";
 
 
-const props = defineProps<{ data: ArrowData; addMode?: boolean }>();
+const props = defineProps<{ data: ArrowData; canEdit?: boolean; addMode?: boolean }>();
 const emit = defineEmits<{
 const emit = defineEmits<{
   (e: "update:position", data: { ndx: number; val: Pos }): void;
   (e: "update:position", data: { ndx: number; val: Pos }): void;
   (e: "update"): void;
   (e: "update"): void;
@@ -95,37 +87,6 @@ const eConfig = computed(() => {
   };
   };
 });
 });
 
 
-const infos = useCustomSnapInfos();
-const addedInfos = [] as ComponentSnapInfo[];
-const clearInfos = () => {
-  addedInfos.forEach(infos.remove);
-};
-
-const startHandler = (ndx: number) => {
-  clearInfos();
-  const geos = [
-    props.data.points.slice(0, ndx),
-    props.data.points.slice(ndx + 1, props.data.points.length),
-  ];
-  if (ndx > 0 && ndx < props.data.points.length - 1) {
-    geos.push([props.data.points[ndx - 1], props.data.points[ndx + 1]]);
-  }
-  geos.forEach((geo) => {
-    const snapInfos = generateSnapInfos(geo, true, true, true);
-    snapInfos.forEach((item) => {
-      infos.add(item);
-      addedInfos.push(item);
-    });
-  });
-};
-
-const endHandler = () => {
-  clearInfos();
-  emit("update");
-};
-
-const status = useMouseShapeStatus(shape);
-
 defineExpose({
 defineExpose({
   get shape() {
   get shape() {
     return shape.value;
     return shape.value;

+ 1 - 0
src/core/components/line/index.ts

@@ -11,6 +11,7 @@ export const shapeName = "线段";
 export const defaultStyle = {
 export const defaultStyle = {
   stroke: themeMouseColors.theme,
   stroke: themeMouseColors.theme,
   strokeWidth: 5,
   strokeWidth: 5,
+  dash: [30, 0],
 };
 };
 
 
 export const addMode = "dots";
 export const addMode = "dots";

+ 1 - 0
src/core/components/line/line.vue

@@ -5,6 +5,7 @@
     :id="data.id"
     :id="data.id"
     @update:position="updatePosition"
     @update:position="updatePosition"
     @update="emit('updateShape', { ...data })"
     @update="emit('updateShape', { ...data })"
+    canEdit
   />
   />
   <PropertyUpdate
   <PropertyUpdate
     :describes="describes"
     :describes="describes"

+ 12 - 54
src/core/components/line/temp-line.vue

@@ -6,47 +6,35 @@
         ...data,
         ...data,
         zIndex: undefined,
         zIndex: undefined,
         points: flatPositions(data.points),
         points: flatPositions(data.points),
-        hitStrokeWidth: data.strokeWidth + 10,
         opacity: addMode ? 0.3 : data.opacity,
         opacity: addMode ? 0.3 : data.opacity,
         hitFunc,
         hitFunc,
       }"
       }"
-    >
-    </v-line>
-
-    <template v-if="status.hover && !status.active">
-      <Point
-        v-for="(p, ndx) in data.points"
-        :id="data.id + ndx"
-        :shapeId="data.id"
-        :position="p"
-        :size="data.strokeWidth + 6"
-        :color="data.stroke"
-        @update:position="(p) => emit('update:position', { ndx, val: p })"
-        @dragend="endHandler()"
-        @dragstart="startHandler(ndx)"
-      />
-    </template>
+    />
+    <EditPolygon
+      :data="data"
+      :shape="shape"
+      :addMode="addMode"
+      :canEdit="canEdit"
+      @update:position="(data) => emit('update:position', data)"
+      @update="emit('update')"
+      v-if="shape"
+    />
   </v-group>
   </v-group>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import Point from "../share/point.vue";
+import EditPolygon from "../share/edit-polygon.vue";
 import { defaultStyle, LineData } from "./index.ts";
 import { defaultStyle, LineData } from "./index.ts";
 import { flatPositions } from "@/utils/shared.ts";
 import { flatPositions } from "@/utils/shared.ts";
-import { computed, ref, watchEffect } from "vue";
+import { computed, ref } from "vue";
 import { DC } from "@/deconstruction.js";
 import { DC } from "@/deconstruction.js";
 import { Line, LineConfig } from "konva/lib/shapes/Line";
 import { Line, LineConfig } from "konva/lib/shapes/Line";
 import { Pos } from "@/utils/math.ts";
 import { Pos } from "@/utils/math.ts";
-import { useCustomSnapInfos } from "@/core/hook/use-snap.ts";
-import { generateSnapInfos } from "../util.ts";
-import { ComponentSnapInfo } from "../index.ts";
-import { useMouseShapeStatus } from "@/core/hook/use-mouse-status.ts";
 
 
 const props = defineProps<{
 const props = defineProps<{
   data: LineData;
   data: LineData;
   addMode?: boolean;
   addMode?: boolean;
   canEdit?: boolean;
   canEdit?: boolean;
-  shapeId?: string;
 }>();
 }>();
 const emit = defineEmits<{
 const emit = defineEmits<{
   (e: "update:position", data: { ndx: number; val: Pos }): void;
   (e: "update:position", data: { ndx: number; val: Pos }): void;
@@ -65,37 +53,7 @@ const hitFunc: LineConfig["hitFunc"] = (con, shape) => {
   con.fillStrokeShape(shape);
   con.fillStrokeShape(shape);
 };
 };
 
 
-const infos = useCustomSnapInfos();
-const addedInfos = [] as ComponentSnapInfo[];
-const clearInfos = () => {
-  addedInfos.forEach(infos.remove);
-};
-
-const startHandler = (ndx: number) => {
-  clearInfos();
-  const geos = [
-    props.data.points.slice(0, ndx),
-    props.data.points.slice(ndx + 1, props.data.points.length),
-  ];
-  if (ndx > 0 && ndx < props.data.points.length - 1) {
-    geos.push([props.data.points[ndx - 1], props.data.points[ndx + 1]]);
-  }
-  geos.forEach((geo) => {
-    const snapInfos = generateSnapInfos(geo, true, true, true);
-    snapInfos.forEach((item) => {
-      infos.add(item);
-      addedInfos.push(item);
-    });
-  });
-};
-
-const endHandler = () => {
-  clearInfos();
-  emit("update");
-};
-
 const shape = ref<DC<Line>>();
 const shape = ref<DC<Line>>();
-const status = useMouseShapeStatus(shape);
 defineExpose({
 defineExpose({
   get shape() {
   get shape() {
     return shape.value;
     return shape.value;

+ 1 - 0
src/core/components/polygon/index.ts

@@ -11,6 +11,7 @@ export const shapeName = "多边形";
 export const defaultStyle = {
 export const defaultStyle = {
   stroke: themeMouseColors.theme,
   stroke: themeMouseColors.theme,
   strokeWidth: 5,
   strokeWidth: 5,
+  dash: [30, 0],
 };
 };
 
 
 export const getMouseStyle = (data: PolygonData) => {
 export const getMouseStyle = (data: PolygonData) => {

+ 1 - 0
src/core/components/polygon/polygon.vue

@@ -4,6 +4,7 @@
     :ref="(v: any) => shape = v?.shape"
     :ref="(v: any) => shape = v?.shape"
     :id="data.id"
     :id="data.id"
     @update:position="updatePosition"
     @update:position="updatePosition"
+    can-edit
     @update="emit('updateShape', { ...data })"
     @update="emit('updateShape', { ...data })"
   />
   />
   <PropertyUpdate
   <PropertyUpdate

+ 18 - 50
src/core/components/polygon/temp-polygon.vue

@@ -11,35 +11,32 @@
       }"
       }"
     >
     >
     </v-line>
     </v-line>
-    <template v-if="status.hover">
-      <Point
-        v-for="(p, ndx) in data.points"
-        :id="data.id + ndx"
-        :shapeId="data.id"
-        :position="p"
-        :size="data.strokeWidth + 6"
-        :color="data.stroke"
-        @update:position="(p) => emit('update:position', { ndx, val: p })"
-        @dragend="endHandler()"
-        @dragstart="startHandler(ndx)"
-      />
-    </template>
+    <EditPolygon
+      :data="data"
+      :shape="shape"
+      :addMode="addMode"
+      closed
+      :canEdit="canEdit"
+      @update:position="(data) => emit('update:position', data)"
+      @update="emit('update')"
+      v-if="shape"
+    />
+    <!-- status.hover -->
+    <SizeLine :data="data"  closed />
   </v-group>
   </v-group>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import Point from "../share/point.vue";
+import EditPolygon from "../share/edit-polygon.vue";
+import SizeLine from "../share/size-line.vue";
 import { defaultStyle, PolygonData } from "./index.ts";
 import { defaultStyle, PolygonData } from "./index.ts";
-import { flatPositions, rangMod } from "@/utils/shared.ts";
+import { flatPositions } from "@/utils/shared.ts";
 import { computed, ref } from "vue";
 import { computed, ref } from "vue";
 import { DC } from "@/deconstruction.js";
 import { DC } from "@/deconstruction.js";
 import { Line } from "konva/lib/shapes/Line";
 import { Line } from "konva/lib/shapes/Line";
 import { Pos } from "@/utils/math.ts";
 import { Pos } from "@/utils/math.ts";
-import { useCustomSnapInfos } from "@/core/hook/use-snap.ts";
-import { ComponentSnapInfo } from "../index.ts";
-import { generateSnapInfos } from "../util.ts";
 import { useMouseShapeStatus } from "@/core/hook/use-mouse-status.ts";
 import { useMouseShapeStatus } from "@/core/hook/use-mouse-status.ts";
-const props = defineProps<{ data: PolygonData; addMode?: boolean }>();
+const props = defineProps<{ data: PolygonData; canEdit?: boolean; addMode?: boolean }>();
 const emit = defineEmits<{
 const emit = defineEmits<{
   (e: "update:position", data: { ndx: number; val: Pos }): void;
   (e: "update:position", data: { ndx: number; val: Pos }): void;
   (e: "update"): void;
   (e: "update"): void;
@@ -47,40 +44,11 @@ const emit = defineEmits<{
 
 
 const data = computed(() => ({ ...defaultStyle, ...props.data }));
 const data = computed(() => ({ ...defaultStyle, ...props.data }));
 const shape = ref<DC<Line>>();
 const shape = ref<DC<Line>>();
-const status = useMouseShapeStatus(shape);
+const status = useMouseShapeStatus(computed(() => shape.value));
+
 defineExpose({
 defineExpose({
   get shape() {
   get shape() {
     return shape.value;
     return shape.value;
   },
   },
 });
 });
-
-const infos = useCustomSnapInfos();
-const addedInfos = [] as ComponentSnapInfo[];
-const clearInfos = () => {
-  addedInfos.forEach(infos.remove);
-};
-
-const startHandler = (ndx: number) => {
-  clearInfos();
-  const len = props.data.points.length;
-  const geos = [props.data.points.slice(0, ndx), props.data.points.slice(ndx + 1, len)];
-  if (len > 2) {
-    geos.push([
-      props.data.points[rangMod(ndx - 1, len)],
-      props.data.points[rangMod(ndx + 1, len)],
-    ]);
-  }
-  geos.forEach((geo) => {
-    const snapInfos = generateSnapInfos(geo, true, true, true);
-    snapInfos.forEach((item) => {
-      infos.add(item);
-      addedInfos.push(item);
-    });
-  });
-};
-
-const endHandler = () => {
-  clearInfos();
-  emit("update");
-};
 </script>
 </script>

+ 109 - 0
src/core/components/share/edit-line.vue

@@ -0,0 +1,109 @@
+<template>
+  <v-line
+    ref="line"
+    :config="{
+      stroke: 'red',
+      strokeWidth: data.strokeWidth + 20,
+      opacity: 0,
+      points: flatPositions(points),
+      hitStrokeWidth: data.strokeWidth + 10,
+    }"
+  />
+</template>
+
+<script lang="ts" setup>
+import { copy, flatPositions } from "@/utils/shared";
+import { LineData } from "../line";
+import { computed, ref, watch } from "vue";
+import { DC } from "@/deconstruction";
+import { Line } from "konva/lib/shapes/Line";
+import { useShapeDrag } from "@/core/hook/use-transformer";
+import { useShapeIsHover } from "@/core/hook/use-mouse-status";
+import { useCursor } from "@/core/hook/use-global-vars";
+import { Pos } from "@/utils/math";
+import { useCustomSnapInfos, useGlobalSnapInfos, useSnap } from "@/core/hook/use-snap";
+import { ComponentSnapInfo } from "..";
+import { generateSnapInfos } from "../util";
+
+type LData = Required<Pick<LineData, "strokeWidth">>;
+const props = defineProps<{
+  data: LData;
+  points: Pos[];
+  id: string;
+  ndx: number;
+  closed?: boolean;
+}>();
+const emit = defineEmits<{
+  (e: "update:line", data: Pos[]): void;
+  (e: "dragend"): void;
+  (e: "dragstart"): void;
+}>();
+
+const line = ref<DC<Line>>();
+const offset = useShapeDrag(line);
+const [isHover] = useShapeIsHover(line);
+const cursor = useCursor();
+watch(isHover, (hover, _, onCleanup) => {
+  if (hover) {
+    onCleanup(cursor.push("move"));
+  }
+});
+const points = computed(() => {
+  return [props.points[props.ndx], props.points[(props.ndx + 1) % props.points.length]];
+});
+
+const infos = useCustomSnapInfos();
+const addedInfos = [] as ComponentSnapInfo[];
+const clearInfos = () => {
+  addedInfos.forEach(infos.remove);
+};
+const dragStartHandler = () => {
+  clearInfos();
+  const ndx = props.ndx;
+  const geos = [
+    props.points.slice(Number(ndx === props.points.length - 1), ndx),
+    props.points.slice(ndx + 2, props.points.length),
+  ];
+  if (ndx > 0 && ndx < props.points.length - 2) {
+    geos.push([props.points[ndx - 1], props.points[ndx + 2]]);
+  }
+  geos.forEach((geo) => {
+    const snapInfos = generateSnapInfos(geo, true, true, true);
+    snapInfos.forEach((item) => {
+      infos.add(item);
+      addedInfos.push(item);
+    });
+  });
+};
+const snapInfos = useGlobalSnapInfos();
+const refSnapInfos = computed(() => {
+  if (!props.id) {
+    return snapInfos.value;
+  } else {
+    return snapInfos.value.filter((p) => !("id" in p) || p.id !== props.id);
+  }
+});
+const snap = useSnap(refSnapInfos);
+
+let init: Pos[];
+watch(offset, (offset, oldOffsert) => {
+  snap.clear();
+  if (!oldOffsert) {
+    emit("dragstart");
+    init = copy(points.value);
+    dragStartHandler();
+  }
+  if (offset) {
+    const current = init.map((p) => ({
+      x: p.x + offset.x,
+      y: p.y + offset.y,
+    }));
+    const refSnapInfos = generateSnapInfos(current, true, true);
+    const transform = snap.move(refSnapInfos);
+    emit("update:line", transform ? current.map((p) => transform.point(p)) : current);
+  } else {
+    clearInfos();
+    emit("dragend");
+  }
+});
+</script>

+ 62 - 9
src/core/components/share/point.vue

@@ -1,5 +1,8 @@
 <template>
 <template>
-  <v-circle :config="{ ...style, ...position }" ref="circle" />
+  <v-circle
+    :config="{ ...style, ...position, hitStrokeWidth: style.strokeWidth + 10 }"
+    ref="circle"
+  />
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
@@ -10,18 +13,21 @@ import { DC } from "@/deconstruction";
 import { Circle } from "konva/lib/shapes/Circle";
 import { Circle } from "konva/lib/shapes/Circle";
 import { useShapeDrag } from "@/core/hook/use-transformer.ts";
 import { useShapeDrag } from "@/core/hook/use-transformer.ts";
 import { getMouseColors } from "@/utils/colors";
 import { getMouseColors } from "@/utils/colors";
-import { useGlobalSnapInfos, useSnap } from "@/core/hook/use-snap";
+import { useCustomSnapInfos, useGlobalSnapInfos, useSnap } from "@/core/hook/use-snap";
 import { generateSnapInfos } from "../util";
 import { generateSnapInfos } from "../util";
 import { ComponentSnapInfo } from "..";
 import { ComponentSnapInfo } from "..";
 import { useShapeIsHover } from "@/core/hook/use-mouse-status";
 import { useShapeIsHover } from "@/core/hook/use-mouse-status";
 import { useCursor } from "@/core/hook/use-global-vars";
 import { useCursor } from "@/core/hook/use-global-vars";
+import { rangMod } from "@/utils/shared";
 
 
 const props = defineProps<{
 const props = defineProps<{
-  position: Pos;
-  id?: any;
+  points: Pos[];
+  ndx: number;
+  id: string;
   color?: string;
   color?: string;
   size?: number;
   size?: number;
-  shapeId?: string;
+  disable?: boolean;
+  closed?: boolean;
   getSelfSnapInfos?: (point: Pos) => ComponentSnapInfo[];
   getSelfSnapInfos?: (point: Pos) => ComponentSnapInfo[];
 }>();
 }>();
 const emit = defineEmits<{
 const emit = defineEmits<{
@@ -30,6 +36,8 @@ const emit = defineEmits<{
   (e: "dragstart"): void;
   (e: "dragstart"): void;
 }>();
 }>();
 
 
+const position = computed(() => props.points[props.ndx]);
+
 const style = computed(() => {
 const style = computed(() => {
   const color = getMouseColors(props.color || themeColor);
   const color = getMouseColors(props.color || themeColor);
   const size = props.size || 5;
   const size = props.size || 5;
@@ -38,21 +46,50 @@ const style = computed(() => {
     fill: color.disable,
     fill: color.disable,
     stroke: color.press,
     stroke: color.press,
     strokeWidth: size / 4,
     strokeWidth: size / 4,
+    opacity: props.disable ? 0.5 : 1,
   };
   };
 });
 });
 
 
+const infos = useCustomSnapInfos();
+const addedInfos = [] as ComponentSnapInfo[];
+const clearInfos = () => {
+  addedInfos.forEach(infos.remove);
+};
+
+const startHandler = () => {
+  clearInfos();
+  const ndx = props.ndx;
+  const geos = [
+    props.points.slice(0, ndx),
+    props.points.slice(ndx + 1, props.points.length),
+  ];
+  if (props.closed || (ndx > 0 && ndx < props.points.length - 1)) {
+    const prev = rangMod(ndx - 1, props.points.length);
+    const next = rangMod(ndx + 1, props.points.length);
+    geos.push([props.points[prev], props.points[next]]);
+  }
+  geos.forEach((geo) => {
+    const snapInfos = generateSnapInfos(geo, true, true, true);
+    snapInfos.forEach((item) => {
+      infos.add(item);
+      addedInfos.push(item);
+    });
+  });
+};
+
 const snapInfos = useGlobalSnapInfos();
 const snapInfos = useGlobalSnapInfos();
 const refSnapInfos = computed(() => {
 const refSnapInfos = computed(() => {
-  if (!props.shapeId) {
+  if (!props.id) {
     return snapInfos.value;
     return snapInfos.value;
   } else {
   } else {
-    return snapInfos.value.filter((p) => !("id" in p) || p.id !== props.shapeId);
+    return snapInfos.value.filter((p) => !("id" in p) || p.id !== props.id);
   }
   }
 });
 });
 const snap = useSnap(refSnapInfos);
 const snap = useSnap(refSnapInfos);
 const circle = ref<DC<Circle>>();
 const circle = ref<DC<Circle>>();
 const offset = useShapeDrag(circle);
 const offset = useShapeDrag(circle);
-const [isHover] = useShapeIsHover(circle);
+const hResult = useShapeIsHover(circle);
+const isHover = hResult[0];
 const cursor = useCursor();
 const cursor = useCursor();
 watch(isHover, (hover, _, onCleanup) => {
 watch(isHover, (hover, _, onCleanup) => {
   if (hover) {
   if (hover) {
@@ -64,7 +101,8 @@ let init: Pos;
 watch(offset, (offset, oldOffsert) => {
 watch(offset, (offset, oldOffsert) => {
   snap.clear();
   snap.clear();
   if (!oldOffsert) {
   if (!oldOffsert) {
-    init = { ...props.position };
+    init = { ...position.value };
+    startHandler();
     emit("dragstart");
     emit("dragstart");
   }
   }
   if (offset) {
   if (offset) {
@@ -79,6 +117,21 @@ watch(offset, (offset, oldOffsert) => {
     emit("update:position", transform ? transform.point(point) : point);
     emit("update:position", transform ? transform.point(point) : point);
   } else {
   } else {
     emit("dragend");
     emit("dragend");
+    clearInfos();
   }
   }
 });
 });
+
+watch(
+  () => props.disable,
+  (disable) => {
+    if (disable) {
+      offset.pause();
+      hResult.pause();
+    } else {
+      offset.resume();
+      hResult.resume();
+    }
+  },
+  { immediate: true }
+);
 </script>
 </script>

+ 58 - 0
src/core/components/share/edit-polygon.vue

@@ -0,0 +1,58 @@
+<template>
+  <template v-if="status.hover && canEdit">
+    <EditLine
+      :data="data"
+      :points="data.points"
+      :closed="closed"
+      :id="data.id"
+      :ndx="ndx"
+      v-for="(_, ndx) in closed ? data.points.length : data.points.length - 1"
+      @update:line="
+        (p) => {
+          emit('update:position', { ndx, val: p[0] });
+          emit('update:position', { ndx: (ndx + 1) % data.points.length, val: p[1] });
+        }
+      "
+      @dragend="emit('update')"
+    />
+  </template>
+
+  <template v-if="status.hover || status.active || addMode">
+    <Point
+      v-for="(_, ndx) in data.points"
+      :size="data.strokeWidth + 6"
+      :points="data.points"
+      :ndx="ndx"
+      :closed="closed"
+      :id="data.id"
+      :disable="addMode"
+      :color="data.stroke"
+      @update:position="(p) => emit('update:position', { ndx, val: p })"
+      @dragend="emit('update')"
+    />
+  </template>
+</template>
+
+<script lang="ts" setup>
+import Point from "../share/edit-point.vue";
+import EditLine from "../share/edit-line.vue";
+import { LineData } from "../line";
+import { DC, EntityShape } from "@/deconstruction.js";
+import { Pos } from "@/utils/math.ts";
+import { useMouseShapeStatus } from "@/core/hook/use-mouse-status";
+import { computed } from "vue";
+
+const props = defineProps<{
+  data: Required<Pick<LineData, "id" | "points" | "stroke" | "strokeWidth">>;
+  addMode?: boolean;
+  canEdit?: boolean;
+  closed?: boolean;
+  shape: DC<EntityShape>;
+}>();
+const emit = defineEmits<{
+  (e: "update:position", data: { ndx: number; val: Pos }): void;
+  (e: "update"): void;
+}>();
+
+const status = useMouseShapeStatus(computed(() => props.shape));
+</script>

+ 95 - 0
src/core/components/share/size-line.vue

@@ -0,0 +1,95 @@
+<template>
+  <v-group :config="{ listening: false }">
+    <template v-for="line in lines">
+      <v-line
+        v-for="p in line.points"
+        :config="{
+          stroke: stroke,
+          strokeWidth: strokeWidth,
+          points: flatPositions(p),
+        }"
+      />
+      <v-textPath
+        :config="{
+          fill: stroke,
+          align: 'center',
+          fontSize,
+          textBaseline: 'middle',
+          ...line.textConfig,
+        }"
+      />
+    </template>
+  </v-group>
+</template>
+
+<script lang="ts" setup>
+import { computed } from "vue";
+import { LineData } from "../line";
+import {
+  getPolygonDirection,
+  getVectorLine,
+  lineLen,
+  lineVector,
+  lineVerticalVector,
+  Pos,
+  vector,
+} from "@/utils/math";
+import { flatPositions } from "@/utils/shared";
+import { useProportion } from "@/core/hook/use-proportion";
+import { TextPathConfig } from "konva/lib/shapes/TextPath";
+
+const props = withDefaults(
+  defineProps<{
+    data: Required<Pick<LineData, "points" | "strokeWidth">>;
+    stroke?: string;
+    strokeWidth?: number;
+    closed?: boolean;
+  }>(),
+  { stroke: "#999", strokeWidth: 1 }
+);
+
+const fontSize = computed(() => 10 + props.strokeWidth * 2);
+const len = computed(() =>
+  props.closed ? props.data.points.length : props.data.points.length - 1
+);
+const getLine = (ndx: number) =>
+  [
+    props.data.points[ndx % props.data.points.length],
+    props.data.points[(ndx + 1) % props.data.points.length],
+  ] as [Pos, Pos];
+const isClockwise = computed(() => getPolygonDirection(props.data.points) <= 0);
+
+const proportion = useProportion();
+const margin = computed(() => -(props.data.strokeWidth + 10));
+const lines = computed(() => {
+  const lines: { points: Pos[][]; textConfig: TextPathConfig }[] = [];
+  for (let i = 0; i < len.value; i++) {
+    const line = getLine(i);
+    const lineOffset = isClockwise.value ? margin.value : -margin.value;
+    const vv = lineVerticalVector(line);
+    const offset = vv.multiplyScalar(lineOffset);
+    const p = [vector(line[0]).add(offset), vector(line[1]).add(offset)];
+    const text = proportion.transform(lineLen(...line));
+    const w = fontSize.value * text.length;
+    const lw = lineLen(p[0], p[1]);
+    if (w > lw) {
+      continue;
+    }
+    const lv = lineVector(p);
+    const line1 = getVectorLine(lv, p[0], (lw - w) / 2);
+    const [_, start2] = getVectorLine(lv, line1[1], w);
+    const line2 = getVectorLine(lv, start2, (lw - w) / 2);
+
+    lines.push({
+      points: [line1, line2],
+      textConfig: {
+        text: proportion.transform(lineLen(...line)),
+        x: 0,
+        y: 0,
+        data: `M${p[0].x},${p[0].y} L${p[1].x},${p[1].y}`,
+      },
+    });
+  }
+  return lines;
+});
+</script>

+ 0 - 2
src/core/helper/split-line.vue

@@ -21,7 +21,6 @@ import { TextConfig } from "konva/lib/shapes/Text";
 import { Transform } from "konva/lib/Util";
 import { Transform } from "konva/lib/Util";
 import { normalPadding } from "@/utils/bound";
 import { normalPadding } from "@/utils/bound";
 import { useConfig } from "../hook/use-config";
 import { useConfig } from "../hook/use-config";
-import { useStore } from "../store";
 import { useProportion } from "../hook/use-proportion";
 import { useProportion } from "../hook/use-proportion";
 
 
 const style = {
 const style = {
@@ -247,7 +246,6 @@ const axissInfo = computed(() => {
       });
       });
     }
     }
   }
   }
-
   return infos;
   return infos;
 });
 });
 </script>
 </script>

+ 12 - 7
src/core/hook/use-draw.ts

@@ -81,7 +81,7 @@ export const useInteractiveDrawShapeAPI = installGlobalVar(() => {
   return {
   return {
     delShape(id: string) {
     delShape(id: string) {
       const type = store.getType(id);
       const type = store.getType(id);
-      console.log(type, id)
+      console.log(type, id);
       type && store.delItem(type, id);
       type && store.delItem(type, id);
     },
     },
     addShape: <T extends ShapeType>(
     addShape: <T extends ShapeType>(
@@ -90,7 +90,7 @@ export const useInteractiveDrawShapeAPI = installGlobalVar(() => {
       data?: PayData<T>,
       data?: PayData<T>,
       pixel = false
       pixel = false
     ) => {
     ) => {
-      console.log(can.dragMode)
+      console.log(can.dragMode);
       if (!can.drawMode) {
       if (!can.drawMode) {
         throw "当前状态不允许添加";
         throw "当前状态不允许添加";
       }
       }
@@ -137,7 +137,6 @@ export const useInteractiveDrawShapeAPI = installGlobalVar(() => {
     },
     },
     quitDrawShape: () => {
     quitDrawShape: () => {
       leave();
       leave();
-      console.error("quit");
       interactiveProps.value = void 0;
       interactiveProps.value = void 0;
     },
     },
   };
   };
@@ -171,6 +170,8 @@ export const useDrawRunning = (shapeType?: ShapeType) => {
 };
 };
 
 
 const usePointBeforeHandler = (enableTransform = false, enableSnap = false) => {
 const usePointBeforeHandler = (enableTransform = false, enableSnap = false) => {
+  const downKeys = useDownKeys();
+  const free = computed(() => downKeys.has("Control"));
   const conversionPosition = useConversionPosition(enableTransform);
   const conversionPosition = useConversionPosition(enableTransform);
   const snap = enableSnap && useSnap();
   const snap = enableSnap && useSnap();
   const infos = useCustomSnapInfos();
   const infos = useCustomSnapInfos();
@@ -178,6 +179,9 @@ const usePointBeforeHandler = (enableTransform = false, enableSnap = false) => {
 
 
   return {
   return {
     transform: (p: SnapPoint, geo = [p], geoNeedConversion = true) => {
     transform: (p: SnapPoint, geo = [p], geoNeedConversion = true) => {
+      if (free.value) {
+        return p
+      }
       snap && snap.clear();
       snap && snap.clear();
       if (geoNeedConversion) {
       if (geoNeedConversion) {
         geo = geo.map(conversionPosition);
         geo = geo.map(conversionPosition);
@@ -231,7 +235,7 @@ const useInteractiveDrawTemp = <T extends ShapeType>({
   const processors = useStoreRenderProcessors();
   const processors = useStoreRenderProcessors();
   const viewTransform = useViewerTransform();
   const viewTransform = useViewerTransform();
   const conversionPosition = useConversionPosition(true);
   const conversionPosition = useConversionPosition(true);
-  const history = useHistory()
+  const history = useHistory();
   const store = useStore();
   const store = useStore();
   const processorIds = processors.register(() => DrawShape);
   const processorIds = processors.register(() => DrawShape);
   const clear = () => {
   const clear = () => {
@@ -260,7 +264,7 @@ const useInteractiveDrawTemp = <T extends ShapeType>({
             consumed: ia.consumedMessage,
             consumed: ia.consumedMessage,
             action: MessageAction.update,
             action: MessageAction.update,
           },
           },
-          data: items[0],
+          data: copy(items[0]),
           history,
           history,
           viewTransform: viewTransform.value,
           viewTransform: viewTransform.value,
           store,
           store,
@@ -298,7 +302,7 @@ const useInteractiveDrawTemp = <T extends ShapeType>({
       // 监听位置变化
       // 监听位置变化
       watch(
       watch(
         cur,
         cur,
-        () =>
+        () => {
           obj.interactiveFixData({
           obj.interactiveFixData({
             info: {
             info: {
               cur,
               cur,
@@ -309,7 +313,8 @@ const useInteractiveDrawTemp = <T extends ShapeType>({
             history,
             history,
             viewTransform: viewTransform.value,
             viewTransform: viewTransform.value,
             store,
             store,
-          } as any),
+          } as any);
+        },
         { deep: true }
         { deep: true }
       ),
       ),
       // 监听是否消费完毕
       // 监听是否消费完毕

+ 25 - 30
src/core/hook/use-global-vars.ts

@@ -4,7 +4,6 @@ import {
   computed,
   computed,
   getCurrentInstance,
   getCurrentInstance,
   nextTick,
   nextTick,
-  onUnmounted,
   reactive,
   reactive,
   Ref,
   Ref,
   ref,
   ref,
@@ -23,48 +22,44 @@ import { Pos } from "@/utils/math.ts";
 import { listener } from "@/utils/event.ts";
 import { listener } from "@/utils/event.ts";
 import { mergeFuns, onlyId } from "@/utils/shared.ts";
 import { mergeFuns, onlyId } from "@/utils/shared.ts";
 import { StoreData } from "../store/store.ts";
 import { StoreData } from "../store/store.ts";
+import { rendererMap, rendererName } from "@/constant/index.ts";
+
+export const useRendererInstance = () => {
+  let instance = getCurrentInstance()!
+  while (instance.type.name !== rendererName) {
+    if (instance.parent) {
+      instance = instance.parent
+    } else {
+      throw '未发现渲染实例'
+    }
+  }
+  return instance;
+}
 
 
 export const installGlobalVar = <T>(
 export const installGlobalVar = <T>(
   create: () => { var: T; onDestroy: () => void } | T,
   create: () => { var: T; onDestroy: () => void } | T,
   key = Symbol("globalVar"),
   key = Symbol("globalVar"),
-  noRefDel = true
 ) => {
 ) => {
-  let initialed = false;
-  let refCount = 0;
-  let onDestroy: (() => void) | null = null;
-
   const useGlobalVar = (): T => {
   const useGlobalVar = (): T => {
-    const instance = getCurrentInstance() as any;
-    const ctx = instance.appContext;
-    if (!initialed) {
+    const instance = useRendererInstance() as any
+    const { unmounteds } = rendererMap.get(instance)!
+    if (!(key in instance)) {
       let val = create() as any;
       let val = create() as any;
       if (typeof val === "object" && "var" in val && "onDestroy" in val) {
       if (typeof val === "object" && "var" in val && "onDestroy" in val) {
-        onDestroy = val.onDestory;
+        val.onDestory && unmounteds.push(val.onDestory)
+        if (import.meta.env.DEV) {
+          unmounteds.push(() => {
+            console.log('销毁变量', key)
+          })
+        }
         val = val.var;
         val = val.var;
       }
       }
-      ctx[key] = val;
-      initialed = true;
+      instance[key] = val
     }
     }
-    return ctx[key];
+    return instance[key];
   };
   };
 
 
-  return noRefDel
-    ? () => {
-        const instance = getCurrentInstance() as any;
-        const ctx = instance.appContext;
-        ++refCount;
-        onUnmounted(() => {
-          if (--refCount === 0 && noRefDel) {
-            initialed = false;
-            delete ctx[key];
-            console.log("销毁", key);
-            onDestroy && onDestroy();
-            onDestroy = null;
-          }
-        });
-        return useGlobalVar();
-      }
-    : useGlobalVar;
+  return useGlobalVar
 };
 };
 
 
 export type InstanceProps = {
 export type InstanceProps = {

+ 4 - 3
src/core/hook/use-mouse-status.ts

@@ -22,6 +22,7 @@ import { useAniamtion } from "./use-animation.ts";
 import { KonvaEventObject } from "konva/lib/Node";
 import { KonvaEventObject } from "konva/lib/Node";
 import { useStore } from "../store/index.ts";
 import { useStore } from "../store/index.ts";
 import { Group } from "konva/lib/Group";
 import { Group } from "konva/lib/Group";
+import { usePause } from "./use-pause.ts";
 
 
 const stageHoverMap = new WeakMap<
 const stageHoverMap = new WeakMap<
   Stage,
   Stage,
@@ -78,7 +79,7 @@ export const useShapeIsHover = (shape: Ref<DC<EntityShape> | undefined>) => {
   const stop = watch(
   const stop = watch(
     () => ({ stage: stage.value?.getNode(), shape: shape.value?.getNode() }),
     () => ({ stage: stage.value?.getNode(), shape: shape.value?.getNode() }),
     ({ stage, shape }, _, onCleanup) => {
     ({ stage, shape }, _, onCleanup) => {
-      if (!stage || !shape) {
+      if (!stage || !shape || result.isPause) {
         isHover.value = false;
         isHover.value = false;
         return;
         return;
       }
       }
@@ -94,8 +95,8 @@ export const useShapeIsHover = (shape: Ref<DC<EntityShape> | undefined>) => {
     },
     },
     { immediate: true }
     { immediate: true }
   );
   );
-
-  return [isHover, stop] as const;
+  const result = usePause([isHover, stop] as const);
+  return result
 };
 };
 
 
 export const useShapeIsTransformerInner = () => {
 export const useShapeIsTransformerInner = () => {

+ 2 - 3
src/core/hook/use-snap.ts

@@ -10,7 +10,6 @@ import {
   createLine,
   createLine,
   eqNGDire,
   eqNGDire,
   eqPoint,
   eqPoint,
-  isVertical,
   lineIntersection,
   lineIntersection,
   lineLen,
   lineLen,
   linePointLen,
   linePointLen,
@@ -110,7 +109,7 @@ export const useCustomSnapInfos = installGlobalVar(() => {
       }
       }
     },
     },
   };
   };
-});
+}, Symbol('customSnapInfos'));
 
 
 export const useGlobalSnapInfos = installGlobalVar(() => {
 export const useGlobalSnapInfos = installGlobalVar(() => {
   const storeInfos = useStoreSnapInfos();
   const storeInfos = useStoreSnapInfos();
@@ -128,7 +127,7 @@ export const useSnapConfig = () => {
   const unitTransform = useCacheUnitTransform();
   const unitTransform = useCacheUnitTransform();
   return {
   return {
     get snapOffset() {
     get snapOffset() {
-      return unitTransform.getPixel(5);
+      return unitTransform.getPixel(10);
     },
     },
   };
   };
 };
 };

+ 5 - 3
src/core/hook/use-transformer.ts

@@ -26,6 +26,7 @@ import { Text } from "konva/lib/shapes/Text";
 import { Group } from "konva/lib/Group";
 import { Group } from "konva/lib/Group";
 import { BaseItem } from "../components/util.ts";
 import { BaseItem } from "../components/util.ts";
 import { useGetComponentData } from "./use-component.ts";
 import { useGetComponentData } from "./use-component.ts";
+import { usePause } from "./use-pause.ts";
 
 
 export type TransformerExtends = Transformer & {
 export type TransformerExtends = Transformer & {
   queueShapes: Ref<EntityShape[]>;
   queueShapes: Ref<EntityShape[]>;
@@ -231,16 +232,16 @@ export const useShapeDrag = (shape: Ref<DC<EntityShape> | undefined>) => {
     ]);
     ]);
   };
   };
 
 
+  const result = usePause(offset)
   watch(
   watch(
-    
     () =>
     () =>
       (can.editMode || mode.include(Mode.update)) &&
       (can.editMode || mode.include(Mode.update)) &&
-      (status.value.active || status.value.hover),
+      (status.value.active || status.value.hover) && !result.isPause,
     (canEdit, _, onCleanup) => {
     (canEdit, _, onCleanup) => {
       canEdit && onCleanup(init(shape.value!.getNode()));
       canEdit && onCleanup(init(shape.value!.getNode()));
     }
     }
   );
   );
-  return offset;
+  return result;
 };
 };
 
 
 type Rep<T> = {
 type Rep<T> = {
@@ -640,6 +641,7 @@ export const useMatCompTransformer = <T extends BaseItem & { mat: number[] }>(
     },
     },
     handler(data, mat) {
     handler(data, mat) {
       data.mat = mat.m;
       data.mat = mat.m;
+      return true
     },
     },
     getRepShape: cloneRepShape,
     getRepShape: cloneRepShape,
     callback,
     callback,

+ 0 - 2
src/core/renderer/draw-group.vue

@@ -12,11 +12,9 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { ShapeType, components } from "../components";
 import { ShapeType, components } from "../components";
 import { useInteractiveAdd } from "../hook/use-draw.ts";
 import { useInteractiveAdd } from "../hook/use-draw.ts";
-import { useStore } from "../store/index.ts";
 
 
 const props = defineProps<{ type: ShapeType }>();
 const props = defineProps<{ type: ShapeType }>();
 const tempItems = useInteractiveAdd(props.type);
 const tempItems = useInteractiveAdd(props.type);
 const type = props.type;
 const type = props.type;
 const ShapeComponent = components[type].TempComponent || components[type].Component;
 const ShapeComponent = components[type].TempComponent || components[type].Component;
-const store = useStore();
 </script>
 </script>

+ 15 - 4
src/core/renderer/renderer.vue

@@ -31,7 +31,7 @@
           </template>
           </template>
         </v-layer>
         </v-layer>
         <v-layer id="helper">
         <v-layer id="helper">
-          <ActiveBoxs />
+          <!-- <ActiveBoxs /> -->
           <SnapLines />
           <SnapLines />
           <SplitLine v-if="expose.config.showLabelLine" />
           <SplitLine v-if="expose.config.showLabelLine" />
           <Compass v-if="config.showCompass" />
           <Compass v-if="config.showCompass" />
@@ -65,22 +65,33 @@ import {
 import { useViewerTransformConfig } from "../hook/use-viewer.ts";
 import { useViewerTransformConfig } from "../hook/use-viewer.ts";
 import { useGlobalResize } from "../hook/use-event.ts";
 import { useGlobalResize } from "../hook/use-event.ts";
 import { useAutoService, useExpose } from "../hook/use-expose.ts";
 import { useAutoService, useExpose } from "../hook/use-expose.ts";
-import { DomMountId, DomOutMountId } from "../../constant";
+import { DomMountId, DomOutMountId, rendererMap, rendererName } from "../../constant";
 import { useStore } from "../store/index.ts";
 import { useStore } from "../store/index.ts";
 import { Mode } from "@/constant/mode.ts";
 import { Mode } from "@/constant/mode.ts";
-import { computed, getCurrentInstance, ref, watch } from "vue";
+import { computed, getCurrentInstance, onUnmounted, ref, watch } from "vue";
 import { install } from "../../install-lib.ts";
 import { install } from "../../install-lib.ts";
 import { useConfig } from "../hook/use-config.ts";
 import { useConfig } from "../hook/use-config.ts";
 import { getEmptyStoreData } from "../store/store.ts";
 import { getEmptyStoreData } from "../store/store.ts";
+import { mergeFuns } from "@/utils/shared.ts";
 
 
 const instance = getCurrentInstance();
 const instance = getCurrentInstance();
 install(instance!.appContext.app);
 install(instance!.appContext.app);
 
 
+defineOptions({ name: rendererName });
+rendererMap.set(instance, { unmounteds: [] });
+onUnmounted(() => {
+  mergeFuns(rendererMap.get(instance)!.unmounteds)();
+});
+
 const props = defineProps<InstanceProps>();
 const props = defineProps<InstanceProps>();
 const store = useStore();
 const store = useStore();
 watch(
 watch(
   () => props.data,
   () => props.data,
-  (data) => store.setStore(data || getEmptyStoreData())
+  (data) => {
+    console.log(data);
+    store.setStore(data || getEmptyStoreData());
+  },
+  { immediate: true }
 );
 );
 
 
 useInstanceProps().set(props);
 useInstanceProps().set(props);

+ 3 - 1
src/example/fuse/views/home.vue

@@ -11,7 +11,7 @@
       <Slide class="slide" v-if="draw" />
       <Slide class="slide" v-if="draw" />
       <div class="content" ref="drawEle">
       <div class="content" ref="drawEle">
         <DrawBoard
         <DrawBoard
-          v-if="drawEle"
+          v-if="drawEle && show"
           :handler-resource="uploadResourse"
           :handler-resource="uploadResourse"
           ref="draw"
           ref="draw"
           :data="data"
           :data="data"
@@ -63,6 +63,8 @@ onUnmounted(
     }
     }
   })
   })
 );
 );
+
+const show = ref(true);
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>

+ 0 - 2
src/example/fuse/views/req.ts

@@ -1,10 +1,8 @@
 import { StoreData } from '@/core/store/store';
 import { StoreData } from '@/core/store/store';
 import { initData } from './test'
 import { initData } from './test'
-import { asyncTimeout } from '@/utils/shared';
 
 
 export const getData = async () => {
 export const getData = async () => {
   const dataStr = localStorage.getItem("draw-data");
   const dataStr = localStorage.getItem("draw-data");
-  await asyncTimeout(1000)
   return (dataStr ? JSON.parse(dataStr) : initData) as StoreData;
   return (dataStr ? JSON.parse(dataStr) : initData) as StoreData;
 }
 }
 
 

+ 15 - 0
src/utils/math.ts

@@ -124,6 +124,20 @@ export const vector2IncludedAngle = (v1: Pos, v2: Pos) => {
   return start.cross(end) > 0 ? angle : -angle;
   return start.cross(end) > 0 ? angle : -angle;
 };
 };
 
 
+// 判断多边形方向(Shoelace Formula)
+export function getPolygonDirection(points: Pos[]) {
+  let area = 0;
+  const numPoints = points.length;
+  for (let i = 0; i < numPoints; i++) {
+      const p1 = points[i];
+      const p2 = points[(i + 1) % numPoints];
+      area += (p2.x - p1.x) * (p2.y + p1.y);
+  }
+
+  // 如果面积为正,是逆时针;否则是顺时针
+  return area;
+}
+
 /**
 /**
  * 获取两线段角度(从线段a出发)
  * 获取两线段角度(从线段a出发)
  * @param line1 线段a
  * @param line1 线段a
@@ -161,6 +175,7 @@ export const lineAndVectorIncludedAngle = (line: Pos[], v: Pos) =>
 export const lineCenter = (line: Pos[]) =>
 export const lineCenter = (line: Pos[]) =>
   vector(line[0]).add(line[1]).multiplyScalar(0.5);
   vector(line[0]).add(line[1]).multiplyScalar(0.5);
 
 
+
 export const lineSpeed = (line: Pos[], step: number) => {
 export const lineSpeed = (line: Pos[], step: number) => {
   const p = vector(line[0])
   const p = vector(line[0])
   const v = vector(line[1]).sub(line[0])
   const v = vector(line[1]).sub(line[0])