Bladeren bron

feat: 优化制表对接

bill 3 maanden geleden
bovenliggende
commit
ca96529e58

File diff suppressed because it is too large
+ 2766 - 2151
pnpm-lock.yaml


+ 5 - 2
src/core/helper/compass.vue

@@ -95,9 +95,12 @@ const mat = computed(() => {
     { right: 20 + margin.value[1], top: 20 + margin.value[0] },
     data.value
   );
-  tf.translate(pos.x + data.value.width / 2, pos.y + data.value.height / 2);
+  tf.translate(pos.x, pos.y);
   if (sizeMat.value) {
-    tf.scale(viewerConfig.value.scaleX, viewerConfig.value.scaleY);
+    tf.scale(viewerConfig.value.scaleX, viewerConfig.value.scaleY).translate(
+      data.value.width / 2,
+      data.value.height / 2
+    );
   }
   tf.rotate(
     MathUtils.degToRad(store.config.compass.rotation + viewerConfig.value.rotation)

+ 67 - 47
src/core/helper/split-line.vue

@@ -1,9 +1,15 @@
 <template>
   <template v-if="axissInfo">
     <v-group v-for="info in axissInfo" :config="{ listening: false }">
-      <v-line :config="{ points: info.points, ...style }" />
+      <v-line :config="{ points: info.points, ...config.labelLineConfig }" />
       <v-text
-        :config="{ ...style, strokeWidth: 0, ...textConfig, fontSize, align: 'center' }"
+        :config="{
+          ...config.labelLineConfig,
+          strokeWidth: 0,
+          ...textConfig,
+          fontSize: config.labelLineConfig.fontSize,
+          align: 'center',
+        }"
         v-for="textConfig in info.texts"
       />
     </v-group>
@@ -20,52 +26,66 @@ import {
   useViewerTransform,
   useViewSize,
 } from "../hook/use-viewer";
-import { useResize } from "../hook/use-event";
 import { Pos } from "@/utils/math";
 import { TextConfig } from "konva/lib/shapes/Text";
 import { Transform } from "konva/lib/Util";
 import { normalPadding } from "@/utils/bound";
 import { useConfig } from "../hook/use-config";
 import { useProportion } from "../hook/use-proportion";
+import { useFormalLayer } from "../hook/use-layer";
+import { Group } from "konva/lib/Group";
+import { DataGroupId } from "@/constant";
 
-const style = {
-  stroke: "#000",
-  strokeWidth: 1,
-  opacity: 1,
-  strokeScaleEnabled: false,
-  shadowColor: "#fff",
-  shadowBlur: 3,
-  shadowOffset: { x: 0, y: 0 },
-  shadowOpacity: 1,
-};
-
-const props = withDefaults(
-  defineProps<{
-    splitOffset?: number;
-    showOffset?: number;
-    splitWidth?: number;
-  }>(),
-  { splitOffset: 80, showOffset: 20, splitWidth: 10 }
-);
+const config = useConfig();
 
+const format = useFormalLayer();
 const getPixel = useGetViewBoxPositionPixel();
+const viewMat = useViewerTransform();
 const size = useViewSize();
-const center = computed(() => {
-  if (!size.value) return;
-  return getPixel({
-    left: size.value.width / 2,
-    top: size.value.height / 2,
-  });
-});
-const config = useConfig();
 const rang = computed(() => {
-  if (!size.value) return;
+  if (!size.value || !format.value) return;
   const mrs = normalPadding(config.margin || 0);
 
-  return {
-    lt: getPixel({ left: props.showOffset + mrs[3], top: props.showOffset + mrs[0] }),
-    rb: getPixel({ right: props.showOffset + mrs[1], bottom: props.showOffset + mrs[2] }),
-  };
+  if (config.labelLineConfig.type === "fix") {
+    return {
+      lt: getPixel({
+        left: config.labelLineConfig.showOffset + mrs[3],
+        top: config.labelLineConfig.showOffset + mrs[0],
+      }),
+      rb: getPixel({
+        right: config.labelLineConfig.showOffset + mrs[1],
+        bottom: config.labelLineConfig.showOffset + mrs[2],
+      }),
+    };
+  } else {
+    // 收集点变动
+    points.value;
+    viewMat.value;
+
+    const formatGroup = format.value.findOne<Group>(`#${DataGroupId}`)!;
+    const rect = formatGroup.getClientRect();
+    const bw = config.labelLineConfig.strokeWidth / 2;
+    // console.log(rect);
+    return {
+      lt: {
+        x: rect.x - config.labelLineConfig.showOffset - mrs[3] + bw,
+        y: rect.y - config.labelLineConfig.showOffset - mrs[0] + bw,
+      },
+      rb: {
+        x: rect.x + rect.width + config.labelLineConfig.showOffset + mrs[1] - bw,
+        y: rect.y + rect.height + config.labelLineConfig.showOffset + mrs[2] - bw,
+      },
+    };
+  }
+});
+
+const center = computed(() => {
+  if (!rang.value) return;
+
+  return getPixel({
+    left: (rang.value.lt.x + rang.value.rb.x) / 2,
+    top: (rang.value.lt.y + rang.value.rb.y) / 2,
+  });
 });
 
 const compsInfo = useComponentsAttach(
@@ -103,7 +123,8 @@ const rectAxisSteps = computed(() => {
       return;
     }
 
-    const can = (data: number) => Math.abs(data - cur) > props.splitOffset;
+    const can = (data: number) =>
+      Math.abs(data - cur) > config.labelLineConfig.splitOffset;
 
     let i = axis.length - 1;
     for (i; i >= 0; i--) {
@@ -133,7 +154,6 @@ const rectAxisSteps = computed(() => {
 
 const { transform: getWidthText } = useProportion();
 const invConfig = useViewerInvertTransformConfig();
-const fontSize = 12;
 const axissInfo = computed(() => {
   if (!rang.value) return;
   const infos: Record<string, { points: number[]; texts: TextConfig[] }> = {
@@ -147,17 +167,17 @@ const axissInfo = computed(() => {
     for (let i = 0; i < rectAxisSteps.value.left.length; i++) {
       const cur = rectAxisSteps.value.left[i];
       infos.left.points.push(rang.value!.lt.x, cur);
-      infos.left.points.push(rang.value!.lt.x + props.splitWidth, cur);
+      infos.left.points.push(rang.value!.lt.x + config.labelLineConfig.splitWidth, cur);
       infos.left.points.push(rang.value!.lt.x, cur);
 
       if (i === 0) continue;
       const prev = rectAxisSteps.value.left[i - 1];
       const lt = {
-        x: rang.value!.lt.x + fontSize / 2,
+        x: rang.value!.lt.x + config.labelLineConfig.fontSize / 2,
         y: prev,
       };
       const width = cur - prev;
-      const height = fontSize;
+      const height = config.labelLineConfig.fontSize;
       const center = { x: 0, y: height / 2 };
 
       const tf = new Transform()
@@ -177,17 +197,17 @@ const axissInfo = computed(() => {
     for (let i = 0; i < rectAxisSteps.value.right.length; i++) {
       const cur = rectAxisSteps.value.right[i];
       infos.right.points.push(rang.value!.rb.x, cur);
-      infos.right.points.push(rang.value!.rb.x - props.splitWidth, cur);
+      infos.right.points.push(rang.value!.rb.x - config.labelLineConfig.splitWidth, cur);
       infos.right.points.push(rang.value!.rb.x, cur);
 
       if (i === 0) continue;
       const prev = rectAxisSteps.value.right[i - 1];
       const lt = {
-        x: rang.value!.rb.x - fontSize / 1.5,
+        x: rang.value!.rb.x - config.labelLineConfig.fontSize / 1.5,
         y: prev,
       };
       const width = cur - prev;
-      const height = fontSize;
+      const height = config.labelLineConfig.fontSize;
       const center = { x: 0, y: height / 2 };
 
       const tf = new Transform()
@@ -207,14 +227,14 @@ const axissInfo = computed(() => {
     for (let i = 0; i < rectAxisSteps.value.top.length; i++) {
       const cur = rectAxisSteps.value.top[i];
       infos.top.points.push(cur, rang.value!.lt.y);
-      infos.top.points.push(cur, rang.value!.lt.y + props.splitWidth);
+      infos.top.points.push(cur, rang.value!.lt.y + config.labelLineConfig.splitWidth);
       infos.top.points.push(cur, rang.value!.lt.y);
 
       if (i === 0) continue;
       const prev = rectAxisSteps.value.top[i - 1];
       const lt = {
         x: prev,
-        y: rang.value!.lt.y + fontSize / 3,
+        y: rang.value!.lt.y + config.labelLineConfig.fontSize / 3,
       };
 
       const width = cur - prev;
@@ -230,14 +250,14 @@ const axissInfo = computed(() => {
     for (let i = 0; i < rectAxisSteps.value.bottom.length; i++) {
       const cur = rectAxisSteps.value.bottom[i];
       infos.bottom.points.push(cur, rang.value!.rb.y);
-      infos.bottom.points.push(cur, rang.value!.rb.y - props.splitWidth);
+      infos.bottom.points.push(cur, rang.value!.rb.y - config.labelLineConfig.splitWidth);
       infos.bottom.points.push(cur, rang.value!.rb.y);
 
       if (i === 0) continue;
       const prev = rectAxisSteps.value.bottom[i - 1];
       const lt = {
         x: prev,
-        y: rang.value!.rb.y - fontSize,
+        y: rang.value!.rb.y - config.labelLineConfig.fontSize,
       };
 
       const width = cur - prev;

+ 19 - 2
src/core/hook/use-config.ts

@@ -10,6 +10,21 @@ export type Border = {
   lineWidth: number;
 };
 
+const defLableLineConfig = {
+  stroke: "#000",
+  strokeWidth: 1,
+  opacity: 1,
+  strokeScaleEnabled: false,
+  shadowColor: "#fff",
+  shadowBlur: 3,
+  shadowOffset: { x: 0, y: 0 },
+  shadowOpacity: 1,
+  splitOffset: 80, 
+  showOffset: 20, 
+  fontSize: 12,
+  splitWidth: 10,
+  type: 'fix'
+}
 export type Config = {
   showGrid: boolean;
   showLabelLine: boolean;
@@ -19,6 +34,7 @@ export type Config = {
   margin?: number | number[];
   showCompass: boolean
   showComponentSize: boolean
+  labelLineConfig: typeof defLableLineConfig
 };
 
 export const defConfig: Config = {
@@ -26,13 +42,14 @@ export const defConfig: Config = {
   showLabelLine: true,
   size: null,
   showCompass: false,
-  showComponentSize: true
+  showComponentSize: true,
+  labelLineConfig: defLableLineConfig
 };
 
 export const useConfig = installGlobalVar(() => {
   const { setFixSize, size } = useGlobalResize();
   return reactive({
-    ...defConfig,
+    ...JSON.parse(JSON.stringify(defConfig)),
     size: computed({
       get: () => size.value!,
       set: (size: Size | null) => {

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

@@ -4,6 +4,7 @@ import {
   useInstanceProps,
   useLayers,
   useStage,
+  useTempStatus,
 } from "./use-global-vars.ts";
 import { useMode, useOperMode } from "./use-status";
 import { Stage } from "konva/lib/Stage";
@@ -205,7 +206,7 @@ export const useExpose = installGlobalVar(() => {
   const store = useStore();
   const history = useHistory();
   const viewer = useViewer().viewer;
-  const { updateSize, setFixSize } = useGlobalResize();
+  const { updateSize } = useGlobalResize();
   const exposeBlob = (config?: PickParams<"toBlob", "callback">) => {
     const $stage = stage.value!.getStage();
     return new Promise<Blob>((resolve) => {
@@ -239,6 +240,7 @@ export const useExpose = installGlobalVar(() => {
       padding: 20,
     });
   };
+  
 
   return {
     ...useInteractiveDrawShapeAPI(),
@@ -246,6 +248,7 @@ export const useExpose = installGlobalVar(() => {
       const $store = stage.value?.getStage();
       return $store;
     },
+    ...useTempStatus(),
     exposeBlob,
     toggleHit,
     formalLayer: useFormalLayer(),

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

@@ -23,6 +23,7 @@ import { debounce, mergeFuns, onlyId } from "@/utils/shared.ts";
 import { StoreData } from "../store/store.ts";
 import { rendererMap, rendererName } from "@/constant/index.ts";
 import { Shape, ShapeConfig } from "konva/lib/Shape";
+import { ElLoading } from "element-plus";
 
 export const useRendererInstance = () => {
   let instance = getCurrentInstance()!;
@@ -212,49 +213,48 @@ export const usePointerPos = installGlobalVar(() => {
 }, Symbol("pointerPos"));
 
 export const usePointerIntersections = installGlobalVar(() => {
-  const shapes = ref<Shape<ShapeConfig>[]>([])
-  const pos = usePointerPos()
-  const stage = useStage()
+  const shapes = ref<Shape<ShapeConfig>[]>([]);
+  const pos = usePointerPos();
+  const stage = useStage();
   const updateShapes = debounce(() => {
     if (!pos.value || !stage.value) {
       return;
     }
-    shapes.value = stage.value.getNode().getAllIntersections(pos.value) || []
-  }, 300)
+    shapes.value = stage.value.getNode().getAllIntersections(pos.value) || [];
+  }, 300);
 
-  const stopWatch =watch(pos, updateShapes)
+  const stopWatch = watch(pos, updateShapes);
 
   return {
     var: shapes,
     onDestroy: () => {
-      stopWatch()
-      shapes.value = []
-    }
-  }
-})
-
+      stopWatch();
+      shapes.value = [];
+    },
+  };
+});
 
 export const usePointerIntersection = installGlobalVar(() => {
-  const shape = ref<Shape<ShapeConfig> | null>(null)
-  const pos = usePointerPos()
-  const stage = useStage()
+  const shape = ref<Shape<ShapeConfig> | null>(null);
+  const pos = usePointerPos();
+  const stage = useStage();
   const updateShape = debounce(() => {
     if (!pos.value || !stage.value) {
       return;
     }
-    shape.value = stage.value.getNode().getIntersection(pos.value)
-  }, 16)
+    shape.value = stage.value.getNode().getIntersection(pos.value);
+  }, 16);
 
-  const stopWatch = watch(pos, updateShape)
+  const stopWatch = watch(pos, updateShape);
 
   return {
     var: shape,
     onDestroy: () => {
-      stopWatch()
-      shape.value = null
-    }
-  }
-})
+      stopWatch();
+      shape.value = null;
+    },
+  };
+});
 export const useDownKeys = installGlobalVar(() => {
   const keyKeys = reactive(new Set<string>());
   const mouseKeys = reactive(new Set<string>());
@@ -265,23 +265,23 @@ export const useDownKeys = installGlobalVar(() => {
     listener(window, "keyup", (ev) => {
       keyKeys.delete(ev.key);
     }),
-    listener(window, 'mousemove', (ev) => {
-      ev.shiftKey ? mouseKeys.add('Shift') : mouseKeys.delete('Shift')
-      ev.altKey ? mouseKeys.add('Alt') : mouseKeys.delete('Alt')
-      ev.metaKey ? mouseKeys.add('Meta') : mouseKeys.delete('Meta')
-      ev.ctrlKey ? mouseKeys.add('Ctrl') : mouseKeys.delete('Ctrl')
+    listener(window, "mousemove", (ev) => {
+      ev.shiftKey ? mouseKeys.add("Shift") : mouseKeys.delete("Shift");
+      ev.altKey ? mouseKeys.add("Alt") : mouseKeys.delete("Alt");
+      ev.metaKey ? mouseKeys.add("Meta") : mouseKeys.delete("Meta");
+      ev.ctrlKey ? mouseKeys.add("Ctrl") : mouseKeys.delete("Ctrl");
     })
   );
-  const keys = reactive(new Set<string>())
+  const keys = reactive(new Set<string>());
   watchEffect(() => {
-    keys.clear()
+    keys.clear();
     for (const key of keyKeys.values()) {
-      keys.add(key)
+      keys.add(key);
     }
     for (const key of mouseKeys.values()) {
-      keys.add(key)
+      keys.add(key);
     }
-  })
+  });
 
   return {
     var: keys,
@@ -322,19 +322,48 @@ export const useMountParts = installGlobalVar(() => {
   };
 });
 
-export const useForciblyShowItemIds = installGlobalVar(
-  () => {
-    const set = new Set() as Set<string> & { cycle: (id: string, fn: () => any) => void }
-    set.cycle = (id, fn) => {
-      set.add(id)
-      const result = fn()
-      if (result instanceof Promise) {
-        result.then(() => set.delete(id))
-      } else {
-        set.delete(id)
-      }
+export const useForciblyShowItemIds = installGlobalVar(() => {
+  const set = new Set() as Set<string> & {
+    cycle: (id: string, fn: () => any) => void;
+  };
+  set.cycle = (id, fn) => {
+    set.add(id);
+    const result = fn();
+    if (result instanceof Promise) {
+      result.then(() => set.delete(id));
+    } else {
+      set.delete(id);
     }
-    return set
-  },
-  Symbol("forciblyShowItemId")
-);
+  };
+  return set;
+}, Symbol("forciblyShowItemId"));
+
+export const useRendererDOM = installGlobalVar(() => ref<HTMLDivElement>())
+
+export const useTempStatus = installGlobalVar(() => {
+  const temp = ref(false);
+  const enterTemp = <T>(fn: () => T): T => {
+    temp.value = true
+    const result = fn()
+    if (result instanceof Promise) {
+      return result.then(async (data) => {
+        temp.value = false
+        await nextTick()
+        return data;
+      }) as T
+    } else {
+      temp.value = false
+      return result
+    }
+  }
+  const dom = useRendererDOM()
+  watch(temp, (_a, _b, onCleanup) => {
+    if (temp.value && dom.value) {
+      const instance = ElLoading.service({ fullscreen: true, target: dom.value })
+      onCleanup(() => instance.close())
+    }
+  })
+  
+
+  return { tempStatus: temp, enterTemp }
+});

+ 9 - 1
src/core/renderer/renderer.vue

@@ -4,6 +4,7 @@
     @contextmenu.prevent
     :style="{ cursor: cursorStyle }"
     ref="layout"
+    :class="{ temp: tempStatus }"
   >
     <div class="mount-mask" :id="DomOutMountId" />
     <div
@@ -76,7 +77,9 @@ import {
   useCursor,
   useInstanceProps,
   useMountParts,
+  useRendererDOM,
   useStage,
+  useTempStatus,
 } from "../hook/use-global-vars.ts";
 import { useMode } from "../hook/use-status.ts";
 import { useViewerTransformConfig } from "../hook/use-viewer.ts";
@@ -107,6 +110,7 @@ onUnmounted(() => {
 });
 
 const mountParts = useMountParts();
+const { tempStatus } = useTempStatus();
 
 const props = defineProps<InstanceProps>();
 const store = useStore();
@@ -125,7 +129,7 @@ useAutoService();
 const isDev = import.meta.env.DEV;
 const stage = useStage();
 const { size, fix } = useGlobalResize();
-const layout = ref();
+const layout = useRendererDOM();
 const viewerConfig = useViewerTransformConfig();
 const types = Object.keys(components) as ShapeType[];
 const mode = useMode();
@@ -164,6 +168,10 @@ defineExpose(expose);
     width: 100%;
     height: 100%;
   }
+
+  &.temp .draw-content {
+    opacity: 0;
+  }
 }
 
 .mount-mask {

+ 20 - 9
src/example/components/header/actions.ts

@@ -26,7 +26,7 @@ const rotateView = (draw: Draw) => {
   });
 };
 
-export const getImage = (draw: Draw, format: string) =>
+export const getImage = (draw: Draw, format: string) => 
   draw.stage!.toBlob({
     pixelRatio: 4,
     mimeType: format,
@@ -74,19 +74,30 @@ export const getHeaderActions = (draw: Draw) => {
       children: [
         {
           handler: async () => {
-            saveAs(await getImage(draw, 'image/jpeg'), "canvas.png");
+            draw.enterTemp(async () => {
+              const oldShowGrid = draw.config.showGrid
+              draw.config.showGrid = false
+              await nextTick()
+              saveAs(await getImage(draw, 'image/jpeg'), "canvas.png");
+              draw.config.showGrid = oldShowGrid
+            })
           },
           text: "jpeg",
           icon: "a-visible",
         },
         {
-          handler: async () => {
-            const oldBack = draw.config.back && {...draw.config.back}
-            draw.config.back = undefined
-            await nextTick()
-            await saveAs(await getImage(draw, 'image/png'), "canvas.png");
-            draw.config.back = oldBack
-            ElMessage.success("导出成功");
+          handler: () => {
+            draw.enterTemp(async () => {
+              const oldBack = draw.config.back && {...draw.config.back}
+              const oldShowGrid = draw.config.showGrid
+              draw.config.showGrid = false
+              draw.config.back = undefined
+              await nextTick()
+              await saveAs(await getImage(draw, 'image/png'), "canvas.png");
+              draw.config.back = oldBack
+              draw.config.showGrid = oldShowGrid
+              ElMessage.success("导出成功");
+            })
           },
           text: "png",
           icon: "a-visible",

+ 47 - 12
src/example/dialog/ai/ai.vue

@@ -1,15 +1,21 @@
 <template>
   <div style="padding: 20px 20px 40px">
-    <VR v-model:value="scene" class="vr-layout" label="(含平面图和AI绘图)" />
+    <VR v-model:value="scene" class="vr-layout" label="场景" />
+    <div v-if="floors?.length" class="tagging-layout">
+      <p class="title">楼层</p>
+      <el-radio-group v-model="syncFloor" style="width: 200px">
+        <el-radio :label="item.name" :value="item.name" v-for="item in floors" />
+      </el-radio-group>
+    </div>
     <div v-if="scene" class="tagging-layout">
-      <p class="title">请选择图例范围</p>
-      <el-checkbox-group v-model="syncTags" style="width: 200px">
-        <el-checkbox
+      <p class="title">图例</p>
+      <el-radio-group v-model="syncTag" style="width: 200px">
+        <el-radio
           :label="item.label"
           :value="item.value"
           v-for="item in options[scene.type]"
         />
-      </el-checkbox-group>
+      </el-radio-group>
     </div>
   </div>
 </template>
@@ -17,8 +23,10 @@
 <script lang="ts" setup>
 import { ref, watch } from "vue";
 import VR from "../vr/vr.vue";
-import { ElCheckboxGroup, ElCheckbox } from "element-plus";
+import { ElRadioGroup, ElRadio, ElMessage } from "element-plus";
 import {
+  compassGets,
+  getFloors,
   lineGets,
   Scene,
   SCENE_TYPE,
@@ -35,21 +43,48 @@ const options = {
   [SCENE_TYPE.cloud]: [{ value: "hot", label: "热点" }],
   [SCENE_TYPE.fuse]: [{ value: "hot", label: "标签" }],
 };
-const syncTags = ref<string[]>([]);
+const syncTag = ref<string>();
+const syncFloor = ref<string>();
+const floors = ref<{ name: string }[]>([]);
+
+let token = 0;
 watch(scene, () => {
+  floors.value = [];
+  syncFloor.value = undefined;
+
+  const curToken = ++token;
   if (!scene.value) {
-    syncTags.value = [];
+    syncTag.value = undefined;
   } else {
-    syncTags.value = options[scene.value.type].map((item) => item.value);
+    syncTag.value = options[scene.value.type][0].value;
+    if (scene.value.type === SCENE_TYPE.mesh) {
+      getFloors[scene.value.type](scene.value).then((data) => {
+        if (token === curToken) {
+          floors.value = data.floors;
+          syncFloor.value = floors.value[0].name;
+        }
+      });
+    }
   }
 });
 
 defineExpose({
   submit: async (): Promise<AIExposeData> => {
+    if (!syncTag.value) {
+      ElMessage.error("请选择同步标签");
+      throw "请选择同步标签";
+    }
+    if (!syncFloor.value && floors.value.length) {
+      ElMessage.error("请选择楼层");
+      throw "请选择楼层";
+    }
     if (!scene.value) return { taggings: [], floors: [] };
-    const taggings = await taggingGets[scene.value.type](scene.value, syncTags.value);
-    const floors = await lineGets[scene.value.type](scene.value);
-    return { taggings, floors };
+    const [taggings, floorGeos, compass] = await Promise.all([
+      taggingGets[scene.value.type](scene.value, [syncTag.value]),
+      lineGets[scene.value.type](scene.value, syncFloor.value),
+      compassGets[scene.value.type](scene.value),
+    ]);
+    return { taggings, floors: floorGeos, compass };
   },
 });
 </script>

+ 1 - 1
src/example/dialog/ai/index.ts

@@ -12,7 +12,7 @@ type Props = {
   cancel: () => void;
 };
 
-export type AIExposeData = {taggings: Tagings, floors: SceneFloors }
+export type AIExposeData = {taggings: Tagings, floors: SceneFloors, compass?: number }
 
 export const props = reactive({
   title: "全景VR",

+ 1 - 1
src/example/dialog/vr/vr.vue

@@ -1,7 +1,7 @@
 <template>
   <div style="padding: 20px 20px 40px">
     <p class="title">
-      请选择场景<span v-if="label">{{ label }}</span>
+      {{ label ? label : "请选择场景" }}
     </p>
     <el-select
       :model-value="value?.id"

+ 0 - 2
src/example/fuse/store.ts

@@ -2,11 +2,9 @@ import { Ref, ref } from "vue";
 import { getOverviewData, getTabulationData } from "./req";
 import { StoreData } from "@/core/store/store";
 
-export const tableCoverScale = 540 / 425
 export const tableCoverKey = '__tableCoverKey'
 export const tableTableKey = '__tableTableKey'
 export const tableTitleKey = '__tableTitleKey'
-export const tableCoverWidth = 514
 
 export const overviewData = ref() as Ref<{
   store: StoreData;

+ 36 - 8
src/example/fuse/views/overview/header.vue

@@ -22,10 +22,13 @@ import {
 } from "../../req.ts";
 import { router } from "../../router.ts";
 import { params } from "@/example/env.ts";
-import { tabulationData, tableCoverScale, refreshTabulationData } from "../../store.ts";
+import { tabulationData, refreshTabulationData } from "../../store.ts";
 import { nextTick } from "vue";
 import { getRepTabulationStore } from "../tabulation/gen.ts";
 import { getPaperConfig, paperConfigs } from "@/example/components/slide/actions.ts";
+import { DataGroupId } from "@/constant/index.ts";
+import { Group } from "konva/lib/Group";
+import { asyncTimeout } from "@/utils/shared.ts";
 
 const draw = useDraw();
 
@@ -61,13 +64,32 @@ const setViewToTableCover = async () => {
   const oldSize = draw.config.size;
   const oldBack = draw.config.back;
   const oldShowCompass = draw.config.showCompass;
+  const oldLabelLineConfig = { ...draw.config.labelLineConfig };
+
+  const rect = draw.stage!.findOne<Group>(`#${DataGroupId}`)!.getClientRect();
+  const rectScale = rect.width / rect.height;
+  const tableCoverScale = 540 / 425;
+
+  let width: number, height: number;
+  if (rectScale > tableCoverScale) {
+    width = 1080;
+    height = width / rectScale;
+  } else {
+    height = 850;
+    width = rectScale * height;
+  }
+
+  // const height = width / tableCoverScale;
 
-  const width = 900;
-  const height = width / tableCoverScale;
   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.strokeWidth = 2;
+  draw.config.labelLineConfig.fontSize = 16;
+
   await nextTick();
   draw.initViewport();
 
@@ -76,17 +98,23 @@ const setViewToTableCover = async () => {
     draw.config.showGrid = oldShowGrid;
     draw.config.back = oldBack;
     draw.config.showCompass = oldShowCompass;
+    draw.config.labelLineConfig = oldLabelLineConfig;
     draw.viewer.setViewMat(oldViewMat);
   };
 };
 
 const saveHandler = async () => {
   const storeData = draw.getData();
+  const blob = await draw.enterTemp(async () => {
+    const recover = await setViewToTableCover();
+    await nextTick();
+    const blob = await getImage(draw, "image/png");
+    recover();
+    await nextTick();
+    return blob;
+  });
+
   await saveOverviewData({ store: storeData, viewport: draw!.viewer.transform.m });
-  const recover = await setViewToTableCover();
-  await nextTick();
-  const blob = await getImage(draw, "image/png");
-  recover();
   const url = await uploadResourse(new File([blob], `tabulation-cover-${params.id}.png`));
   await saveTabulationCover(url);
   await refreshTabulationData();
@@ -102,7 +130,7 @@ const saveHandler = async () => {
     pConfig.margin,
     url
   );
-  console.error("tabStore", tabStore);
+  tabStore.config.compass = storeData.config.compass;
   await saveTabulationData({ ...tabulationData.value, paperKey, store: tabStore });
 };
 

+ 1 - 0
src/example/fuse/views/overview/index.vue

@@ -39,6 +39,7 @@ const init = async (draw: Draw) => {
   draw.config.showCompass = false;
   draw.config.showLabelLine = true;
   draw.config.showComponentSize = true;
+  console.log("0.0.0");
   draw.config.back = { color: "#f0f2f5", opacity: 1 };
   draw.store.setStore(overviewData.value.store);
   overviewData.value.viewport && draw.viewer.setViewMat(overviewData.value.viewport);

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

@@ -1,10 +1,10 @@
 import { defaultLayer } from "@/constant";
 import { getBaseItem } from "@/core/components/util";
-import { StoreData } from "@/core/store/store";
+import { getEmptyStoreData, StoreData } from "@/core/store/store";
 import { getFixPosition } from "@/utils/bound";
 import { Size } from "@/utils/math";
 import { getImage } from "@/utils/resource";
-import { tableCoverKey, tableCoverWidth, tableTableKey, tableTitleKey } from "../../store";
+import { tableCoverKey, tableTableKey, tableTitleKey } from "../../store";
 import { TableData } from "@/core/components/table";
 import { TextData } from "@/core/components/text";
 import { ImageData } from "@/core/components/image";
@@ -63,18 +63,33 @@ const setTitlePosition = (
   title.mat[5] = titlePos.y;
 };
 
+
 const genDefaultCover = async (cover: string) => {
   const image = await getImage(cover);
+  const tableCoverWidth = 514
+  const tableCoverHeight = 425
+  const rectScale = image.width / image.height
+  const tableCoverScale = tableCoverWidth / tableCoverHeight
+
+  let width: number, height: number;
+  if (rectScale > tableCoverScale) {
+    width = tableCoverWidth;
+    height = width / rectScale;
+  } else {
+    height = tableCoverHeight;
+    width = rectScale * height;
+  }
+
   const coverData = {
     ...getBaseItem(),
     cornerRadius: 0,
-    strokeWidth: 1,
+    strokeWidth: 0,
     url: cover,
     lock: true,
     key: tableCoverKey,
     zIndex: -1,
-    width: tableCoverWidth,
-    height: (image.height / image.width) * tableCoverWidth,
+    width,
+    height,
     mat: [1, 0, 0, 1, 0, 0],
   };
   return coverData;
@@ -151,12 +166,14 @@ export const getRepTabulationStore = async (
     );
 
     if (coverUrl) {
+      const imageData = await genDefaultCover(coverUrl);
       if (!~coverNdx) {
-        const imageData = await genDefaultCover(coverUrl);
         setCoverPosition(size, margin, imageData);
         layer.image.push(imageData);
       } else {
-        layer.image[coverNdx].url = coverUrl;
+        layer.image[coverNdx].url = imageData.url
+        layer.image[coverNdx].width = imageData.width
+        layer.image[coverNdx].height = imageData.height
       }
     } else if (~coverNdx) {
       layer.image.splice(coverNdx, 1);
@@ -180,10 +197,18 @@ export const getRepTabulationStore = async (
         ...layers,
         [defaultLayer]: layer,
       },
+      config: {
+        ...getEmptyStoreData(),
+        ...store.config,
+      }
     };
   } else {
     return {
       ...store,
+      config: {
+        ...getEmptyStoreData(),
+        ...store.config,
+      },
       layers: { [defaultLayer]: layer },
     };
   }

+ 24 - 20
src/example/fuse/views/tabulation/header.vue

@@ -39,28 +39,32 @@ const actions = [
     {
       handler: async () => {
         const ef = await selectExposeFormat();
-        const oldMat = draw.viewer.viewMat;
-        draw.viewer.setViewMat([1, 0, 0, 1, 0, 0]);
-        await nextTick();
-        const viewSize = draw.viewer.viewSize!;
-        const size = {
-          width: draw.stage!.width(),
-          height: draw.stage!.height(),
-        };
-        const rect = {
-          x: (size.width - viewSize.width) / 2,
-          y: (size.height - viewSize.height) / 2,
-          ...viewSize,
-        };
         const format = "image/jpeg";
+        let [rect, fileBlob] = await draw.enterTemp(async () => {
+          const oldMat = draw.viewer.viewMat;
+          draw.viewer.setViewMat([1, 0, 0, 1, 0, 0]);
+          await nextTick();
 
-        let fileBlob = await (draw.stage!.toBlob({
-          pixelRatio: 4,
-          mimeType: format,
-          quality: 1,
-          ...rect,
-        }) as Promise<Blob>);
-        draw.viewer.setViewMat(oldMat);
+          const viewSize = draw.viewer.viewSize!;
+          const size = {
+            width: draw.stage!.width(),
+            height: draw.stage!.height(),
+          };
+          const rect = {
+            x: (size.width - viewSize.width) / 2,
+            y: (size.height - viewSize.height) / 2,
+            ...viewSize,
+          };
+
+          let fileBlob = await (draw.stage!.toBlob({
+            pixelRatio: 4,
+            mimeType: format,
+            quality: 1,
+            ...rect,
+          }) as Promise<Blob>);
+          draw.viewer.setViewMat(oldMat);
+          return [rect, fileBlob] as const;
+        });
 
         const filename = "canvas";
         let ext = "jpg";

+ 2 - 1
src/example/fuse/views/tabulation/slide.vue

@@ -26,8 +26,9 @@ const paper = {
 };
 const menus = reactive([paper, text, serial, table]);
 
-const setPaperAfterHandler = async (paperKey: string) => {
+const setPaperAfterHandler = async (paperKey: string, oldPaperKey?: string) => {
   paperRaw.children.find((item) => item.key === paperKey)!.handler(draw);
+  if (!oldPaperKey) return;
   await nextTick();
   const data = await getRepTabulationStore(
     draw.store.data,

+ 19 - 10
src/example/platform/platform-draw.ts

@@ -35,6 +35,7 @@ const scaleResource = (info: AIExposeData, scale: number) => {
   }));
 
   return {
+    ...info,
     taggings,
     floors,
   };
@@ -68,12 +69,11 @@ const getResourceLayers = (data: AIExposeData) => {
         ...floor,
         box,
         taggings: data.taggings
-          .filter(
-            (item) => {
-              return true
-              // return item.position.z > box.bound.z_min &&
-              // item.position.z <= box.bound.z_max
-            })
+          // .filter(
+          //   (item) => {
+          //     return item.position.z > box.bound.z_min &&
+          //     item.position.z <= box.bound.z_max
+          //   })
           .map((item) => ({
             ...item,
             position: { x: item.position.x, y: item.position.y },
@@ -171,12 +171,21 @@ export const drawPlatformResource = (data: AIExposeData, draw: Draw) => {
   draw.history.onceTrack(() => {
     draw.store.setConfig({ proportion: { scale: 1, unit } });
     for (const layer of layers) {
-      if (!draw.store.layers.includes(layer.name)) {
-        draw.store.addLayer(layer.name);
-      }
-      draw.store.setCurrentLayer(layer.name);
+      // if (!draw.store.layers.includes(layer.name)) {
+      //   draw.store.addLayer(layer.name);
+      // }
+      // draw.store.setCurrentLayer(layer.name);
       layerBounds.push(drawLayerResource(layer, draw)!);
     }
+    if (typeof data.compass === 'number') {
+      draw.store.setConfig({
+        compass: {
+          rotation: data.compass,
+          url: draw.store.config.compass.url
+        }
+      })
+    }
+    
   });
 
   if (layerBounds.length === 0 || !draw.viewer.size) return;

+ 67 - 40
src/example/platform/platform-resource.ts

@@ -53,6 +53,25 @@ export type Taging = {
 
 export type Tagings = Taging[];
 
+export const compassGets = {
+  [SCENE_TYPE.fuse]: () => void 0,
+  [SCENE_TYPE.cloud]: () => void 0,
+  [SCENE_TYPE.mesh]: async (scene: Scene) => {
+    const prev = `/scene_view_data/${scene.m}`;
+    const config = await getSceneApi(scene.type, `${prev}/data/scene.json`)
+      .then((url) => fetch(url))
+      .then((res) => res.json())
+      .catch(() => ({ version: 0, billboards: 0, tags: 0, orientation: 0 }));
+
+    const floorpanCompass = await getSceneApi(scene.type, `${prev}/user/floorplan.json?_=${config.version}`)
+      .then(url => fetch(url))
+      .then(res => res.json())
+      .then(data => data.compass)
+      .catch(() => null)
+    return (typeof floorpanCompass === 'number' ? floorpanCompass : config.orientation) 
+  }
+}
+
 export const taggingGets = {
   [SCENE_TYPE.fuse]: async (scene: Scene, options: string[]) => {
     if (!options.includes("hot")) return [];
@@ -125,7 +144,7 @@ export const taggingGets = {
     const config = await getSceneApi(scene.type, `${prev}/data/scene.json`)
       .then((url) => fetch(url))
       .then((res) => res.json())
-      .catch(() => ({ version: 0, billboards: 0, tags: 0 }));
+      .catch(() => ({ version: 0, billboards: 0, tags: 0, orientation: 0 }));
 
     if (options.includes("hot") && config.tags) {
       const medias = await getSceneApi(
@@ -138,11 +157,7 @@ export const taggingGets = {
 
       const reqs = medias.map((media: any) => {
         if (!validNum(media.position.x) || !validNum(media.position.y)) return;
-        return getSceneApi(
-          scene.type,
-          `${prev}/user/${media.icon}`
-          // `/v4-test/www/sdk/images/tag/${media.icon}`
-        )
+        return getSceneApi(scene.type, `${prev}/user/${media.icon}`)
           .then((url) => {
             tags.push({ url, position: media.position });
           })
@@ -170,7 +185,7 @@ export const taggingGets = {
           .catch(() => {});
       });
     }
-    return tags;
+    return tags
   },
 };
 
@@ -190,22 +205,29 @@ export type SceneFloor = {
     rotate: number;
     scale: number;
   };
+  compass?: number
 };
 
 export type SceneFloors = SceneFloor[];
 
+export const getFloors = {
+  [SCENE_TYPE.mesh]: async (scene: Scene) => {
+    return getSceneApi(scene.type, `/scene_view_data/${scene.m}/data/floorplan_cad.json?_=${Date.now()}`)
+      .then((url) => fetch(url))
+      .then((res) => res.json())
+      .catch(() => ({ floors: [] }))
+  }
+}
+
 export const lineGets = {
   [SCENE_TYPE.fuse]: async (scene: Scene) => {
     const tags = await taggingGets[SCENE_TYPE.fuse](scene, ["hot"]);
     return { name: "1楼", geos: [tags.map((item) => item.position)] };
   },
-  [SCENE_TYPE.mesh]: async (scene: Scene) => {
+  [SCENE_TYPE.mesh]: async (scene: Scene, floorName?: string) => {
     const prev = `/scene_view_data/${scene.m}/data/`;
     const [{ floors }, bounds] = await Promise.all([
-      getSceneApi(scene.type, `${prev}floorplan_cad.json?_=${Date.now()}`)
-        .then((url) => fetch(url))
-        .then((res) => res.json())
-        .catch(() => ({ floors: [] })),
+      getFloors[SCENE_TYPE.mesh](scene),
       getSceneApi(scene.type, `${prev}floorplan/info.json`)
         .then((url) => fetch(url))
         .then((res) => res.json())
@@ -215,35 +237,40 @@ export const lineGets = {
 
     const data: any = [];
 
-    const reqs = floors.map((floor: any, ndx: number) => {
-      const bound = {
-        ...(floor.cadInfo.cadBoundingBox || {}),
-        ...(bounds[ndx]?.bound || {}),
-      };
-      const item: any = {
-        name: floor.name,
-        thumb: "",
-        box: {
-          bound: {
-            ...bound,
-            y_min: -bound.y_max,
-            y_max: -bound.y_min,
+    const reqs = floors
+      .filter((item: any) => !floorName || item.name === floorName)
+      .map((floor: any, ndx: number) => {
+        const bound = {
+          ...(floor.cadInfo.cadBoundingBox || {}),
+          ...(bounds[ndx]?.bound || {}),
+        };
+        const item: any = {
+          name: floor.name,
+          thumb: "",
+          box: {
+            bound: {
+              ...bound,
+              y_min: -bound.y_max,
+              y_max: -bound.y_min,
+            },
+            rotate: floor.cadInfo.res,
+            scale: floor.cadInfo.currentScale,
           },
-          rotate: floor.cadInfo.res,
-          scale: floor.cadInfo.currentScale,
-        },
-        geos: extractConnectedSegments(floor.segment).map((geo) => {
-          return geo.map((id) => {
-            const p = floor["vertex-xy"].find((item: any) => item.id === id);
-            return { x: p.x, y: -p.y } as Pos;
-          });
-        }),
-      };
-      data.push(item);
-      return getSceneApi(scene.type, `${prev}floorplan/floor_${floor.subgroup}.png`)
-        .then((url) => (item.thumb = url))
-        .catch(() => (item.thumb = ""));
-    });
+          geos: extractConnectedSegments(floor.segment).map((geo) => {
+            return geo.map((id) => {
+              const p = floor["vertex-xy"].find((item: any) => item.id === id);
+              return { x: p.x, y: -p.y } as Pos;
+            });
+          }),
+        };
+        data.push(item);
+        return getSceneApi(
+          scene.type,
+          `${prev}floorplan/floor_${floor.subgroup}.png`
+        )
+          .then((url) => (item.thumb = url))
+          .catch(() => (item.thumb = ""));
+      });
     await Promise.all(reqs);
     return data;
   },

+ 25 - 17
src/utils/resource.ts

@@ -2,28 +2,36 @@ import { reactive } from "vue";
 import { Pos, Size } from "./math";
 
 let imageCache: Record<string, Promise<HTMLImageElement>>;
+let imageCache1: Record<string, HTMLImageElement>;
 export let imageInfo: Record<string, Record<'width' | 'height', number>> = reactive({})
 export let getImage = (url: string): Promise<HTMLImageElement> => {
   imageCache = {};
+  imageCache1 = {};
   getImage = (url: string) => {
     if (url in imageCache) {
-      return imageCache[url];
-    } else {
-      return (imageCache[url] = new Promise((resolve, reject) => {
-        const image = new Image();
-        image.onload = () => {
-          resolve(image);
-					imageInfo[url] = {
-						width: image.width,
-						height: image.height
-					}
-        };
-        image.onerror = (e) => {
-          reject(e);
-        };
-        image.src = url;
-      }));
-    }
+      if (imageCache1[url].width === 0 || imageCache1[url].height === 0) {
+        delete imageCache1[url]
+        delete imageCache[url];
+        delete imageInfo[url]
+      } else {
+        return imageCache[url];
+      }
+    } 
+    return (imageCache[url] = new Promise((resolve, reject) => {
+      const image = new Image();
+      image.onload = () => {
+        resolve(image);
+        imageInfo[url] = {
+          width: image.width,
+          height: image.height
+        }
+        imageCache1[url] = image
+      };
+      image.onerror = (e) => {
+        reject(e);
+      };
+      image.src = url;
+    }));
   };
   return getImage(url);
 };