Просмотр исходного кода

feat: 制作锁定解锁功能

bill 6 месяцев назад
Родитель
Сommit
5de98af21b

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

@@ -18,6 +18,7 @@ import { useComponentStatus } from "@/core/hook/use-component.ts";
 import { Line } from "konva/lib/shapes/Line";
 import { Transform } from "konva/lib/Util";
 
+
 const props = defineProps<{ data: ArrowData }>();
 const emit = defineEmits<{
   (e: "updateShape", value: ArrowData): void;

+ 0 - 4
src/core/components/circle/circle.vue

@@ -17,7 +17,6 @@ import { useComponentStatus } from "@/core/hook/use-component.ts";
 import { Transform } from "konva/lib/Util";
 import { MathUtils } from "three";
 import { cloneRepShape, useCustomTransformer } from "@/core/hook/use-transformer.ts";
-import { setShapeTransform } from "@/utils/shape.ts";
 import { Ellipse } from "konva/lib/shapes/Ellipse";
 import { Pos } from "@/utils/math.ts";
 
@@ -29,8 +28,6 @@ const emit = defineEmits<{
 }>();
 
 const matToData = (data: CircleData, mat: Transform, initRadius?: Pos) => {
-  // data.mat = mat.m;
-  // return data;
   if (!initRadius) {
     initRadius = {
       x: data.radiusX,
@@ -59,7 +56,6 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus<
     mat.multiply(tf);
     return matToData(data, mat);
   },
-  // transformType: "mat",
   transformType: "custom",
   customTransform(callback, shape, data) {
     let initRadius: Pos;

+ 2 - 0
src/core/components/icon/temp-icon.vue

@@ -41,6 +41,7 @@ const pathConfigs = computed(() => {
   return svg.value.paths.map((path) => ({
     ...path,
     ...data.value,
+    id: undefined,
     zIndex: undefined,
     offset: { x: svg.value!.x, y: svg.value!.y },
   }));
@@ -67,6 +68,7 @@ const initDecMat = computed(() => {
 const groupConfig = computed(() => {
   return {
     ...new Transform(data.value.mat).decompose(),
+    id: data.value.id,
     opacity: props.addMode ? 0.3 : 1,
   };
 });

+ 1 - 0
src/core/components/text/text-dom.vue

@@ -164,6 +164,7 @@ textarea {
   pointer-events: all;
   outline: none;
   word-break: break-all;
+  overflow-wrap: break-word;
   transform-origin: left top;
 }
 

+ 1 - 2
src/core/components/text/text.vue

@@ -17,7 +17,7 @@
 </template>
 
 <script lang="ts" setup>
-import { TextData, getMouseStyle, defaultStyle, textNodeMap } from "./index.ts";
+import { TextData, getMouseStyle, defaultStyle } from "./index.ts";
 import { PropertyUpdate, Operate } from "../../propertys";
 import TempText from "./temp-text.vue";
 import TextDom from "./text-dom.vue";
@@ -155,7 +155,6 @@ const dbclickHandler = (ev: KonvaEventObject<MouseEvent>) => {
   transformer.hide();
 };
 const quitHandler = (val: string) => {
-  console.log("quit");
   editText.value = false;
   transformer.show();
   $shape.value && $shape.value.show();

+ 0 - 1
src/core/helper/active-boxs.vue

@@ -61,7 +61,6 @@ const shapeListener = (shape: EntityShape, onCleanup: OnCleanup) => {
 
 watchEffect(
   (onCleanup) => {
-    console.error(status.actives)
     status.actives.forEach((shape) => shapeListener(shape, onCleanup));
   },
   { flush: "pre" }

+ 4 - 1
src/core/history.ts

@@ -19,7 +19,7 @@ export class SingleHistory<T = any> {
 
   list() {
     const history: any = this.history;
-    return history.$records.map((item: any) => {
+    return history.$records.filter((item: any) => !!item).map((item: any) => {
       const id = item.hashes[0];
       return { id, data: JSON.parse(history.$chunks[item.hashes[0]]).data };
     }) as {id: string, data: T}[];
@@ -56,6 +56,9 @@ export class SingleHistory<T = any> {
   }
 
   push(data: T) {
+    if (!data) {
+      console.error('push了null的条目', data)
+    }
     this.history.pushSync({ data });
     this.syncState();
   }

+ 69 - 25
src/core/hook/use-component.ts

@@ -3,15 +3,20 @@ import {
   computed,
   EmitFn,
   isRef,
+  markRaw,
   reactive,
   Ref,
   ref,
   shallowReactive,
+  shallowRef,
   watchEffect,
 } from "vue";
 import { useAutomaticData } from "./use-automatic-data";
 import { useCurrentZIndex, useZIndex } from "./use-layer";
-import { useAnimationMouseStyle } from "./use-mouse-status";
+import {
+  useAnimationMouseStyle,
+  useMouseShapesStatus,
+} from "./use-mouse-status";
 import { components, DrawItem, ShapeType } from "../components";
 import { useMatCompTransformer, useLineTransformer } from "./use-transformer";
 import { useGetShapeCopyTransform } from "./use-copy";
@@ -19,17 +24,19 @@ import {
   Bottom,
   Delete,
   DocumentCopy,
+  Download,
   Location,
   Lock,
   Top,
   Unlock,
+  Upload,
 } from "@element-plus/icons-vue";
-import { mergeFuns, onlyId } from "@/utils/shared";
+import { asyncTimeout, mergeFuns, onlyId } from "@/utils/shared";
 import { Shape } from "konva/lib/Shape";
 import { Transform } from "konva/lib/Util";
 import { mergeDescribes, PropertyKeys } from "../propertys";
 import { useStore } from "../store";
-import { globalWatch } from "./use-global-vars";
+import { globalWatch, useStage } from "./use-global-vars";
 import { useAlignmentShape } from "./use-alignment";
 
 type Emit<T> = EmitFn<{
@@ -51,24 +58,28 @@ export const useComponentMenus = <T extends DrawItem>(
     handler: () => void;
   }> = shallowReactive([]);
 
-  // 锁定 解锁
+  // 置顶 置底
+  const currentZIndex = useCurrentZIndex();
   operateMenus.push(
-    reactive({
-      label: computed(() => (data.value.lock ? "解锁" : "锁定")) as any,
-      icon: computed(() => (data.value.lock ? Unlock : Lock)),
+    {
+      label: `上移`,
+      icon: Top,
       handler() {
-        data.value.lock = !data.value.lock;
+        data.value.zIndex += 1;
         emit("updateShape", { ...data.value });
       },
-    })
-  );
-
-  // 置顶 置底
-  const currentZIndex = useCurrentZIndex();
-  operateMenus.push(
+    },
+    {
+      label: `下移`,
+      icon: Bottom,
+      handler() {
+        data.value.zIndex -= 1;
+        emit("updateShape", { ...data.value });
+      },
+    },
     {
       label: `置顶`,
-      icon: Top,
+      icon: Upload,
       handler() {
         data.value.zIndex = currentZIndex.max + 1;
         emit("updateShape", { ...data.value });
@@ -76,7 +87,7 @@ export const useComponentMenus = <T extends DrawItem>(
     },
     {
       label: `置底`,
-      icon: Bottom,
+      icon: Download,
       handler() {
         data.value.zIndex = currentZIndex.min - 1;
         emit("updateShape", { ...data.value });
@@ -84,6 +95,18 @@ export const useComponentMenus = <T extends DrawItem>(
     }
   );
 
+  // 锁定 解锁
+  operateMenus.push(
+    reactive({
+      label: computed(() => (data.value.lock ? "解锁" : "锁定")) as any,
+      icon: computed(() => (data.value.lock ? Unlock : Lock)),
+      handler() {
+        data.value.lock = !data.value.lock;
+        emit("updateShape", { ...data.value });
+      },
+    })
+  );
+
   if (alignment) {
     const [alignmentShape] = useAlignmentShape(shape);
     operateMenus.push({
@@ -99,17 +122,26 @@ export const useComponentMenus = <T extends DrawItem>(
 
   if (copyHandler) {
     const getCopyTransform = useGetShapeCopyTransform(shape);
+    const status = useMouseShapesStatus();
+    const stage = useStage();
     operateMenus.push({
       label: `复制`,
       icon: DocumentCopy,
-      handler() {
+      async handler() {
         const transform = getCopyTransform();
         const copyData = copyHandler(
           transform,
           JSON.parse(JSON.stringify(data.value)) as T
         );
-        copyData.id = onlyId();
         emit("addShape", copyData);
+        await asyncTimeout(100);
+        const $stage = stage.value?.getNode();
+        if (!$stage) return;
+        const $shape = $stage.findOne<Shape>(`#${copyData.id}`);
+        if ($shape) {
+          status.actives.length = 0;
+          status.actives.push($shape);
+        }
       },
     });
   }
@@ -149,14 +181,30 @@ export type UseComponentStatusProps<
 export const useComponentStatus = <S extends EntityShape, T extends DrawItem>(
   args: UseComponentStatusProps<T, S>
 ) => {
-  const shape = ref<DC<S>>();
+  const shape = shallowRef<DC<S>>();
+  watchEffect(
+    () => {
+      if (shape.value) {
+        markRaw(shape.value);
+      }
+    },
+    { flush: "sync" }
+  );
   const data = useAutomaticData(() => args.props.data);
-  const [style] = useAnimationMouseStyle({
+  const [style, pause, resume] = useAnimationMouseStyle({
     data: data,
     shape,
     getMouseStyle: args.getMouseStyle,
   }) as any;
 
+  watchEffect(() => {
+    if (data.value.lock) {
+      pause();
+    } else {
+      resume();
+    }
+  });
+
   if (args.transformType === "line") {
     useLineTransformer(
       shape as any,
@@ -195,11 +243,7 @@ export const useComponentStatus = <S extends EntityShape, T extends DrawItem>(
       args.alignment,
       args.copyHandler
     ),
-    describes: mergeDescribes(
-      data,
-      args.defaultStyle,
-      args.propertys || []
-    )
+    describes: mergeDescribes(data, args.defaultStyle, args.propertys || []),
   };
 };
 

+ 77 - 39
src/core/hook/use-global-vars.ts

@@ -6,6 +6,7 @@ import {
   nextTick,
   onUnmounted,
   reactive,
+  Ref,
   ref,
   shallowRef,
   watch,
@@ -67,19 +68,19 @@ export const installGlobalVar = <T>(
 export type InstanceProps = {
   id?: string;
   data: DrawData;
-  handlerResource(file: File): Promise<string>
-}
+  handlerResource(file: File): Promise<string>;
+};
 export const useInstanceProps = installGlobalVar(() => {
-  const props = ref<InstanceProps>()
+  const props = ref<InstanceProps>();
   return {
     set(val: InstanceProps) {
-      props.value = val
+      props.value = val;
     },
     get() {
-      return props.value!
-    }
-  }
-}, Symbol('instanceId'))
+      return props.value!;
+    },
+  };
+}, Symbol("instanceId"));
 
 export const stackVar = <T>(init?: T) => {
   const stack = reactive([init]) as T[];
@@ -125,21 +126,21 @@ export const globalWatch = <T>(
 };
 
 export const useStage = installGlobalVar(
-  () => shallowRef<DC<Stage> | undefined>(),               
+  () => shallowRef<DC<Stage> | undefined>(),
   Symbol("stage")
 );
 export const useMode = installGlobalVar(() => {
-  const stack = stackVar(new Set([Mode.write]))
+  const stack = stackVar(new Set([Mode.write]));
   const modeStack = {
     ...stack,
     get value() {
-      return stack.value
+      return stack.value;
     },
     set value(val: Set<Mode>) {
-      stack.value = val
+      stack.value = val;
     },
     push(...modes: Mode[]) {
-      return stack.push(new Set(modes))
+      return stack.push(new Set(modes));
     },
     include(...modes: Mode[]) {
       return modes.every((m) => modeStack.value.has(m));
@@ -149,44 +150,49 @@ export const useMode = installGlobalVar(() => {
     },
     del(...modes: Mode[]) {
       modes.forEach((mode) => modeStack.value.delete(mode));
-    }
-  }
-  if (import.meta.env.DEV) {
-    watchEffect(() => {
-      console.error([...modeStack.value.values()].join(','))
-    }, { flush: 'sync' })
-  }
+    },
+  };
+  // if (import.meta.env.DEV) {
+  //   watchEffect(
+  //     () => {
+  //       console.error([...modeStack.value.values()].join(","));
+  //     },
+  //     { flush: "sync" }
+  //   );
+  // }
   return modeStack;
 }, Symbol("mode"));
 export const useCan = installGlobalVar(() => {
   const mode = useMode();
   const stage = useStage();
-  const key = useDownKeys()
-  const loaded = computed(() => !!stage.value?.getStage())
+  const key = useDownKeys();
+  const loaded = computed(() => !!stage.value?.getStage());
 
   // 鼠标是否可用
-  const mouse = computed(() => loaded.value && !mode.include(Mode.static))
+  const mouse = computed(() => loaded.value && !mode.include(Mode.static));
 
   // 可以进入拖拽模式
   const dragMode = computed(() => {
-    if (!mouse.value || mode.include(Mode.readonly) || key.has(' ')) return false;
-    return mode.include(Mode.draw) || mode.include(Mode.update)
-  })
+    if (!mouse.value || mode.include(Mode.readonly) || key.has(" "))
+      return false;
+    return mode.include(Mode.draw) || mode.include(Mode.update);
+  });
 
   // 是否在视图模式
   const viewMode = computed(() => {
-    return mouse.value && (!mode.include(Mode.draging) || key.has(' '))
-  })
+    return mouse.value && (!mode.include(Mode.draging) || key.has(" "));
+  });
 
   // shape是否可以对鼠标做出反应
-  const mouseReact = computed(() => mouse.value && (mode.include(Mode.write) || mode.include(Mode.update)))
+  const mouseReact = computed(
+    () => mouse.value && (mode.include(Mode.write) || mode.include(Mode.update))
+  );
 
   // 可以进入编辑模式
-  const editMode = computed(() => mouse.value && mode.include(Mode.write))
+  const editMode = computed(() => mouse.value && mode.include(Mode.write));
 
   // 可以进入绘制模式
-  const drawMode = computed(() => mouse.value && mode.include(Mode.write))
-
+  const drawMode = computed(() => mouse.value && mode.include(Mode.write));
 
   return reactive({
     viewMouseReact: mouse,
@@ -200,7 +206,33 @@ export const useCan = installGlobalVar(() => {
 
 export const usePointerPos = installGlobalVar(() => {
   const stage = useStage();
-  const pos = ref<Pos | null>(null);
+  const pos = ref(null) as Ref<Pos | null> & { replay: () => void };
+  let lastClient: { clientX: number; clientY: number } | null = null;
+  let replayIng = false;
+
+  const replay = () => {
+    const $stage = stage.value?.getNode();
+    if (!$stage || !lastClient) return;
+    replayIng = true;
+    const dom = $stage.container().querySelector("canvas") as HTMLCanvasElement;
+    const moveConf = {
+      bubbles: true, // 事件是否能够冒泡
+      cancelable: true, // 事件是否可以被取消
+      isPrimary: true,
+      pointerId: 1,
+    };
+    dom.dispatchEvent(
+      new PointerEvent("pointermove", {
+        ...moveConf,
+        clientX: -9999999,
+        clientY: -9999999,
+      })
+    );
+    dom.dispatchEvent(
+      new PointerEvent("pointermove", { ...lastClient, ...moveConf })
+    );
+    replayIng = false;
+  };
 
   watchEffect((onCleanup) => {
     const $stage = stage.value?.getNode();
@@ -209,15 +241,22 @@ export const usePointerPos = installGlobalVar(() => {
     const mount = $stage.container().parentElement!;
     pos.value = $stage.pointerPos;
     onCleanup(
-      listener(mount, "pointermove", () => {
+      listener(mount, "pointermove", (ev) => {
         pos.value = $stage.pointerPos;
+        if (pos.value && !replayIng) {
+          lastClient = {
+            clientX: ev.clientX,
+            clientY: ev.clientY,
+          };
+        }
       })
     );
   });
+
+  pos.replay = replay;
   return pos;
 }, Symbol("pointerPos"));
 
-
 export const useDownKeys = installGlobalVar(() => {
   const keys = reactive(new Set<string>());
   const cleanup = mergeFuns(
@@ -244,8 +283,7 @@ export const useTransformIngShapes = installGlobalVar(
   Symbol("transformIngShapes")
 );
 
-
 export const useCursor = installGlobalVar(
-  () => stackVar('default'),
-  Symbol('cursor')
-)
+  () => stackVar("default"),
+  Symbol("cursor")
+);

+ 84 - 29
src/core/hook/use-mouse-status.ts

@@ -1,4 +1,12 @@
-import { computed, reactive, ref, Ref, toRaw, watch, watchEffect } from "vue";
+import {
+  computed,
+  reactive,
+  ref,
+  Ref,
+  toRaw,
+  watch,
+  watchEffect,
+} from "vue";
 import { DC, EntityShape } from "../../deconstruction";
 import { Shape } from "konva/lib/Shape";
 import {
@@ -127,6 +135,7 @@ export const useMouseShapesStatus = installGlobalVar(() => {
         hovers.value.pop();
       }
     };
+
     const stopHoverCheck = watch(
       () => [hover.value, prevent.value, hovers.value[0]],
       (_a, _b, onCleanup) => hoverChange(onCleanup)
@@ -145,22 +154,26 @@ export const useMouseShapesStatus = installGlobalVar(() => {
     return mergeFuns(
       stopHoverCheck,
       hoverDestory,
-      listener(stage.container(), "pointerup", () => {
-        if (prevent.value) return;
-        press.value = [];
-        if (Date.now() - downTime >= 300) return;
-        if (downTarget) {
-          const ndx = selects.value.indexOf(downTarget);
-          if (~ndx) {
-            selects.value.splice(ndx, 1);
+      listener(
+        stage.container().parentElement as HTMLDivElement,
+        "pointerup",
+        () => {
+          if (prevent.value) return;
+          press.value = [];
+          if (Date.now() - downTime >= 300) return;
+          if (downTarget) {
+            const ndx = selects.value.indexOf(downTarget);
+            if (~ndx) {
+              selects.value.splice(ndx, 1);
+            } else {
+              selects.value.push(downTarget);
+            }
+            actives.value = [downTarget];
           } else {
-            selects.value.push(downTarget);
+            actives.value = [];
           }
-          actives.value = [downTarget];
-        } else {
-          actives.value = [];
         }
-      }),
+      ),
       () => {
         listeners.value.forEach((shape) => {
           shape.off("pointerleave.mouse-status");
@@ -190,8 +203,29 @@ export const useMouseShapesStatus = installGlobalVar(() => {
     { immediate: true }
   );
 
+  const pauseShapes = ref(new Set<EntityShape>());
+  const getShapes = (shapes: Ref<EntityShape[]>) =>
+    computed(() => {
+      return shapes.value
+        .filter((shape) => !pauseShapes.value.has(shape))
+        .map(toRaw);
+    });
+  const status = reactive({
+    hovers: getShapes(hovers),
+    actives: getShapes(actives),
+    selects: getShapes(selects),
+    press: getShapes(press),
+    listeners,
+    pause(shape: EntityShape) {
+      pauseShapes.value.add(shape);
+    },
+    resume(shape: EntityShape) {
+      pauseShapes.value.delete(shape);
+    },
+  });
+
   return {
-    var: reactive({ hovers, actives, selects, press, listeners }),
+    var: status,
     onDestroy: () => {
       stopStatusWatch();
       cleanup && cleanup();
@@ -203,32 +237,40 @@ export const useMouseShapeStatus = (
   shape: Ref<DC<EntityShape> | undefined>
 ) => {
   const status = useMouseShapesStatus();
+  const shapeStatus = computed(() => {
+    const $shape = shape.value?.getStage() as Shape;
+    return {
+      hover: status.hovers.includes($shape),
+      active: status.actives.includes($shape),
+      press: status.press.includes($shape),
+      select: status.selects.includes($shape),
+      pause: () => shape.value?.getNode() && status.pause(shape.value?.getNode()),
+      resume: () => shape.value?.getNode() && status.resume(shape.value?.getNode()),
+    };
+  });
+
   watch(
     () => shape.value?.getStage(),
     (shape, _, onCleanup) => {
       if (shape) {
         if (status.listeners.includes(shape)) return;
+
         status.listeners.push(shape);
         onCleanup(() => {
           for (const key in status) {
             const k = key as keyof typeof status;
-            const ndx = status[k].indexOf(shape);
-            ~ndx && status[k].splice(ndx, 1);
+            if (Array.isArray(status[k])) {
+              const ndx = status[k].indexOf(shape);
+              ~ndx && status[k].splice(ndx, 1);
+            }
           }
         });
       }
-    }
+    },
+    { immediate: true }
   );
 
-  return computed(() => {
-    const $shape = shape.value?.getStage() as Shape;
-    return {
-      hover: status.hovers.includes($shape),
-      active: status.actives.includes($shape),
-      press: status.press.includes($shape),
-      select: status.selects.includes($shape),
-    };
-  });
+  return shapeStatus;
 };
 
 export const useActiveItem = <T extends EntityShape>() => {
@@ -303,6 +345,19 @@ export const useMouseStyle = <T extends ShapeType>(
 export const useAnimationMouseStyle = <T extends ShapeType>(
   props: MouseStyleProps<T>
 ) => {
-  const { currentStyle } = useMouseStyle(props);
-  return useAniamtion(currentStyle as any);
+  const { currentStyle, status } = useMouseStyle(props);
+  const [data, pauseAnimation, resumeAnimation] = useAniamtion(
+    currentStyle as any
+  );
+  return [
+    data,
+    () => {
+      pauseAnimation();
+      status.value.pause();
+    },
+    () => {
+      status.value.resume();
+      resumeAnimation();
+    },
+  ];
 };

+ 25 - 0
src/core/hook/use-pause.ts

@@ -0,0 +1,25 @@
+import { ref } from "vue"
+
+export type PausePack<T extends object> =  T & {
+  pause: () => void,
+  resume: () => void,
+  isPause: boolean
+}
+export const usePause = <T extends object>(api?: T): PausePack<T> => {
+  const isPause = ref(false)
+  const result = (api || {}) as PausePack<T>
+
+  Object.defineProperty(result, 'isPause', {
+    get() {
+      return isPause.value
+    },
+    set(v) {
+      return true
+    }
+  })
+
+  result.pause = () => isPause.value = true
+  result.resume = () => isPause.value = false
+
+  return result
+}

+ 4 - 1
src/core/hook/use-transformer.ts

@@ -176,6 +176,7 @@ export const useShapeDrag = (shape: Ref<DC<EntityShape> | undefined>) => {
   const can = useCan();
   const conversion = useConversionPosition(true);
   const transformIngShapes = useTransformIngShapes();
+  const status = useMouseShapeStatus(shape)
 
   const init = (shape: EntityShape) => {
     const dom = shape.getStage()!.container();
@@ -231,7 +232,9 @@ export const useShapeDrag = (shape: Ref<DC<EntityShape> | undefined>) => {
   };
 
   watch(
-    () => (can.editMode || mode.include(Mode.update)) && shape.value?.getNode(),
+    () =>
+      (can.editMode || mode.include(Mode.update)) &&
+      (status.value.active || status.value.hover),
     (canEdit, _, onCleanup) => {
       canEdit && onCleanup(init(shape.value!.getNode()));
     }

+ 1 - 3
src/core/propertys/hover-operate.vue

@@ -29,7 +29,6 @@
 import { computed, nextTick, ref, watch, watchEffect } from "vue";
 import { useStage } from "../hook/use-global-vars.ts";
 import { DC, EntityShape } from "@/deconstruction.js";
-import { useMouseShapeStatus } from "../hook/use-mouse-status.ts";
 import { useViewerTransformConfig } from "../hook/use-viewer.ts";
 import { Transform } from "konva/lib/Util";
 import { DomMountId } from "@/constant/index.ts";
@@ -205,7 +204,7 @@ watch(useViewerTransformConfig(), () => {
 
 .pointer-fade-enter-active,
 .pointer-fade-leave-active {
-  transition: opacity 0.3s ease, max-height 0.3s ease;
+  transition: opacity 0.3s ease;
   .item {
     pointer-events: none;
   }
@@ -213,6 +212,5 @@ watch(useViewerTransformConfig(), () => {
 .pointer-fade-enter-from,
 .pointer-fade-leave-to {
   opacity: 0;
-  max-height: 0;
 }
 </style>

+ 1 - 3
src/core/renderer/group.vue

@@ -15,7 +15,7 @@
 
 <script setup lang="ts">
 import { ShapeType, components } from "../components";
-import { computed, watchEffect } from "vue";
+import { computed } from "vue";
 import { useStore, useStoreRenderProcessors } from "../store";
 
 const props = defineProps<{ type: ShapeType }>();
@@ -24,6 +24,4 @@ const type = props.type as "arrow";
 const ShapeComponent = components[type].Component;
 const items = computed(() => store.data[type] || []);
 const { itemHasRegistor, renderer } = useStoreRenderProcessors();
-
-watchEffect(() => console.log(props.type, items.value));
 </script>

+ 20 - 6
src/utils/shared.ts

@@ -46,7 +46,7 @@ export const mergeFuns = (...fns: (() => void)[] | (() => void)[][]) => {
   };
 };
 
-export const copy = <T>(data: T): T => JSON.parse(JSON.stringify(data))
+export const copy = <T>(data: T): T => JSON.parse(JSON.stringify(data));
 
 /**
  * 获取数据类型
@@ -177,15 +177,15 @@ export const startAnimation = (update: () => void, dur = -1) => {
   };
   animation();
 
-  let timeout: any
+  let timeout: any;
   if (dur >= 0) {
-    setTimeout(() => isStop = true, dur)
+    setTimeout(() => (isStop = true), dur);
   }
 
   return () => {
-    clearTimeout(timeout)
-    isStop = true
-  }
+    clearTimeout(timeout);
+    isStop = true;
+  };
 };
 
 export const arrayInsert = <T>(
@@ -203,3 +203,17 @@ export const arrayInsert = <T>(
   return array;
 };
 
+export const asyncTimeout = (time: number) => {
+  let timeout: any;
+  let reject: any
+  const promise = new Promise<void>((resolve, r) => {
+    timeout = setTimeout(resolve, time);
+    reject = r
+  }) as Promise<void> & { stop: () => void };
+
+  promise.stop = () => {
+    clearTimeout(timeout);
+    reject('取消')
+  };
+  return promise;
+};