bill 1 неделя назад
Родитель
Сommit
da5964bda7
33 измененных файлов с 3411 добавлено и 10580 удалено
  1. 0 7138
      package-lock.json
  2. 1 1
      package.json
  3. 2305 2891
      pnpm-lock.yaml
  4. 0 2
      src/core/components/group/temp-group.vue
  5. 1 1
      src/core/components/image/image.vue
  6. 2 3
      src/core/components/image/index.ts
  7. 13 4
      src/core/components/image/temp-image.vue
  8. 1 2
      src/core/components/line/single-line.vue
  9. 1 0
      src/core/components/serial/index.ts
  10. 2 2
      src/core/components/serial/serial-group.vue
  11. 1 0
      src/core/components/util.ts
  12. 1 2
      src/core/helper/split-line.vue
  13. 13 5
      src/core/history.ts
  14. 0 1
      src/core/hook/use-copy.ts
  15. 6 0
      src/core/hook/use-expose.ts
  16. 1 0
      src/core/hook/use-global-vars.ts
  17. 7 4
      src/core/hook/use-history.ts
  18. 34 16
      src/core/hook/use-viewer.ts
  19. 5 1
      src/core/html-mount/propertys/hover-operate.vue
  20. 34 17
      src/core/renderer/renderer.vue
  21. 33 0
      src/core/store/compatible.ts
  22. 168 164
      src/core/store/store.ts
  23. 17 2
      src/core/viewer.ts
  24. 22 3
      src/example/constant.ts
  25. 3 3
      src/example/fuse/enter.ts
  26. 92 0
      src/example/fuse/views/overview/actions.ts
  27. 22 131
      src/example/fuse/views/overview/header.vue
  28. 11 3
      src/example/fuse/views/overview/index.vue
  29. 7 1
      src/example/fuse/views/tabulation/gen-tab.ts
  30. 13 2
      src/example/fuse/views/tabulation/header.vue
  31. 290 133
      src/example/fuse/views/tabulation/index.vue
  32. 254 0
      src/example/fuse/views/tabulation/overview-viewport.vue
  33. 51 48
      src/utils/shared.ts

Разница между файлами не показана из-за своего большого размера
+ 0 - 7138
package-lock.json


+ 1 - 1
package.json

@@ -28,7 +28,7 @@
     "html2canvas": "^1.4.1",
     "jspdf": "^3.0.1",
     "jszip": "^3.10.1",
-    "konva": "^9.3.18",
+    "konva": "9.3.20",
     "leaflet": "^1.9.4",
     "localforage": "^1.10.0",
     "martinez-polygon-clipping": "^0.7.4",

Разница между файлами не показана из-за своего большого размера
+ 2305 - 2891
pnpm-lock.yaml


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

@@ -125,8 +125,6 @@ watch(
   { immediate: true }
 );
 
-onUnmounted(() => console.error("des temp-group"));
-
 defineExpose({
   get shape() {
     return shape.value;

+ 1 - 1
src/core/components/image/image.vue

@@ -89,6 +89,6 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus<
 });
 
 if (props.data.key === "kankan-floor-cover") {
-  operateMenus[1].hide = false
+  operateMenus[1].hide = false;
 }
 </script>

+ 2 - 3
src/core/components/image/index.ts

@@ -41,7 +41,7 @@ export const getSnapInfos = (data: ImageData) => {
 export const getSnapPoints = (data: ImageData) => {
   const tf = new Transform(data.mat);
   const useData = data.width && data.height;
-  if (!useData && !(data.url in imageInfo)) {
+  if (!data.url || (!useData && !(data.url in imageInfo))) {
     return [];
   }
 
@@ -63,7 +63,7 @@ export type ImageData = Partial<typeof defaultStyle> &
     heightRaw?: number
     strokeWidth?: number;
     cornerRadius?: number;
-    url: string;
+    url?: string;
     mat: number[];
   };
 
@@ -73,7 +73,6 @@ export const interactiveToData: InteractiveTo<"image"> = ({
   ...args
 }) => {
   if (info.cur) {
-    console.log(preset)
     return interactiveFixData({
       ...args,
       info,

+ 13 - 4
src/core/components/image/temp-image.vue

@@ -9,14 +9,22 @@
         zIndex: undefined,
         name: 'repShape',
       }"
-      v-if="image"
     />
+    <!-- <v-rect
+      :config="{
+        width: config.width,
+        height: config.height,
+        offset: config.offset,
+        fill: '#ff0000',
+        opacity: 0.3,
+      }"
+    /> -->
   </v-group>
 </template>
 
 <script lang="ts" setup>
 import { defaultStyle, ImageData } from "./index.ts";
-import { computed, ref, watch, watchEffect } from "vue";
+import { computed, onUnmounted, ref, watch, watchEffect } from "vue";
 import { getImage } from "@/utils/resource.ts";
 import { Transform } from "konva/lib/Util";
 import { Group } from "konva/lib/Group";
@@ -31,12 +39,13 @@ defineExpose({
     return shape.value;
   },
 });
-
 watch(
   () => data.value.url,
   async (url) => {
     image.value = null;
-    image.value = await getImage(window.platform.getResource(url));
+    if (url) {
+      image.value = await getImage(window.platform.getResource(url));
+    }
   },
   { immediate: true }
 );

+ 1 - 2
src/core/components/line/single-line.vue

@@ -88,7 +88,7 @@
 <script lang="ts" setup>
 import EditLine from "../share/edit-line.vue";
 import singlePoint from "./single-point.vue";
-import { computed, ref } from "vue";
+import { computed, ref, watchEffect } from "vue";
 import { getMouseStyle, LineData, LineDataLine, renderer } from "./index.ts";
 import { onlyId } from "@/utils/shared.ts";
 import { Pos } from "@/utils/math.ts";
@@ -227,7 +227,6 @@ const dragendHandler = () => {
   emit("update");
   lDataSnap.clear();
 };
-
 defineExpose({
   get shape() {
     return shape.value;

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

@@ -174,6 +174,7 @@ export const delItemRaw = (
 };
 
 export const joinKey = "serial-table";
+export const tableTitle = `图示`
 export const delItem = (store: DrawStore, item: SerialData) => {
   const table = store
     .getTypeItems("table")

+ 2 - 2
src/core/components/serial/serial-group.vue

@@ -24,7 +24,7 @@ import {
   TableData,
   interactiveToData as tableInteractiveToData,
 } from "../table";
-import { defaultTableStyle, delItem, getCurrentNdx, joinKey, SerialData } from ".";
+import { defaultTableStyle, delItem, getCurrentNdx, joinKey, SerialData, tableTitle } from ".";
 import {
   useGetViewBoxPositionPixel,
   useViewerInvertTransform,
@@ -113,7 +113,7 @@ const addTable = () => {
       notaddCol: true,
       key: joinKey,
       fontSize: defaultTableStyle.fontSize,
-      title: "图示",
+      title: tableTitle,
       content: [content],
       strokeWidth: defaultTableStyle.tableStrokeWidth,
     },

+ 1 - 0
src/core/components/util.ts

@@ -19,6 +19,7 @@ export type BaseItem = {
   ref: boolean
   hide?: boolean
   listening?: boolean
+  userData?: any
 };
 
 export const getBaseItem = (): BaseItem => ({

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

@@ -73,8 +73,7 @@ const rang = computed(() => {
 
     const formatGroup = format.value.findOne<Group>(`#${DataGroupId}`)!;
     const rect = formatGroup.getClientRect();
-    const bw = config.labelLineConfig.strokeWidth / 2;
-    // console.log(rect);
+    let bw = config.labelLineConfig.strokeWidth / 2;
     return {
       lt: {
         x: rect.x - config.labelLineConfig.showOffset - mrs[3] + bw,

+ 13 - 5
src/core/history.ts

@@ -19,10 +19,12 @@ export class SingleHistory<T = any> {
 
   list() {
     const history: any = this.history;
-    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}[];
+    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 }[];
   }
 
   get currentId(): string {
@@ -39,6 +41,12 @@ export class SingleHistory<T = any> {
     return this.history.get()?.data;
   }
 
+  setcur(data: T) {
+    const history = this.history as any;
+    const id = history.$records[history.$index].hashes[0];
+    history.$chunks[id] = JSON.stringify({ data });
+  }
+
   undo(): T {
     if (this.history.hasUndo) {
       this.history.undo();
@@ -57,7 +65,7 @@ export class SingleHistory<T = any> {
 
   push(data: T) {
     if (!data) {
-      console.error('push了null的条目', data)
+      console.error("push了null的条目", data);
     }
     this.history.pushSync({ data });
     this.syncState();

+ 0 - 1
src/core/hook/use-copy.ts

@@ -54,7 +54,6 @@ export const useGetShapeCopyTransform = (shape: Ref<DC<EntityShape> | undefined>
     const origin = invViewTransform.value.point({x: 0, y: 0})
     const target = invViewTransform.value.point(translate);
     // 转化为真实坐标
-    console.log(target.x - origin.x, target.y - origin.y)
     return  new Transform().translate(target.x - origin.x, target.y - origin.y)
   }
 }

+ 6 - 0
src/core/hook/use-expose.ts

@@ -14,9 +14,12 @@ import { Stage } from "konva/lib/Stage";
 import { useInteractiveProps } from "./use-interactive.ts";
 import { useStore } from "../store/index.ts";
 import {
+  useFixedScale,
   useGetViewBoxPositionPixel,
   useSetViewport,
   useViewer,
+  useViewerDebounce,
+  useViewerTransformConfig,
 } from "./use-viewer.ts";
 import { useGlobalResize, useListener } from "./use-event.ts";
 import { useInteractiveDrawShapeAPI } from "./use-draw.ts";
@@ -292,6 +295,8 @@ export const useExpose = installGlobalVar(() => {
     formalLayer: useFormalLayer(),
     updateSize,
     history,
+    fixedScale: useFixedScale(),
+    viewerDebounce: useViewerDebounce(),
     store,
     ...useSetViewport(),
     mode,
@@ -300,6 +305,7 @@ export const useExpose = installGlobalVar(() => {
     },
     getViewBoxPositionPixel: useGetViewBoxPositionPixel(),
     viewer,
+    viewerConfig: useViewerTransformConfig(),
     excludeSelection: useExcludeSelection(),
     runHook: useRunHook(),
     presetAdd: interactiveProps,

+ 1 - 0
src/core/hook/use-global-vars.ts

@@ -82,6 +82,7 @@ export const useRunHook = installGlobalVar(() => {
 export type InstanceProps = {
   id?: string;
   data?: StoreData;
+  mergeLayers?: boolean
   handlerResource(file: File): Promise<string>;
 };
 export const useInstanceProps = installGlobalVar(() => {

+ 7 - 4
src/core/hook/use-history.ts

@@ -146,15 +146,18 @@ export class DrawHistory {
   }
 
   push(data: string) {
-    if (this.prevent.flag) return;
+    if (this.prevent.flag) {
+      this.history.setcur({ data, attachs: JSON.stringify(this.pushAttachs) })
+      return;
+    }
+    
     if (this.once.flag) {
       this.onceHistory = data;
     } else if (data !== this.current?.data || this.enforce.flag) {
-      console.log('push history')
-      this.bus.emit("push", data);
+      !this.prevent.flag && this.bus.emit("push", data);
       this.history.push({ attachs: JSON.stringify(this.pushAttachs), data });
       this.pushAttachs = {};
-      this.bus.emit("pushed");
+      !this.prevent.flag && this.bus.emit("pushed");
     }
   }
 

+ 34 - 16
src/core/hook/use-viewer.ts

@@ -1,5 +1,5 @@
 import { Viewer } from "../viewer.ts";
-import { computed, nextTick, ref, watch, watchEffect } from "vue";
+import { computed, nextTick, Ref, ref, watch, watchEffect } from "vue";
 import { dragListener, scaleListener } from "../../utils/event.ts";
 import { globalWatch, installGlobalVar, useStage } from "./use-global-vars.ts";
 import { useCan } from "./use-status";
@@ -40,12 +40,16 @@ export const useViewer = installGlobalVar(() => {
       }),
       scaleListener(dom, (info) => {
         if (can.viewMode || disabled.value) {
+          
           viewer.scalePixel(info.center, info.scale);
         }
       }),
-      watchEffect(() => {
-        size.value && viewer.setSize(size.value);
-      })
+      watchEffect(
+        () => {
+          size.value && viewer.setSize(size.value);
+        },
+        { flush: "sync" }
+      )
     );
 
     viewer.bus.on("transformChange", (newTransform) => {
@@ -99,17 +103,30 @@ export const useViewerInvertTransformConfig = () => {
   return computed(() => transform.value.decompose());
 };
 
-export const useFixedScale = () => {
-  const inv = useViewerInvertTransformConfig()
-  const scale = ref(inv.value.scaleX)
-  watch(inv, frameEebounce((inv) => {
-    const newScale = inv.scaleX
-    // if (Math.abs(newScale - scale.value) > 0.05) {
-      scale.value = newScale
-    // }
-  }))
-  return scale
-}
+export const useViewerDebounce = installGlobalVar(() => ref(true))
+
+export const useFixedScale = installGlobalVar(() => {
+  const inv = useViewerInvertTransformConfig();
+  const debounce = useViewerDebounce()
+  const scale = ref(inv.value.scaleX) as Ref<number> & { debounce: boolean };
+
+  const update = () => {
+    const newScale = inv.value.scaleX;
+    scale.value = newScale;
+  };
+  
+  const debounceUpdate = frameEebounce(update);
+  const stopWatch = globalWatch(
+    () => [inv.value, debounce.value],
+    () => (debounce.value ? debounceUpdate() : update()),
+    { flush: "sync" }
+  );
+
+  return {
+    var: scale,
+    onDestroy: stopWatch,
+  };
+});
 
 export const useUnitTransform = installGlobalVar(() => {
   const transform = useViewerTransform();
@@ -238,7 +255,8 @@ export const useSetViewport = () => {
       .translate(center.x, center.y)
       .rotate(MathUtils.degToRad(config.value.rotation))
       .translate(-center.x, -center.y);
-    const start = mat.point({x: rect.x, y: rect.y});
+
+    const start = mat.point({ x: rect.x, y: rect.y });
     const end = mat.point({
       x: rect.x + rect.width,
       y: rect.y + rect.height,

+ 5 - 1
src/core/html-mount/propertys/hover-operate.vue

@@ -10,7 +10,11 @@
       >
         <ElMenu>
           <template v-for="menu in menus">
-            <ElMenuItem v-if="!menu.hide" @click="clickHandler(menu.handler)">
+            <ElMenuItem
+              v-if="!menu.hide"
+              :index="menu.label"
+              @click="clickHandler(menu.handler)"
+            >
               <span class="menu-item">{{ menu.label }}</span>
             </ElMenuItem>
           </template>

+ 34 - 17
src/core/renderer/renderer.vue

@@ -39,23 +39,36 @@
               v-bind="part.props"
             />
           </v-group>
-        </v-layer>
-        <!--	临时组,提供临时绘画,以及高频率渲染	-->
-        <v-layer :config="viewerConfig" id="temp">
-          <template v-if="mode.include(Mode.draw)">
-            <DrawShapeGroup v-for="type in types" :type="type" :key="type" />
+
+          <template v-if="mergeLayers">
+            <v-group :config="invViewerConfig">
+              <SnapLines />
+              <SplitLine v-if="expose.config.showLabelLine" />
+              <Compass v-if="config.showCompass" />
+              <layers v-if="store.layers.length > 1" />
+              <Debugger v-if="isDev" />
+              <Border />
+            </v-group>
           </template>
-          <TempShapeGroup v-for="type in types" :type="type" :key="type" />
-        </v-layer>
-        <v-layer id="helper">
-          <!-- <ActiveBoxs /> -->
-          <SnapLines />
-          <SplitLine v-if="expose.config.showLabelLine" />
-          <Compass v-if="config.showCompass" />
-          <layers v-if="store.layers.length > 1" />
-          <Debugger v-if="isDev" />
-          <Border />
         </v-layer>
+        <template v-if="!mergeLayers">
+          <!--	临时组,提供临时绘画,以及高频率渲染	-->
+          <v-layer :config="viewerConfig" id="temp">
+            <template v-if="mode.include(Mode.draw)">
+              <DrawShapeGroup v-for="type in types" :type="type" :key="type" />
+            </template>
+            <TempShapeGroup v-for="type in types" :type="type" :key="type" />
+          </v-layer>
+          <v-layer id="helper">
+            <!-- <ActiveBoxs /> -->
+            <SnapLines />
+            <SplitLine v-if="expose.config.showLabelLine" />
+            <Compass v-if="config.showCompass" />
+            <layers v-if="store.layers.length > 1" />
+            <Debugger v-if="isDev" />
+            <Border />
+          </v-layer>
+        </template>
       </v-stage>
     </div>
   </div>
@@ -84,7 +97,11 @@ import {
   useTempStatus,
 } from "../hook/use-global-vars.ts";
 import { useMode } from "../hook/use-status.ts";
-import { useViewerTransformConfig } from "../hook/use-viewer.ts";
+import {
+  useViewerInvertTransform,
+  useViewerInvertTransformConfig,
+  useViewerTransformConfig,
+} from "../hook/use-viewer.ts";
 import { useGlobalResize } from "../hook/use-event.ts";
 import { useAutoService, useExpose } from "../hook/use-expose.ts";
 import {
@@ -102,7 +119,6 @@ import { useConfig } from "../hook/use-config.ts";
 import { getEmptyStoreData } from "../store/store.ts";
 import { mergeFuns } from "@/utils/shared.ts";
 
-console.log("hja?");
 const instance = getCurrentInstance();
 install(instance!.appContext.app);
 
@@ -133,6 +149,7 @@ const stage = useStage();
 const { size, fix } = useGlobalResize();
 const layout = useRendererDOM();
 const viewerConfig = useViewerTransformConfig();
+const invViewerConfig = useViewerInvertTransformConfig();
 const types = Object.keys(components) as ShapeType[];
 const mode = useMode();
 const config = useConfig();

+ 33 - 0
src/core/store/compatible.ts

@@ -0,0 +1,33 @@
+import { joinKey, tableTitle } from "../components/serial";
+import { StoreData } from "./store";
+
+function versionToNumber(v: string) {
+  return v
+    .split(".")
+    .reduce((acc, part, i) => acc + parseInt(part) * Math.pow(1000, 2 - i), 0);
+}
+
+// v1.3.0 序号表格去除th  添加title
+const serialTableHandler13 = (data: StoreData) => {
+  for (const [_, layer] of Object.entries(data.layers)) {
+    const tables = layer.table || [];
+    for (const table of tables) {
+      if (table.key === joinKey) {
+        table.title = tableTitle;
+        const th = table.content.shift()!;
+        table.height -= th[0].height;
+      }
+    }
+  }
+};
+
+export const compatible = (data: StoreData) => {
+  const currentVersion = versionToNumber(data.version || '1.2.0')
+
+  if (currentVersion < versionToNumber('1.3.0')) {
+    serialTableHandler13(data);
+  }
+
+  data.version = __VERSION__;
+  return data;
+};

+ 168 - 164
src/core/store/store.ts

@@ -1,6 +1,9 @@
 import { defineStore } from "pinia";
 import { DrawData, DrawItem, ShapeType } from "../components";
 import { defaultLayer } from "@/constant";
+import { onlyId } from "@/utils/shared";
+import { installGlobalVar } from "../hook/use-global-vars";
+import { compatible } from "./compatible";
 
 export const sortFn = (
   a: Pick<DrawItem, "zIndex" | "createTime">,
@@ -19,194 +22,195 @@ export type StoreConfig = {
 export type StoreData = {
   layers: Record<string, DrawData>;
   config: StoreConfig;
+  version: string,
   __currentLayer: string;
 };
 const defConfig: StoreData["config"] = {
   compass: { rotation: 0, url: "icons/edit_compass.svg" },
   proportion: { scale: 1, unit: "px" },
-  strokeProportion: { scale: 1, unit: "px" }
+  strokeProportion: { scale: 1, unit: "px" },
 };
 
 export const getEmptyStoreData = (): StoreData => {
   return {
     layers: { [defaultLayer]: {} },
     config: { ...defConfig },
+    version: __VERSION__,
     __currentLayer: defaultLayer,
   };
 };
 
-export const useStoreRaw = defineStore("draw-data", {
-  state: () => ({ data: getEmptyStoreData() }),
-  getters: {
-    currentLayer() {
-      const layer = (this as any).data.__currentLayer;
-      const layers = (this as any).layers;
-      if (layers.includes(layer)) {
-        return layer;
-      } else {
-        return layers[0];
-      }
-    },
-    layers() {
-      const data = this.data as StoreData;
-      return Object.keys(data.layers);
-    },
-    typeItems() {
-      const data = this.data as StoreData;
-      const layer = (this as any).currentLayer;
-      return data.layers[layer] || {};
-    },
-    items() {
-      return Object.values(this.typeItems).flat() as DrawItem[];
-    },
-    sortItems() {
-      return (this.items as any).sort(sortFn) as DrawItem[];
-    },
-    config() {
-      return (this as any).data.config as StoreData["config"];
-    },
-  },
-  actions: {
-    setStore(store: Partial<StoreData>) {
-      const newStore = JSON.parse(JSON.stringify(store));
-      this.$patch((state) => {
-        state.data = {
-          ...state.data,
-          ...newStore,
-          config: {
-            ...state.data.config,
-            ...(newStore.config || {}),
-          },
-        };
-      });
-    },
-    setLayerStore(layerStore: DrawData) {
-      this.$patch((state) => {
-        state.data.layers[this.currentLayer] = layerStore;
-      });
-    },
-    getItemNdx<T extends ShapeType>(type: T, id: string) {
-      const items = this.typeItems[type];
+export const useStoreRaw = installGlobalVar(() =>
+  defineStore(onlyId(), {
+    state: () => ({ data: getEmptyStoreData() }),
+    getters: {
+      currentLayer() {
+        const layer = (this as any).data.__currentLayer;
+        const layers = (this as any).layers;
+        if (layers.includes(layer)) {
+          return layer;
+        } else {
+          return layers[0];
+        }
+      },
+      layers() {
+        const data = this.data as StoreData;
+        return Object.keys(data.layers);
+      },
+      typeItems() {
+        const data = this.data as StoreData;
+        const layer = (this as any).currentLayer;
+        return data.layers[layer] || {};
+      },
+      items() {
+        return Object.values(this.typeItems).flat() as DrawItem[];
+      },
+      sortItems() {
+        return (this.items as any).sort(sortFn) as DrawItem[];
+      },
+      config() {
+        return (this as any).data.config as StoreData["config"];
+      },
+    },
+    actions: {
+      setStore(store: Partial<StoreData>) {
+        const newStore = compatible(JSON.parse(JSON.stringify(store)));
+        this.$patch((state) => {
+          state.data = {
+            ...state.data,
+            ...newStore,
+            config: {
+              ...state.data.config,
+              ...(newStore.config || {}),
+            },
+          };
+        });
+      },
+      setLayerStore(layerStore: DrawData) {
+        this.$patch((state) => {
+          state.data.layers[this.currentLayer] = layerStore;
+        });
+      },
+      getItemNdx<T extends ShapeType>(type: T, id: string) {
+        const items = this.typeItems[type];
 
-      if (items) {
-        return items.findIndex((item) => item.id === id);
-      }
-      return -1;
-    },
-    getTypeItems<T extends ShapeType>(type: T): DrawItem<T>[] {
-      return (this.typeItems[type] as DrawItem<T>[]) || [];
-    },
-    getItem<T extends ShapeType>(type: T, id: string) {
-      const ndx = this.getItemNdx(type, id);
-      if (~ndx) return this.typeItems[type]![ndx];
-    },
-    addItems<T extends ShapeType>(type: T, items: DrawItem<T>[]) {
-      items.forEach((item) => this.addItem(type, item));
-    },
-    addItem<T extends ShapeType>(type: T, item: DrawItem<T>) {
-      const typeItems = this.typeItems;
-      this.$patch(() => {
-        if (!(type in typeItems)) {
-          typeItems[type] = [];
+        if (items) {
+          return items.findIndex((item) => item.id === id);
         }
-        typeItems[type]!.push(item as any);
-      });
-    },
-    delItem<T extends ShapeType>(type: T, id: string) {
-      const ndx = this.getItemNdx(type, id);
-      const typeItems = this.typeItems;
-      if (~ndx) {
+        return -1;
+      },
+      getTypeItems<T extends ShapeType>(type: T): DrawItem<T>[] {
+        return (this.typeItems[type] as DrawItem<T>[]) || [];
+      },
+      getItem<T extends ShapeType>(type: T, id: string) {
+        const ndx = this.getItemNdx(type, id);
+        if (~ndx) return this.typeItems[type]![ndx];
+      },
+      addItems<T extends ShapeType>(type: T, items: DrawItem<T>[]) {
+        items.forEach((item) => this.addItem(type, item));
+      },
+      addItem<T extends ShapeType>(type: T, item: DrawItem<T>) {
+        const typeItems = this.typeItems;
         this.$patch(() => {
-          typeItems[type]!.splice(ndx, 1);
+          if (!(type in typeItems)) {
+            typeItems[type] = [];
+          }
+          typeItems[type]!.push(item as any);
         });
-      }
-    },
-    setItem<T extends ShapeType>(
-      type: T,
-      playData: { value: Partial<DrawItem<T>>; id: string }
-    ) {
-      const typeItems = this.typeItems;
-      const ndx = this.getItemNdx(type, playData.id);
-      if (~ndx) {
-        this.$patch(() => {
-          console.log();
-          const old = typeItems[type]![ndx];
-          Object.assign(typeItems[type]![ndx], playData.value);
-          const newv = typeItems[type]![ndx];
+      },
+      delItem<T extends ShapeType>(type: T, id: string) {
+        const ndx = this.getItemNdx(type, id);
+        const typeItems = this.typeItems;
+        if (~ndx) {
+          this.$patch(() => {
+            typeItems[type]!.splice(ndx, 1);
+          });
+        }
+      },
+      setItem<T extends ShapeType>(
+        type: T,
+        playData: { value: Partial<DrawItem<T>>; id: string }
+      ) {
+        const typeItems = this.typeItems;
+        const ndx = this.getItemNdx(type, playData.id);
+        if (~ndx) {
+          this.$patch(() => {
+            Object.assign(typeItems[type]![ndx], playData.value);
+          });
+        }
+      },
+      getItemsZIndex(items?: DrawItem[]) {
+        if (!items) {
+          return this.sortItems;
+        } else {
+          return items.sort(sortFn);
+        }
+      },
+      getType(id: string) {
+        const typeItems = this.typeItems;
+        const types = Object.keys(typeItems) as ShapeType[];
+        for (const type of types) {
+          if (typeItems[type]?.some((item) => item.id === id)) {
+            return type;
+          }
+        }
+      },
+      getItemById(id: string) {
+        const types = Object.keys(this.typeItems) as ShapeType[];
+        for (const type of types) {
+          const item = this.typeItems[type]?.find((item) => item.id === id);
+          if (item) {
+            return item;
+          }
+        }
+      },
+      setConfig(config: Partial<StoreData["config"]>) {
+        this.$patch((state) => {
+          state.data.config = {
+            ...state.data.config,
+            ...config,
+          };
         });
-      }
-    },
-    getItemsZIndex(items?: DrawItem[]) {
-      if (!items) {
-        return this.sortItems;
-      } else {
-        return items.sort(sortFn);
-      }
-    },
-    getType(id: string) {
-      const typeItems = this.typeItems;
-      const types = Object.keys(typeItems) as ShapeType[];
-      for (const type of types) {
-        if (typeItems[type]?.some((item) => item.id === id)) {
-          return type;
+      },
+      setCurrentLayer(layer: string) {
+        if (!this.layers.includes(layer)) {
+          throw `不存在${layer}层`;
+        } else {
+          this.data.__currentLayer = layer;
         }
-      }
-    },
-    getItemById(id: string) {
-      const types = Object.keys(this.typeItems) as ShapeType[];
-      for (const type of types) {
-        const item = this.typeItems[type]?.find((item) => item.id === id);
-        if (item) {
-          return item;
+      },
+      clear() {
+        this.data.layers[this.currentLayer] = {};
+      },
+      addLayer(layer: string) {
+        if (this.layers.includes(layer)) {
+          throw `已存在${layer}层`;
+        } else {
+          this.data.layers[layer] = {};
         }
-      }
-    },
-    setConfig(config: Partial<StoreData["config"]>) {
-      this.$patch((state) => {
-        state.data.config = {
-          ...state.data.config,
-          ...config,
-        };
-      });
-    },
-    setCurrentLayer(layer: string) {
-      if (!this.layers.includes(layer)) {
-        throw `不存在${layer}层`;
-      } else {
-        this.data.__currentLayer = layer;
-      }
-    },
-    clear() {
-      this.data.layers[this.currentLayer] = {};
-    },
-    addLayer(layer: string) {
-      if (this.layers.includes(layer)) {
-        throw `已存在${layer}层`;
-      } else {
-        this.data.layers[layer] = {};
-      }
-    },
-    delLayer(layer: string, translateLayer?: string) {
-      if (!this.layers.includes(layer)) {
-        return;
-      }
-
-      if (translateLayer) {
-        if (!this.layers.includes(translateLayer)) {
-          throw `不存在${translateLayer}层`;
+      },
+      delLayer(layer: string, translateLayer?: string) {
+        if (!this.layers.includes(layer)) {
+          return;
         }
-        const layerData = this.data.layers[layer];
-        const tLayerData = this.data.layers[translateLayer];
-        const keys = Object.keys(layerData) as ShapeType[];
 
-        for (const key of keys) {
-          if (key in tLayerData && tLayerData[key]) {
-            tLayerData[key] = [...tLayerData[key], ...layerData[key]!] as any;
-          } else {
-            tLayerData[key] = layerData[key] as any;
+        if (translateLayer) {
+          if (!this.layers.includes(translateLayer)) {
+            throw `不存在${translateLayer}层`;
+          }
+          const layerData = this.data.layers[layer];
+          const tLayerData = this.data.layers[translateLayer];
+          const keys = Object.keys(layerData) as ShapeType[];
+
+          for (const key of keys) {
+            if (key in tLayerData && tLayerData[key]) {
+              tLayerData[key] = [...tLayerData[key], ...layerData[key]!] as any;
+            } else {
+              tLayerData[key] = layerData[key] as any;
+            }
           }
         }
-      }
+      },
     },
-  },
-});
+  })()
+);

+ 17 - 2
src/core/viewer.ts

@@ -91,10 +91,17 @@ export class Viewer {
     // this.move(tf.invert().point(position), this.viewMat)
   }
 
+  private min = 0.005
+  private max = 50
+  setScaleRange(min: number, max: number) {
+    this.min = min
+    this.max = max
+  }
+
   scale(center: Pos, scale: number, initMat = this.viewMat) {
     const base = initMat.decompose().scaleX;
-    const min = 0.005
-    const max = 50
+    const min = this.min
+    const max = this.max
     const isMin = base * scale < min
     const isMax = base * scale > max
     if (isMax || isMin) {
@@ -125,6 +132,14 @@ export class Viewer {
   }
 
   scalePixel(center: Pos, scale: number, initMat = this.viewMat) {
+    if (this.viewSize && this.size) {
+      const offsetX = (this.size.width - this.viewSize.width) / 2
+      const offsetY = (this.size.height - this.viewSize.height) / 2
+      center = {
+        x: center.x - offsetX,
+        y: center.y - offsetY,
+      }
+    }
     const pos = initMat.copy().invert().point(center);
     this.scale(pos, scale, initMat);
   }

+ 22 - 3
src/example/constant.ts

@@ -247,7 +247,6 @@ for (const icon of trIcons) {
   }
 }
 
-
 export const tableCoverKey = "__tableCoverKey";
 export const tableCoverScaleKey = "__tableCoverScaleKey";
 export const tableTableKey = "__tableTableKey";
@@ -258,5 +257,25 @@ export const tableCoverWidth = 370;
 export const tableCoverHeight = 306;
 
 // 像素转毫米比例
-export const overviewMMToPixel = 0.1
-export const overviewBorderMMToPixel = 7.69
+export const overviewMMToPixel = 0.1;
+export const overviewBorderMMToPixel = 7.69;
+
+if (import.meta.env.DEV) {
+  const icons = iconGroups.flatMap((group) =>
+    group.children.flatMap((itemGroup) =>
+      itemGroup.children.flatMap((item) => item.icon)
+    )
+  );
+  const prev = '../../public/icons/'
+  const svgModules = import.meta.glob('../../public/icons/*.svg');
+
+  // 示例:获取所有路径
+  const unusesdIcons: string[] = []
+  for (const path in svgModules) {
+    const name = path.substring(prev.length, path.length - 4)
+    if (!icons.includes(name)) {
+      unusesdIcons.push(name)
+    }
+  }
+  console.log('未使用svg: ', unusesdIcons)
+}

+ 3 - 3
src/example/fuse/enter.ts

@@ -20,9 +20,9 @@ window.platform.login = (isBack = true) => {
   if (import.meta.env.DEV) {
     platform
       .post("/service/manage/login", {
-        password: "Di8r5tFpExMjM0NTY=F39Vd0znQWfBY7W9iG",
-        username: "W测试2",
-        userName: "W测试2",
+        password: "SeJhJBbaExMjM0NTY=SoWosNzcQWC8T7xb06",
+        username: "super-admin",
+        userName: "super-admin",
       })
       .then((res) => {
         params.value.token = res.token;

+ 92 - 0
src/example/fuse/views/overview/actions.ts

@@ -0,0 +1,92 @@
+import { DataGroupId } from "@/constant";
+import { Mode } from "@/constant/mode";
+import { Draw } from "@/example/components/container/use-draw";
+import { tableCoverHeight, tableCoverWidth } from "@/example/constant";
+import { nextTick } from "vue";
+import { TabCover } from "../../store";
+import { Group } from "konva/lib/Group";
+
+export const setViewToTableCover = async (
+  draw: Draw,
+  size?: { width?: number; height?: number },
+  padding = 70
+) => {
+  const oldViewMat = draw.viewer.viewMat;
+  const oldShowGrid = draw.config.showGrid;
+  const oldSize = draw.config.size;
+  const oldBack = draw.config.back;
+  const oldShowCompass = draw.config.showCompass;
+  const oldLabelLineConfig = { ...draw.config.labelLineConfig };
+  const oldShowOffset = draw.config.labelLineConfig.showOffset;
+  draw.initViewport(0);
+  await nextTick();
+  const getRect = (id: string) =>
+    draw.stage!.findOne<Group>(`#${id}`)?.getClientRect();
+
+  const pop = draw.mode.push(Mode.readonly);
+  const rect = getRect(DataGroupId)!;
+
+  if (!size || (!size.height && !size.width)) {
+    size = {
+      width: rect.width,
+      height: rect.height,
+    };
+  } else {
+    if (!size.width) {
+      size.width = (rect.width / rect.height) * size.height!;
+    } else {
+      size.height = (rect.height / rect.width) * size.width!;
+    }
+  }
+
+  let width = size.width!;
+  let height = size.height!;
+  const rectScale = width / height;
+  const tableCoverScale = tableCoverWidth / tableCoverHeight;
+
+  if (rectScale > tableCoverScale) {
+    height = width / tableCoverScale;
+  } else {
+    width = tableCoverScale * height;
+  }
+  if (width < padding * 2) {
+    width += padding * 2;
+  }
+  if (height < padding * 2) {
+    height += padding * 2;
+  }
+
+  draw.config.size = { width, height };
+  draw.config.showGrid = false;
+  draw.config.back = undefined;
+  draw.config.showCompass = false;
+
+  draw.config.labelLineConfig.type = "auto";
+  draw.config.labelLineConfig.fontSize = width / 60;
+  draw.config.labelLineConfig.strokeWidth =
+    draw.config.labelLineConfig.fontSize / 10;
+
+  await nextTick();
+  draw.config.labelLineConfig.showOffset = padding - 5;
+  draw.initViewport(padding);
+  draw.viewerDebounce = false
+  await nextTick();
+  draw.viewerDebounce = true
+
+  return [
+    {
+      width,
+      height,
+    },
+    () => {
+      pop();
+      draw.config.size = oldSize;
+      draw.config.showGrid = oldShowGrid;
+      draw.config.back = oldBack;
+      draw.config.showCompass = oldShowCompass;
+      draw.config.labelLineConfig = oldLabelLineConfig;
+      draw.config.labelLineConfig.showOffset = oldShowOffset;
+      draw.viewer.setViewMat(oldViewMat);
+    },
+  ] as const;
+};

+ 22 - 131
src/example/fuse/views/overview/header.vue

@@ -23,25 +23,17 @@ import { useDraw } from "../../../components/container/use-draw.ts";
 import { selectScene } from "../../../dialog/vr/index.ts";
 import { Scene } from "../../../platform/platform-resource.ts";
 import { getHeaderActions, getImage } from "../../../components/header/actions.ts";
-import {
-  tabulationData,
-  refreshTabulationData,
-  tableCoverWidth,
-  tableCoverHeight,
-  overviewData,
-  TabCover,
-} from "../../store.ts";
+import { tabulationData, refreshTabulationData, overviewData } from "../../store.ts";
 import { nextTick, onUnmounted } from "vue";
-import { DataGroupId } from "@/constant/index.ts";
-import { Group } from "konva/lib/Group";
 import { Mode } from "@/constant/mode.ts";
 import { lineLen } from "@/utils/math.ts";
 import { repTabulationStore } from "../tabulation/gen-tab.ts";
 import { router } from "../../router.ts";
 import { overviewId, params, tabulationId } from "@/example/env.ts";
 import { listener } from "@/utils/event.ts";
-import { mergeFuns, repeatedlyOnly } from "@/utils/shared.ts";
+import { asyncTimeout, mergeFuns, repeatedlyOnly } from "@/utils/shared.ts";
 import saveAs from "@/utils/file-serve.ts";
+import { setViewToTableCover } from "./actions.ts";
 
 const props = defineProps<{ title: string }>();
 const draw = useDraw();
@@ -81,7 +73,7 @@ const actions = [
           if (format !== "dxf") {
             const blob = await draw.enterTemp(async () => {
               const back = draw.config.back;
-              const [_, _a, recover] = await setViewToTableCover();
+              const [_, recover] = await setViewToTableCover(draw);
               if (format === "jpg") draw.config.back = back;
               await nextTick();
               const blob = await getImage(draw, `image/${format}`);
@@ -104,89 +96,6 @@ const actions = [
   ],
 ];
 
-const setViewToTableCover = async () => {
-  const oldViewMat = draw.viewer.viewMat;
-  const oldShowGrid = draw.config.showGrid;
-  const oldSize = draw.config.size;
-  const oldBack = draw.config.back;
-  const oldShowCompass = draw.config.showCompass;
-  const oldLabelLineConfig = { ...draw.config.labelLineConfig };
-  const oldShowOffset = draw.config.labelLineConfig.showOffset;
-  draw.initViewport(0);
-  await nextTick();
-  const getRect = (id: string) => draw.stage!.findOne<Group>(`#${id}`)?.getClientRect();
-
-  const pop = draw.mode.push(Mode.readonly);
-  const rect = getRect(DataGroupId)!;
-
-  let width = rect.width;
-  let height = rect.height;
-  const rectScale = width / height;
-  const tableCoverScale = tableCoverWidth / tableCoverHeight;
-  const padding = 70;
-
-  if (rectScale > tableCoverScale) {
-    height = width / tableCoverScale;
-  } else {
-    width = tableCoverScale * height;
-  }
-  if (width < padding * 2) {
-    width += padding * 2;
-  }
-  if (height < padding * 2) {
-    height += padding * 2;
-  }
-
-  draw.config.size = { width, height };
-  draw.config.showGrid = false;
-  draw.config.back = undefined;
-  draw.config.showCompass = false;
-
-  draw.config.labelLineConfig.type = "auto";
-  draw.config.labelLineConfig.fontSize = width / 60;
-  draw.config.labelLineConfig.strokeWidth = draw.config.labelLineConfig.fontSize / 10;
-
-  await nextTick();
-  draw.config.labelLineConfig.showOffset = padding - 5;
-  draw.initViewport(padding);
-  await nextTick();
-
-  const syncItems: TabCover["syncItems"] = [];
-  // const positionRect = getRect("formal")!;
-  // draw.store.items.forEach((item) => {
-  //   if (item.key === "trace") {
-  //     const itemRect = getRect(item.id);
-  //     if (itemRect) {
-  //       itemRect.x = itemRect.x - positionRect.x;
-  //       itemRect.y = itemRect.y - positionRect.y;
-  //       syncItems.push({
-  //         ...item,
-  //         rect: itemRect,
-  //         desc: (item as any).name || "",
-  //       } as any);
-  //     }
-  //   }
-  // });
-
-  return [
-    {
-      width,
-      height,
-    },
-    syncItems,
-    () => {
-      pop();
-      draw.config.size = oldSize;
-      draw.config.showGrid = oldShowGrid;
-      draw.config.back = oldBack;
-      draw.config.showCompass = oldShowCompass;
-      draw.config.labelLineConfig = oldLabelLineConfig;
-      draw.config.labelLineConfig.showOffset = oldShowOffset;
-      draw.viewer.setViewMat(oldViewMat);
-    },
-  ] as const;
-};
-
 const setViewToKanKanCover = async () => {
   const oldViewMat = draw.viewer.viewMat;
   const oldBack = draw.config.back && { ...draw.config.back };
@@ -239,6 +148,7 @@ const setViewToKanKanCover = async () => {
 
   draw.initViewport(70);
   await nextTick();
+  await asyncTimeout(100);
   const blob = await getImage(draw, "image/png");
   mergeFuns(clearupItems)();
   pop();
@@ -252,36 +162,27 @@ const setViewToKanKanCover = async () => {
 
 const saveHandler = repeatedlyOnly(async () => {
   const storeData = draw.getData();
-  const [tabBlob, listBlob, kkBlob, scale, rect, syncItems] = await draw.enterTemp(
-    async () => {
-      const back = draw.config.back;
-      const [rect, syncItems, recover] = await setViewToTableCover();
-      await nextTick();
-      const mat = draw.viewer.transform.invert();
-      const scale =
-        lineLen(mat.point({ x: 1, y: 0 }), mat.point({ x: 0, y: 0 })) *
-        draw.store.config.proportion.scale;
-      const tabBlob = await getImage(draw, "image/png");
-      draw.config.back = back;
-      await nextTick();
-      const listBlob = await getImage(draw, "image/jpg");
-      recover();
-      await nextTick();
-
-      const kkBlob = await setViewToKanKanCover();
-      return [tabBlob, listBlob, kkBlob, scale, rect, syncItems] as const;
-    }
-  );
+  const [listBlob, kkBlob] = await draw.enterTemp(async () => {
+    const back = draw.config.back;
+    const [_, recover] = await setViewToTableCover(draw);
+    draw.config.back = back;
+    await nextTick();
+    const listBlob = await getImage(draw, "image/jpg");
+    recover();
+    await nextTick();
+
+    const kkBlob = await setViewToKanKanCover();
+    return [listBlob, kkBlob] as const;
+  });
 
-  let tabUrl = null;
   let listUrl = null;
   let kankanUrl = null;
-  if (!tabBlob || !listBlob || !kkBlob) {
+  if (!listBlob || !kkBlob) {
     ElMessage.error("截图保存失败");
   } else {
     console.error(window.platform.uploadResourse);
-    [tabUrl, listUrl, kankanUrl] = await Promise.all([
-      window.platform.uploadResourse(new File([tabBlob], `tabulation-cover.png`)),
+    [listUrl, kankanUrl] = await Promise.all([
+      // window.platform.uploadResourse(new File([tabBlob], `tabulation-cover.png`)),
       window.platform.uploadResourse(new File([listBlob], `list-cover.png`)),
       window.platform.uploadResourse(new File([kkBlob], `kankan-cover.png`)),
     ]);
@@ -290,23 +191,13 @@ const saveHandler = repeatedlyOnly(async () => {
   tabulationId.value = await window.platform.getTabulationId(overviewId.value);
   await refreshTabulationData();
 
-  const cover = {
-    url: tabUrl,
-    width: rect.width,
-    height: rect.height,
-    proportion: { ...draw.store.config.proportion, scale },
-    syncItems,
-  };
-
-  console.log(tabulationData.value.store);
   const tabStore = await repTabulationStore(
     tabulationData.value.paperKey,
     storeData.config.compass.rotation,
-    cover,
+    undefined,
     tabulationData.value.isAutoGen ? undefined : tabulationData.value.store
   );
 
-  console.log(tabStore);
   tabStore.config.compass = storeData.config.compass;
   const body: any = {
     ...overviewData.value,
@@ -317,7 +208,7 @@ const saveHandler = repeatedlyOnly(async () => {
       ...tabulationData.value,
       id: tabulationId.value,
       title: overviewData.value.title,
-      cover,
+      cover: null,
       store: tabStore,
       overviewId: overviewId.value,
     },

+ 11 - 3
src/example/fuse/views/overview/index.vue

@@ -1,8 +1,16 @@
 <template>
-  <Container :upload-resourse="uploadResourse" v-model:full="full" :ref="(d: any) => (draw = d?.draw)">
+  <Container
+    :upload-resourse="uploadResourse"
+    v-model:full="full"
+    :ref="(d: any) => (draw = d?.draw)"
+  >
     <template #header>
-      <Header @selectVR="(scene) => (vrScene = scene)" title="绘图" @save-after="() => mergeFuns(saveAfterHandlers)()"
-        :ref="(r) => (header = r)" />
+      <Header
+        @selectVR="(scene) => (vrScene = scene)"
+        title="绘图"
+        @save-after="() => mergeFuns(saveAfterHandlers)()"
+        :ref="(r) => (header = r)"
+      />
     </template>
     <template #slide>
       <Slide />

+ 7 - 1
src/example/fuse/views/tabulation/gen-tab.ts

@@ -53,10 +53,17 @@ export const transformPaper = (
 
 export const getCoverPaperScale = (cover: ImageData, paperKey: PaperKey) => {
   let pixelScale = (cover.widthRaw! / cover.width) * cover.proportion!.scale;
+
   const realPixelScale = paperConfigs[paperKey].scale;
   return realPixelScale * pixelScale;
 };
 
+export const getCoverWidthRaw = (cover: ImageData, paperKey: PaperKey, pp: number) => {
+  const pixelScale = pp / paperConfigs[paperKey].scale
+  const widthRaw = (pixelScale / cover.proportion!.scale ) * cover.width
+  return widthRaw
+}
+
 export const setCoverPaperScale = (
   cover: ImageData,
   paperKey: PaperKey,
@@ -66,7 +73,6 @@ export const setCoverPaperScale = (
   cover.width =
     cover.widthRaw! / (scale / realPixelScale / cover.proportion!.scale);
   cover.height = (cover.heightRaw! / cover.widthRaw!) * cover.width;
-  console.log(cover.width, cover.height);
 };
 
 export const genTabulationData = async (

+ 13 - 2
src/example/fuse/views/tabulation/header.vue

@@ -32,16 +32,18 @@ import { overviewId, tabulationId } from "@/example/env.ts";
 import { listener } from "@/utils/event.ts";
 import { router } from "../../router.ts";
 import { initViewport, paperConfigs } from "@/example/components/slide/actions.ts";
+import { asyncTimeout } from "@/utils/shared.ts";
 
 const props = defineProps<{ title: string }>();
+const emit = defineEmits<{ (e: "screenshot", val: boolean): void }>();
 
 const draw = useDraw();
 const getCoverImage = (format: string) => {
+  emit("screenshot", true);
   return draw.enterTemp(async () => {
     const pop = draw.mode.push(Mode.readonly);
     const oldMat = draw.viewer.viewMat;
     draw.viewer.setViewMat([1, 0, 0, 1, 0, 0]);
-    await nextTick();
 
     const viewSize = draw.viewer.viewSize!;
     const size = {
@@ -53,6 +55,7 @@ const getCoverImage = (format: string) => {
       y: (size.height - viewSize.height) / 2,
       ...viewSize,
     };
+    await asyncTimeout(16);
 
     let fileBlob = await (draw.stage!.toBlob({
       pixelRatio: 2,
@@ -62,6 +65,8 @@ const getCoverImage = (format: string) => {
     }) as Promise<Blob>);
     draw.viewer.setViewMat(oldMat);
     pop();
+
+    emit("screenshot", false);
     return [rect, fileBlob] as const;
   });
 };
@@ -72,7 +77,13 @@ const actions = [
     // baseActions.clear,
     {
       ...baseActions.initViewport,
-      handler: initViewport.bind(null, draw, 0.05),
+      handler: () => {
+        if (import.meta.env.DEV) {
+          draw.viewer.setViewMat(new Transform());
+        } else {
+          initViewport(draw, 0.05);
+        }
+      },
     },
   ],
   [

+ 290 - 133
src/example/fuse/views/tabulation/index.vue

@@ -2,16 +2,28 @@
   <Container
     :upload-resourse="uploadResourse"
     v-model:full="full"
-    :ref="(d: any) => (draw = d?.draw)"
+    v-if="dataLoaded"
+    :ref="(d: any) => initDraw(d?.draw)"
   >
     <template #header>
-      <Header v-if="inited" title="图纸" />
+      <Header v-if="draw" title="图纸" @screenshot="(val) => (needScreenshot = val)" />
     </template>
     <template #slide>
-      <Slide v-if="inited" @update-map-image="setMapHandler" />
+      <Slide v-if="draw" @update-map-image="setMapHandler" />
     </template>
   </Container>
 
+  <Preview
+    @change-origin="(origin) => setOrigin && setOrigin(origin)"
+    :cover-scale="coverSetting?.scale"
+    :paperKey="tabulationData?.paperKey"
+    :pixelRatio="needScreenshot ? 4 : pixelRatio"
+    :not-debounce="needScreenshot"
+    :data="overviewData?.store"
+    :scale="draw?.viewerConfig.scaleX"
+    @change-config="(config) => (originConfig = config)"
+    v-if="overviewData?.store.config?.proportion.scale"
+  />
   <Dialog />
 </template>
 
@@ -19,7 +31,16 @@
 import Header from "./header.vue";
 import Slide from "./slide.vue";
 import Container from "../../../components/container/container.vue";
-import { computed, nextTick, ref, watch, watchEffect } from "vue";
+import {
+  computed,
+  nextTick,
+  onUnmounted,
+  reactive,
+  ref,
+  shallowRef,
+  watch,
+  watchEffect,
+} from "vue";
 import { Draw } from "../../../components/container/use-draw";
 import Dialog from "../../../dialog/dialog.vue";
 import {
@@ -28,16 +49,12 @@ import {
   tableCoverKey,
   tableCoverScaleKey,
   tableCompassKey,
+  overviewData,
+  refreshOverviewData,
 } from "../../store";
 import { ImageData } from "@/core/components/image";
 import { TextData } from "@/core/components/text";
-import {
-  genTabulationData,
-  getCoverPaperScale,
-  getRealPixel,
-  repTabulationStore,
-  setCoverPaperScale,
-} from "./gen-tab";
+import { genTabulationData, getRealPixel, repTabulationStore } from "./gen-tab";
 import { IconData } from "@/core/components/icon";
 import { Transform } from "konva/lib/Util";
 import { MathUtils } from "three";
@@ -48,10 +65,21 @@ import { getImageSize } from "@/utils/shape";
 import { tabCustomStyle } from "../defStyle";
 import { defaultLayer } from "@/constant";
 import { Size } from "@/utils/math";
+import Preview from "./overview-viewport.vue";
+import { Image } from "konva/lib/shapes/Image";
+import { Group } from "konva/lib/Group";
+import { getBaseItem } from "@/core/components/util";
+import {
+  getPaperConfig,
+  paperConfigs,
+  PaperKey,
+} from "@/example/components/slide/actions";
+import { getFixPosition } from "@/utils/bound";
 
 const uploadResourse = window.platform.uploadResourse;
 const full = ref(false);
 const draw = ref<Draw>();
+const pixelRatio = ref(window.devicePixelRatio);
 
 const setMap = async (config: { url: string; size: Size }) => {
   const store = tabulationData.value.store;
@@ -120,111 +148,158 @@ const setMapHandler = async (config: { url: string; size: Size }) => {
   }
 };
 
-const inited = ref(false);
-const init = async (draw: Draw) => {
-  const quitMerges: Array<() => void> = [];
-  await refreshTabulationData();
-  draw.config.showCompass = false;
-  draw.config.showGrid = false;
-  draw.config.showLabelLine = false;
-  draw.config.showComponentSize = false;
-
-  const config: any = tabulationData.value.store.config || {};
-  const data = tabulationData.value;
-  const p = tabulationData.value.paperKey;
-  await setMap({ url: data.mapUrl!, size: { width: data.width!, height: data.high! } });
-  draw.store.setStore({
-    ...tabulationData.value.store,
-    config: {
-      ...config,
-      compass: {
-        rotation: 0,
-        ...(config.compass || {}),
-        url: "./icons/compass.svg",
-      },
-      proportion: {
-        scale: 1 / getRealPixel(1, p),
-        unit: "mm",
+const needScreenshot = ref(false);
+const coverSetting = computed(() => {
+  const cover = draw.value?.store.items.find((item) => item.key === tableCoverKey);
+  return cover?.userData as { showScale: boolean; scale: number } | undefined;
+});
+let setOrigin: (canvas: HTMLCanvasElement) => void;
+const originConfig = shallowRef<{ size: Size; scale: number }>();
+const setCover = (paperKey: PaperKey, draw: Draw) => {
+  const cleanups: (() => void)[] = [];
+  const cleanup = () => {
+    mergeFuns(cleanups)();
+    cleanups.length = 0;
+  };
+  let cover = draw.store.items.find((item) => item.key === tableCoverKey) as ImageData;
+  if (!cover) {
+    cover = reactive({
+      ...getBaseItem(),
+      cornerRadius: 0,
+      strokeWidth: 0,
+      disableDelete: true,
+      key: tableCoverKey,
+      disableTransformer: true,
+      userData: {
+        showScale: true,
+        scale: 0,
       },
-      strokeProportion: {
-        scale: 1 / getRealPixel(1, p),
-        unit: "mm",
+      zIndex: -1,
+      width: 0,
+      height: 0,
+      mat: [1, 0, 0, 1, 0, 0],
+      itemName: "比例",
+    });
+    const stopWatch = watch(
+      originConfig,
+      (config) => {
+        if (!config) return;
+        const { margin, size } = getPaperConfig(
+          paperConfigs[paperKey].size,
+          paperConfigs[paperKey].scale
+        );
+        const pos = getFixPosition(
+          {
+            left: config.size.width / 2 + margin[3] + getRealPixel(15, paperKey),
+            bottom: -config.size.height / 2 + margin[2] + getRealPixel(15, paperKey),
+          },
+          config.size,
+          size
+        );
+        const mat = new Transform().translate(pos.x, pos.y).m;
+
+        draw.history.preventTrack(() => {
+          draw.store.setItem("image", {
+            id: cover.id,
+            value: {
+              mat: mat,
+              userData: {
+                ...cover.userData,
+                scale: config.scale,
+              },
+            },
+          });
+        });
+        nextTick(() => stopWatch());
       },
-    },
-  });
-  inited.value = true;
-  quitMerges.push(tabCustomStyle(p, draw));
-  return () => mergeFuns(quitMerges)();
-};
-watch(draw, (draw, _, onCleanup) => {
-  if (draw) {
-    const quits: (() => void)[] = [
-      mergeFuns(
-        Object.keys(components).map((type) =>
-          draw.menusFilter.setMenusFilter(type as ShapeType, (items) => {
-            return items.filter((item) => item.label !== "隐藏");
-          })
-        )
-      ),
-    ];
+      { flush: "sync", immediate: true }
+    );
+    cleanups.push(stopWatch);
+    draw.store.addItem("image", cover);
+  } else {
+    delete cover.url;
+  }
 
-    let des = false;
-    let unInit: () => void;
-    init(draw).then((_unInit) => {
-      if (!des) {
-        unInit = _unInit;
-      } else {
-        _unInit();
-      }
-    });
-    quits.push(() => {
-      unInit && unInit();
-      des = true;
+  let text = draw.store.items.find((item) => item.key === tableCoverScaleKey) as TextData;
+  if (!text) {
+    text = reactive({
+      ...getBaseItem(),
+      content: ``,
+      width: getRealPixel(30, paperKey),
+      heihgt: getRealPixel(4.8, paperKey),
+      fontSize: getRealPixel(4, paperKey),
+      disableEditText: true,
+      key: tableCoverScaleKey,
+      disableDelete: true,
+      align: "center",
+      mat: [1, 0, 0, 1, 0, 0],
     });
-    onCleanup(mergeFuns(quits));
+    draw.store.addItem("text", text);
+    let stopWatch = watch(
+      () => cover.width && cover.height,
+      (loaded) => {
+        if (loaded) {
+          const mat = [...cover.mat];
+          const x = mat[4] - (text.width || 0) / 2;
+          const y = mat[5] + cover.height / 2 + getRealPixel(5, paperKey);
+          mat[4] = x;
+          mat[5] = y;
+          draw.history.preventTrack(() => {
+            draw.store.setItem("text", { id: text.id, value: { mat } });
+          });
+          nextTick(() => {
+            stopWatch();
+          });
+        }
+      }
+    );
+    cleanups.push(stopWatch);
   }
-});
-
-const cover = computed(
-  () => draw.value?.store.items.find((item) => item.key === tableCoverKey) as ImageData
-);
-watchEffect(() => {
-  if (cover.value && draw.value) {
-    draw.value.excludeSelection.push(cover.value.id);
+  // 兼容旧数据
+  if (!cover.userData) {
+    const scale =
+      (cover.widthRaw! / cover.width) *
+      paperConfigs[paperKey].scale *
+      cover.proportion!.scale;
+    console.log("兼容旧cover");
+    cover.userData = {
+      showScale: true,
+      scale: round(scale, 1),
+    };
   }
-});
-const coverScale = computed({
-  get: () =>
-    cover.value &&
-    Math.round(getCoverPaperScale(cover.value, tabulationData.value.paperKey)),
-  set: (val) => {
-    setCoverPaperScale(cover.value, tabulationData.value.paperKey, val);
-  },
-});
-watch(cover, (cover, _, onCleanup) => {
-  if (!cover || !draw.value || !cover.widthRaw || !cover.heightRaw || !cover.proportion)
-    return;
-  const mountMenus = draw.value.mountFilter;
-  const menusFilter = draw.value.menusFilter;
-  const quits = [
-    watchEffect(() => {
-      console.log(cover);
-      console.log("当前图片宽高:", cover.width, cover.height);
-      console.log("当前图片真实宽高:", cover.widthRaw, cover.heightRaw);
-      console.log("当前图片比例:", { ...cover.proportion });
-    }),
+
+  const coverQue = computed(() => draw.store.getItem("image", cover.id));
+  const coverTexQue = computed(() => draw.store.getItem("text", text.id));
+
+  const mountMenus = draw.mountFilter;
+  const menusFilter = draw.menusFilter;
+  draw.excludeSelection.push(cover.id);
+
+  cleanups.push(
+    watch(
+      coverSetting,
+      (setting) => {
+        if (coverTexQue.value) {
+          coverTexQue.value.content = setting ? `1:${setting.scale}` : "";
+          coverTexQue.value.hide = !setting?.showScale;
+        }
+      },
+      { deep: true }
+    ),
     menusFilter.setShapeMenusFilter(cover.id, () => []),
-    mountMenus.setShapeMenusFilter(cover.id, (des) => ({
-      ...des,
+    mountMenus.setShapeMenusFilter(cover.id, (menus) => ({
+      ...menus,
       scale: {
         type: "fixProportion",
         label: "缩放比例",
         "layout-type": "row",
         get value() {
-          return coverScale.value;
+          return coverSetting.value?.scale;
         },
         set value(val) {
-          coverScale.value = Math.max(val || 0, 1);
+          if (coverSetting.value) {
+            coverSetting.value.scale = Math.max(val || 0, 1);
+          }
         },
         props: { min: 1 },
       },
@@ -233,47 +308,129 @@ watch(cover, (cover, _, onCleanup) => {
         label: "显示比例",
         "layout-type": "row",
         get value() {
-          return (cover as any).showScale || false;
+          return coverSetting.value?.showScale;
         },
         set value(val) {
-          (cover as any).showScale = val;
+          if (coverSetting.value) {
+            coverSetting.value.showScale = val || false;
+          }
         },
         props: {},
       },
     })),
-  ];
-  onCleanup(mergeFuns(quits));
-});
+    watch(coverQue, (cover) => {
+      if (!cover) {
+        draw.history.preventTrack(() => {
+          draw.store.delItem("text", text.id);
+        });
+        cleanup();
+      }
+    }),
+    watch(
+      originConfig,
+      (config, _) => {
+        if (coverQue.value) {
+          coverQue.value.width = config?.size.width || 0;
+          coverQue.value.height = config?.size.height || 0;
+        }
+      },
+      { deep: true, immediate: true }
+    )
+  );
 
-const coverScaleText = computed(() => {
-  return draw.value?.store.items.find(
-    (item) => item.key === tableCoverScaleKey
-  ) as TextData;
-});
-watch(
-  () => {
-    return {
-      text: coverScaleText.value,
-      exists: cover.value,
-      show: (cover.value as any)?.showScale,
-      content: coverScale.value ? `1:${coverScale.value}` : "",
-    };
-  },
-  ({ text, show, exists, content }) => {
-    if (!text) return;
-    draw.value!.history.preventTrack(() => {
-      if (!exists) {
-        draw.value!.store.delItem("text", text.id);
-      } else {
-        draw.value!.store.setItem("text", {
-          value: { hide: !show, content },
-          id: text.id,
+  setOrigin = (origin) => {
+    const stage = draw.stage!;
+    const $group = stage.findOne<Group>(`#${cover.id}`);
+    const $image = $group?.findOne<Image>(".repShape");
+    if (!$group || !$image) return;
+    // cover.url = origin!.toDataURL();
+    $image.image(origin);
+  };
+  return cleanup;
+};
+
+let clearDraw: (() => void) | undefined;
+onUnmounted(() => (clearDraw = undefined));
+const dataLoaded = ref(false);
+Promise.all([refreshOverviewData(), refreshTabulationData()]).then(
+  () => (dataLoaded.value = true)
+);
+
+const lowVersionHandler = (_draw: Draw) => {
+  // 旧数据处理
+  _draw.history.preventTrack(() => {
+    _draw.store.getTypeItems("image").forEach((item) => {
+      if (item.key === tableCoverKey) {
+        _draw.store.setItem("image", {
+          value: {
+            itemName: undefined,
+            disableDelete: false,
+          },
+          id: item.id,
         });
       }
     });
-  },
-  { flush: "post" }
-);
+  });
+};
+
+const initDraw = async (_draw: Draw) => {
+  if (_draw === draw.value) return;
+  clearDraw && clearDraw();
+  if (!_draw) return;
+  const quitMerges: Array<() => void> = [];
+
+  _draw.config.showCompass = false;
+  _draw.config.showGrid = false;
+  _draw.config.showLabelLine = false;
+  _draw.config.showComponentSize = false;
+  _draw.viewer.setScaleRange(0.2, 5);
+  const config: any = tabulationData.value.store.config || {};
+  const data = tabulationData.value;
+  const p = tabulationData.value.paperKey;
+  await setMap({ url: data.mapUrl!, size: { width: data.width!, height: data.high! } });
+  _draw.store.setStore({
+    ...tabulationData.value.store,
+    config: {
+      ...config,
+      compass: {
+        rotation: 0,
+        ...(config.compass || {}),
+        url: "./icons/compass.svg",
+      },
+      proportion: {
+        scale: 1 / getRealPixel(1, p),
+        unit: "mm",
+      },
+      strokeProportion: {
+        scale: 1 / getRealPixel(1, p),
+        unit: "mm",
+      },
+    },
+  });
+  quitMerges.push(tabCustomStyle(p, _draw));
+  if (overviewData.value.store?.config?.proportion.scale) {
+    _draw.history.preventTrack(() => {
+      const quit = setCover(p, _draw);
+      quit && quitMerges.push(quit);
+    });
+  } else {
+    lowVersionHandler(_draw);
+  }
+  quitMerges.concat(
+    Object.keys(components).map((type) =>
+      _draw.menusFilter.setMenusFilter(type as ShapeType, (items) => {
+        return items.filter((item) => item.label !== "隐藏");
+      })
+    )
+  );
+  clearDraw = () => {
+    console.error("clear");
+    draw.value = undefined;
+    mergeFuns(quitMerges)();
+    clearDraw = undefined;
+  };
+  draw.value = _draw;
+};
 
 const compass = computed(
   () => draw.value?.store.items.find((item) => item.key === tableCompassKey) as IconData

+ 254 - 0
src/example/fuse/views/tabulation/overview-viewport.vue

@@ -0,0 +1,254 @@
+<template>
+  <div class="content" ref="drawEle">
+    <DrawBoard
+      v-if="drawEle"
+      :merge-layers="true"
+      :ref="(d: any) => initDraw(d)"
+      :handler-resource="uploadResourse"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { computed, nextTick, ref, watch, watchEffect } from "vue";
+import { Draw } from "../../../components/container/use-draw";
+import { DrawBoard } from "@/index";
+import { debounce, round } from "@/utils/shared";
+import { StoreData } from "@/core/store/store";
+import { Group } from "konva/lib/Group";
+import { lineLen, Size } from "@/utils/math";
+import { paperConfigs, PaperKey } from "@/example/components/slide/actions";
+import { IRect } from "konva/lib/types";
+import { DataGroupId } from "@/constant";
+import { tableCoverHeight, tableCoverWidth } from "@/example/constant";
+import { Transform } from "konva/lib/Util";
+import { Mode } from "@/constant/mode";
+
+const props = defineProps<{
+  scale?: number;
+  data?: StoreData;
+  coverScale?: number;
+  paperKey?: PaperKey;
+  pixelRatio?: number;
+  notDebounce?: boolean;
+}>();
+const emit = defineEmits<{
+  (e: "changeConfig", config: { size: Size; scale: number }): void;
+  (e: "changeOrigin", origin: HTMLCanvasElement): void;
+}>();
+const uploadResourse = window.platform.uploadResourse;
+const draw = ref<Draw>();
+const initDraw = (d: Draw) => {
+  if (d) {
+    d.viewerDebounce = false;
+    d.config.showComponentSize = false;
+    d.config.showGrid = false;
+    d.mode.add(Mode.readonly);
+  }
+  draw.value = d;
+};
+const drawEle = ref<HTMLDivElement | null>(null);
+
+const loaded = ref(false);
+watch(
+  () => [props.data, props.data, draw.value],
+  async (_a, _b, onCleanup) => {
+    if (!props.data || !draw.value) return;
+    await nextTick();
+    if (!props.data || !draw.value) {
+      loaded.value = false;
+    } else {
+      draw.value.store.setStore(props.data);
+      const images = draw.value.store
+        .getTypeItems("image")
+        .filter((image) => !image.hide);
+
+      const stage = draw.value.stage!;
+      const interval = setInterval(() => {
+        const load = images.every((image) => {
+          const $group = stage.findOne<Group>(`#${image.id}`);
+          const $image = $group?.findOne(".repShape");
+          return !!$image;
+        });
+        if (load) {
+          setTimeout(() => {
+            loaded.value = true;
+          }, 100);
+
+          clearInterval(interval);
+        }
+      });
+      onCleanup(() => clearInterval(interval));
+    }
+  }
+);
+
+const rect = ref<IRect>();
+watch(
+  loaded,
+  async (loaded) => {
+    if (!loaded) {
+      rect.value = undefined;
+    } else {
+      draw.value!.viewer.setViewMat(new Transform());
+      await nextTick();
+      rect.value = draw.value!.stage!.findOne<Group>(`#${DataGroupId}`)!.getClientRect();
+    }
+  },
+  { immediate: true }
+);
+
+const padding = 1;
+const coverScale = computed(() => {
+  if (!props.coverScale && rect.value && props.paperKey) {
+    const c = paperConfigs[props.paperKey!].scale * props.data!.config.proportion.scale;
+    const wScale = (rect.value.width * c) / (tableCoverWidth - 2 * padding);
+    const hScale = (rect.value.height * c) / (tableCoverHeight - 2 * padding);
+    const scale = round(Math.max(wScale, hScale) / 10, 0) * 10;
+    return scale;
+  } else {
+    return props.coverScale;
+  }
+});
+const preed = computed(
+  () => loaded.value && props.paperKey && rect.value && coverScale.value
+);
+const rectSize = computed(() => {
+  if (!preed.value) return;
+
+  let width = rect.value!.width;
+  let height = rect.value!.height;
+  // const rectScale = width / height;
+  // const tableCoverScale = tableCoverWidth / tableCoverHeight;
+  // if (rectScale > tableCoverScale) {
+  //   height = width / tableCoverScale;
+  // } else {
+  //   width = tableCoverScale * height;
+  // }
+  return { width, height };
+});
+
+const pixelPaperToDrawPixel = computed(() => {
+  if (!preed.value) return;
+  // 纸张一个像素代表的真实尺寸
+  const unitPaperPixel = coverScale.value! / paperConfigs[props.paperKey!].scale;
+  // 纸张一个像素转换到绘图多少像素
+  return unitPaperPixel / props.data!.config.proportion.scale;
+});
+
+const realRect = computed(
+  () =>
+    rectSize.value &&
+    pixelPaperToDrawPixel.value && {
+      width: rectSize.value.width / pixelPaperToDrawPixel.value,
+      height: rectSize.value.height / pixelPaperToDrawPixel.value,
+    }
+);
+
+const viewScale = computed(() => {
+  if (!pixelPaperToDrawPixel.value) return;
+  let scale = pixelPaperToDrawPixel.value < 1 ? 1 : props.scale || 1;
+  scale = Math.min(Math.max(scale, 1), 10);
+  scale *= props.pixelRatio || 1;
+  return scale;
+});
+
+const originSize = computed(
+  () =>
+    viewScale.value &&
+    realRect.value && {
+      width: realRect.value.width + (padding / viewScale.value) * 2,
+      height: realRect.value.height + (padding / viewScale.value) * 2,
+    }
+);
+
+const canvasSize = computed(
+  () =>
+    viewScale.value &&
+    realRect.value && {
+      width: realRect.value.width * viewScale.value + padding * 2,
+      height: realRect.value.height * viewScale.value + padding * 2,
+    }
+);
+
+watchEffect(() => {
+  if (originSize.value) {
+    emit("changeConfig", {
+      size: originSize.value,
+      scale: coverScale.value!,
+    });
+  }
+});
+
+const updateOrigin = async () => {
+  const d = draw.value!;
+  // 样式设置
+  {
+    const setStyle = (item: any) => {
+      if (!("fixed" in item)) return;
+      item.fixed = false;
+      if ("strokeWidth" in item && !("__strokeWidth" in item)) {
+        item.__strokeWidth = item.strokeWidth;
+      }
+      if ("__strokeWidth" in item) {
+        item.strokeWidth = (item.__strokeWidth * pixelPaperToDrawPixel.value!) / 2;
+      }
+    };
+    d.store.items.forEach(setStyle);
+    const lineItems = d.store.getTypeItems("line")[0];
+    lineItems.lines.forEach(setStyle);
+  }
+
+  const size = canvasSize.value as Size;
+  // imageScale
+  d.config.size = size;
+  d.config.labelLineConfig.type = "auto";
+  d.config.labelLineConfig.fontSize = size.width / 40;
+  d.config.labelLineConfig.splitWidth = size.width / 90;
+  d.config.labelLineConfig.strokeWidth = size.width / 600;
+  d.config.labelLineConfig.splitOffset = 4 * d.config.labelLineConfig.fontSize;
+  d.config.labelLineConfig.showOffset = 0;
+
+  d.initViewport(padding);
+  const canvas = draw.value!.stage!.container().children[0]
+    .children[0] as HTMLCanvasElement;
+  emit("changeOrigin", canvas);
+
+  if (import.meta.env.DEV) {
+    const mat = d!.viewer.transform.invert();
+    const testScale =
+      lineLen(mat.point({ x: 1, y: 0 }), mat.point({ x: 0, y: 0 })) * viewScale.value!;
+    // 一个像素 真实尺寸
+    console.log(`需要: 绘图一个像素:${pixelPaperToDrawPixel.value}`);
+    console.log(`实际: 绘图一个像素:${testScale}`);
+  }
+};
+
+const frameUpdateOrigin = debounce(updateOrigin, 300);
+
+// 缩放更改
+watch([preed, viewScale, () => props.notDebounce], () => {
+  if (preed.value) {
+    props.notDebounce ? updateOrigin() : frameUpdateOrigin();
+  }
+});
+// 设置更改
+watch(coverScale, () => {
+  frameUpdateOrigin.stop();
+  preed.value && updateOrigin();
+});
+</script>
+
+<style lang="scss" scoped>
+.content {
+  position: absolute;
+  left: -100vw;
+  top: -100vh;
+  // left: 0;
+  // top: 0;
+  width: 100vw;
+  height: 100vh;
+  pointer-events: none;
+  visibility: hidden;
+}
+</style>

+ 51 - 48
src/utils/shared.ts

@@ -127,12 +127,15 @@ export const debounce = <T extends (...args: any) => any>(
   delay: number = 160
 ) => {
   let timeout: any;
-  return function (...args: Parameters<T>) {
+  const run = function (...args: Parameters<T>) {
     clearTimeout(timeout);
     timeout = setTimeout(() => {
       fn.apply(null, args);
     }, delay);
-  };
+  } as T & { stop: () => void };
+
+  run.stop = () => clearTimeout(timeout);
+  return run;
 };
 
 // 防抖
@@ -150,34 +153,34 @@ export const frameEebounce = <T extends (...args: any) => any>(fn: T) => {
 
 // 节流
 export const frameThrottling = <T extends (...args: any) => any>(fn: T) => {
-  let iswait = false
+  let iswait = false;
   return function (...args: Parameters<T>) {
     if (iswait) {
       return;
     }
-    iswait = true
+    iswait = true;
     requestAnimationFrame(() => {
-      iswait = false
+      iswait = false;
       fn.apply(null, args);
     });
   };
 };
 
 export const frameInterval = (fn: () => void) => {
-  let isStop = false
+  let isStop = false;
   const animation = () => {
     requestAnimationFrame(() => {
-      fn()
+      fn();
       if (!isStop) {
-        animation()
+        animation();
       }
-    })
-  }
-  animation()
+    });
+  };
+  animation();
   return () => {
-    isStop = true
-  }
-}
+    isStop = true;
+  };
+};
 
 /**
  * 获取数据变化
@@ -511,57 +514,57 @@ export const hoverManage = <T, K>(
 export const trackFlag = (trackCallback?: (flag: number) => void) => {
   let flag = 0;
   const callback = () => {
-    flag--
-    trackCallback && trackCallback(flag)
-  }
+    flag--;
+    trackCallback && trackCallback(flag);
+  };
 
   return {
     get flag() {
-      return flag
+      return flag;
     },
     track<T>(fn: () => T): T {
       flag++;
       const result = fn();
       if (result instanceof Promise) {
         return result.then((data) => {
-          console.log('callback')
-          callback()
-          return data
+          console.log("callback");
+          callback();
+          return data;
         }) as T;
       } else {
         callback();
-        return result
+        return result;
       }
     },
   };
 };
 
 export const pickPromise = <T>() => {
-  let resolve: (data: T) => void
-  let reject: (reason?: any) => void
+  let resolve: (data: T) => void;
+  let reject: (reason?: any) => void;
   const promise = new Promise<T>((_resolve, _reject) => {
-    resolve = _resolve
-    reject = _reject
-  })
+    resolve = _resolve;
+    reject = _reject;
+  });
 
   return {
     promise,
     resolve: resolve!,
-    reject: reject!
-  }
-}
+    reject: reject!,
+  };
+};
 
 // 日期格式化
-export const formatDate = (date: Date, fmt: string = 'yyyy-MM-dd hh:mm') => {
+export const formatDate = (date: Date, fmt: string = "yyyy-MM-dd hh:mm") => {
   const map = {
-    'M+': date.getMonth() + 1, //月份
-    'd+': date.getDate(), //日
-    'h+': date.getHours(), //小时
-    'm+': date.getMinutes(), //分
-    's+': date.getSeconds(), //秒
-    'q+': Math.floor((date.getMonth() + 3) / 3), //季度
-    S: date.getMilliseconds() //毫秒
-  }
+    "M+": date.getMonth() + 1, //月份
+    "d+": date.getDate(), //日
+    "h+": date.getHours(), //小时
+    "m+": date.getMinutes(), //分
+    "s+": date.getSeconds(), //秒
+    "q+": Math.floor((date.getMonth() + 3) / 3), //季度
+    S: date.getMilliseconds(), //毫秒
+  };
 
   if (/(y+)/.test(fmt)) {
     fmt = fmt.replace(
@@ -570,18 +573,18 @@ export const formatDate = (date: Date, fmt: string = 'yyyy-MM-dd hh:mm') => {
         .getFullYear()
         .toString()
         .substr(4 - RegExp.$1.length)
-    )
+    );
   }
 
-  ;(Object.keys(map) as Array<keyof typeof map>).forEach(k => {
-    if (new RegExp('(' + k + ')').test(fmt)) {
-      const val = map[k].toString()
+  (Object.keys(map) as Array<keyof typeof map>).forEach((k) => {
+    if (new RegExp("(" + k + ")").test(fmt)) {
+      const val = map[k].toString();
       fmt = fmt.replace(
         RegExp.$1,
-        RegExp.$1.length === 1 ? val : ('00' + val).substr(val.length)
-      )
+        RegExp.$1.length === 1 ? val : ("00" + val).substr(val.length)
+      );
     }
-  })
+  });
 
-  return fmt
-}
+  return fmt;
+};