瀏覽代碼

fix: 修改bug

bill 3 月之前
父節點
當前提交
2ff7b728a4
共有 45 個文件被更改,包括 662 次插入322 次删除
  1. 9 1
      public/icons/xueji_o.svg
  2. 1 0
      public/icons/xueji_o_1.svg
  3. 3 1
      public/icons/xuepo_o.svg
  4. 1 0
      public/icons/xuepo_o_1.svg
  5. 1 1
      src/core/components/arrow/temp-arrow.vue
  6. 7 7
      src/core/components/circle/index.ts
  7. 0 1
      src/core/components/circle/temp-circle.vue
  8. 13 10
      src/core/components/icon/icon.vue
  9. 4 1
      src/core/components/icon/temp-icon.vue
  10. 13 10
      src/core/components/image/image.vue
  11. 73 25
      src/core/components/line/index.ts
  12. 1 1
      src/core/components/line/single-line.vue
  13. 2 27
      src/core/components/line/temp-line.vue
  14. 7 7
      src/core/components/rectangle/index.ts
  15. 1 1
      src/core/components/serial/index.ts
  16. 14 12
      src/core/components/share/edit-point.vue
  17. 5 1
      src/core/components/table/table.vue
  18. 1 1
      src/core/components/triangle/triangle.vue
  19. 2 0
      src/core/helper/compass.vue
  20. 27 23
      src/core/helper/split-line.vue
  21. 37 31
      src/core/hook/use-component.ts
  22. 95 7
      src/core/hook/use-event.ts
  23. 16 6
      src/core/hook/use-expose.ts
  24. 59 15
      src/core/hook/use-global-vars.ts
  25. 2 2
      src/core/hook/use-transformer.ts
  26. 22 5
      src/core/hook/use-viewer.ts
  27. 38 51
      src/core/html-mount/propertys/hover-operate.vue
  28. 1 1
      src/core/html-mount/propertys/index.ts
  29. 7 8
      src/core/html-mount/propertys/mount.vue
  30. 1 1
      src/example/components/container/container.vue
  31. 3 3
      src/example/components/header/actions.ts
  32. 11 1
      src/example/components/slide/actions.ts
  33. 3 3
      src/example/constant.ts
  34. 1 1
      src/example/dialog/vr/vr.vue
  35. 16 8
      src/example/env.ts
  36. 1 0
      src/example/fuse/store.ts
  37. 2 1
      src/example/fuse/views/overview/header.vue
  38. 1 1
      src/example/fuse/views/overview/index.vue
  39. 1 1
      src/example/fuse/views/overview/slide-icons.vue
  40. 53 5
      src/example/fuse/views/tabulation/gen-tab.ts
  41. 54 7
      src/example/fuse/views/tabulation/index.vue
  42. 5 1
      src/example/fuse/views/tabulation/slide.vue
  43. 9 10
      src/example/platform/platform-draw.ts
  44. 30 22
      src/example/platform/platform-resource.ts
  45. 9 1
      src/utils/dom.ts

文件差異過大導致無法顯示
+ 9 - 1
public/icons/xueji_o.svg


文件差異過大導致無法顯示
+ 1 - 0
public/icons/xueji_o_1.svg


文件差異過大導致無法顯示
+ 3 - 1
public/icons/xuepo_o.svg


文件差異過大導致無法顯示
+ 1 - 0
public/icons/xuepo_o_1.svg


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

@@ -45,7 +45,7 @@
           :color="data.fill"
           @update:position="(p) => emit('update:position', { ndx, val: p })"
           @dragend="emit('update')"
-          :not-delete="true"
+          notDelete
         />
       </template>
     </v-group>

+ 7 - 7
src/core/components/circle/index.ts

@@ -30,14 +30,14 @@ export const addMode = "area";
 
 export const getMouseStyle = (data: CircleData) => {
   const fillStatus = data.fill && getMouseColors(data.fill);
-  const strokeStatus = getMouseColors(data.stroke || defaultStyle.stroke);
-  const strokeWidth = data.strokeWidth || defaultStyle.strokeWidth;
+  const strokeStatus = data.stroke && getMouseColors(data.stroke);
+  const strokeWidth = data.strokeWidth
   return {
-    default: {  stroke: data.stroke || defaultStyle.stroke, strokeWidth },
-    hover: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus.hover },
-    select: { fill: fillStatus && fillStatus.select, stroke: strokeStatus.select  },
-    focus: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus.hover },
-    press: { fill: fillStatus && fillStatus.press, stroke: strokeStatus.press },
+    default: {  stroke: data.stroke, strokeWidth },
+    hover: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus && strokeStatus.hover },
+    select: { fill: fillStatus && fillStatus.select, stroke: strokeStatus && strokeStatus.select  },
+    focus: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus && strokeStatus.hover },
+    press: { fill: fillStatus && fillStatus.press, stroke: strokeStatus && strokeStatus.press },
   };
 };
 

+ 0 - 1
src/core/components/circle/temp-circle.vue

@@ -51,7 +51,6 @@ const emit = defineEmits<{
 const config = useConfig();
 const data = computed(() => ({ ...defaultStyle, ...props.data }));
 
-console.log(data.value);
 const matConfig = computed(() => {
   const mat = new Transform(data.value.mat);
   return mat.decompose();

+ 13 - 10
src/core/components/icon/icon.vue

@@ -54,18 +54,21 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus({
         const rectShape = (shape as Group).findOne(".repShape") as Rect;
         const posShape = (shape as Group).findOne(".rep-position") as Group;
         const rect = new Rect();
-        rect.width(rectShape.width());
-
-        rect.height(rectShape.height());
-        const prevMat = posShape.getTransform().copy();
-        prevInvMat = prevMat.copy().invert();
-
-        const tf = shape.getTransform().copy().multiply(prevMat);
-        setShapeTransform(rect, tf);
-
+        const update = () => {
+          rect.width(rectShape.width());
+          rect.height(rectShape.height());
+          const prevMat = posShape.getTransform().copy();
+          prevInvMat = prevMat.copy().invert();
+          const tf = shape.getTransform().copy().multiply(prevMat);
+          setShapeTransform(rect, tf);
+        };
+        update();
         return {
           shape: rect,
-        } as any;
+          update(data) {
+            update();
+          },
+        };
       },
       callback,
       openSnap: false,

+ 4 - 1
src/core/components/icon/temp-icon.vue

@@ -1,5 +1,5 @@
 <template>
-  <v-group :config="groupConfig" v-if="groupConfig" ref="shape">
+  <v-group :config="groupConfig" v-if="groupConfig && svg" ref="shape">
     <v-group :config="initDecMat" name="rep-position">
       <v-rect :config="rectConfig" name="repShape" />
       <v-path v-for="config in pathConfigs" :config="config" name="icon-path" />
@@ -34,6 +34,9 @@ watch(
     svg.value = null;
     const svgContent = await getSvgContent(url);
     svg.value = parseSvgContent(svgContent);
+    if (svg.value.paths.length === 0) {
+      svg.value = null;
+    }
   },
   { immediate: true }
 );

+ 13 - 10
src/core/components/image/image.vue

@@ -50,19 +50,22 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus<
       getRepShape(shape) {
         const imgShape = (shape as Group).findOne(".repShape") as Image;
         const rect = new Rect();
-        rect.width(imgShape.width());
-        rect.height(imgShape.height());
-        const prevMat = new Transform().translate(
-          -imgShape.width() / 2,
-          -imgShape.height() / 2
-        );
-        prevInvMat = prevMat.copy().invert();
-
-        const tf = shape.getTransform().copy().multiply(prevMat);
-        setShapeTransform(rect, tf);
+        const update = () => {
+          rect.width(imgShape.width());
+          rect.height(imgShape.height());
+          const prevMat = new Transform().translate(
+            -imgShape.width() / 2,
+            -imgShape.height() / 2
+          );
+          prevInvMat = prevMat.copy().invert();
 
+          const tf = shape.getTransform().copy().multiply(prevMat);
+          setShapeTransform(rect, tf);
+        };
+        update();
         return {
           shape: rect,
+          update,
         } as any;
       },
       callback,

+ 73 - 25
src/core/components/line/index.ts

@@ -8,10 +8,9 @@ import { MathUtils } from "three";
 import { DrawStore } from "@/core/store/index.ts";
 import { getInitCtx, normalLineData } from "./use-draw.ts";
 
-
 export { default as Component } from "./line.vue";
 export { default as TempComponent } from "./temp-line.vue";
-export { useDraw } from './use-draw.ts'
+export { useDraw } from "./use-draw.ts";
 
 export const shapeName = "线段";
 export const defaultStyle = {
@@ -36,11 +35,11 @@ export const getMouseStyle = (data: LineData) => {
 
 export const getSnapInfos = (data: LineData) => {
   const vh = generateSnapInfos(getSnapPoints(data), true, false, true);
-  data.lines.forEach(item => {
-    const a = data.points.find(p => p.id === item.a)!
-    const b = data.points.find(p => p.id === item.b)!
+  data.lines.forEach((item) => {
+    const a = data.points.find((p) => p.id === item.a)!;
+    const b = data.points.find((p) => p.id === item.b)!;
     const prevVector = lineVector([a, b]);
-    const vLine = verticalVector(prevVector)
+    const vLine = verticalVector(prevVector);
 
     vh.push({
       point: a,
@@ -48,9 +47,9 @@ export const getSnapInfos = (data: LineData) => {
       linkDirections: [prevVector],
       linkAngle: [rangMod(MathUtils.radToDeg(vectorAngle(vLine)), 180)],
     });
-  })
-  return vh
-}
+  });
+  return vh;
+};
 
 export const getSnapPoints = (data: LineData) => {
   return data.points;
@@ -67,10 +66,10 @@ export type LineData = Partial<typeof defaultStyle> &
       stroke: string;
       dash: number[];
     }[];
-    polygon: { points: string[], id: string}[];
+    polygon: { points: string[]; id: string }[];
     attitude: number[];
-    updateTime?: number
-    calcTime?: number
+    updateTime?: number;
+    calcTime?: number;
   };
 
 export const interactiveToData: InteractiveTo<"line"> = ({
@@ -99,7 +98,7 @@ export const interactiveToData: InteractiveTo<"line"> = ({
 export const interactiveFixData: InteractiveFix<"line"> = ({ data, info }) => {
   const nv = [...info.consumed, info.cur!];
 
-  data.points.length = nv.length
+  data.points.length = nv.length;
   for (let i = 0; i < nv.length; i++) {
     if (inRevise(data.points[i], nv[i])) {
       if (!data.points[i]) {
@@ -115,7 +114,7 @@ export const interactiveFixData: InteractiveFix<"line"> = ({ data, info }) => {
       }
     }
   }
-  data.lines.length = nv.length - 1
+  data.lines.length = nv.length - 1;
   for (let i = 0; i < nv.length - 1; i++) {
     if (!data.lines[i]) {
       data.lines[i] = {
@@ -156,15 +155,64 @@ export const getPredefine = (key: keyof LineData) => {
   }
 };
 
-
 export const delItem = (store: DrawStore, data: LineData, childId: string) => {
-  const ndx = data.lines.findIndex(item => item.id === childId)
-  if (!~ndx) return;
-
-  const delLine = data.lines[ndx]
-  const ctx = getInitCtx()
-  ctx.del.lines[delLine.id] = delLine
-  data.lines.splice(ndx, 1)
-  normalLineData(data, ctx);
-  store.setItem('line', {value: data, id: data.id})
-}
+  if (!childId) {
+    store.delItem("line", data.id);
+    return;
+  }
+
+  let ndx;
+  if (~(ndx = data.lines.findIndex((item) => item.id === childId))) {
+    const delLine = data.lines[ndx];
+    const ctx = getInitCtx();
+    ctx.del.lines[delLine.id] = delLine;
+    data.lines.splice(ndx, 1);
+    normalLineData(data, ctx);
+    store.setItem("line", { value: data, id: data.id });
+  } else if (~(ndx = data.points.findIndex((item) => item.id === childId))) {
+    const { ctx } = delPoint(data, childId);
+    normalLineData(data, ctx);
+    store.setItem("line", { value: data, id: data.id });
+  }
+};
+
+export const delPoint = (data: LineData, id: string, ctx = getInitCtx()) => {
+  const p = data.points.find((item) => item.id === id);
+  if (!p) return { data, ctx };
+  const checkLines = data.lines.filter(
+    (item) => item.a === p.id || item.b === p.id
+  );
+  if (checkLines.length > 1) {
+    const joinPoints = new Set<string>();
+    checkLines.forEach((item) => {
+      joinPoints.add(item.a);
+      joinPoints.add(item.b);
+    });
+
+    if (joinPoints.size === 3) {
+      const prev = checkLines.find((item) => item.b === p.id);
+      const next = checkLines.find((item) => item.a === p.id);
+      if (prev && next) {
+        const l = { ...prev, id: onlyId(), b: next.b };
+        ctx.add.lines[l.id] = l;
+        data.lines.push(l);
+      } else {
+        const l = prev || next || checkLines[0];
+        const ps = [...joinPoints].filter((item) => item !== p.id);
+        const nl = { ...l, id: onlyId(), a: ps[0], b: ps[1] };
+        ctx.add.lines[l.id] = nl;
+        data.lines.push(nl);
+      }
+    }
+  }
+  checkLines.forEach((l) => {
+    ctx.del.lines[l.id] = l;
+    const ndx = data.lines.findIndex((ln) => ln.id === l.id);
+    ~ndx && data.lines.splice(ndx, 1);
+  });
+
+  ctx.del.points[p.id] = p;
+  const ndx = data.points.findIndex((pn) => pn.id === p.id);
+  ~ndx && data.points.splice(ndx, 1);
+  return { data, ctx };
+};

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

@@ -222,6 +222,6 @@ const dragstartHandler = (eIds: string[]) => {
 };
 const dragendHandler = () => {
   emit("update");
-  // snapInfos.forEach((item) => infos.remove(item));
+  snapInfos.forEach((item) => infos.remove(item));
 };
 </script>

+ 2 - 27
src/core/components/line/temp-line.vue

@@ -20,7 +20,7 @@
 
 <script lang="ts" setup>
 import { onlyId } from "@/utils/shared.ts";
-import { LineData } from "./index.ts";
+import { delPoint, LineData } from "./index.ts";
 import singleLine from "./single-line.vue";
 import { getInitCtx, NLineDataCtx, normalLineData, useInitData } from "./use-draw.ts";
 import { computed, ref } from "vue";
@@ -53,32 +53,7 @@ const updateBeforeHandler = (ids: string[]) => {
 };
 
 const delPointHandler = (p: LineData["points"][0]) => {
-  const checkLines = props.data.lines.filter(
-    (item) => item.a === p.id || item.b === p.id
-  );
-  if (checkLines.length > 1) {
-    const joinPoints = new Set<string>();
-    checkLines.forEach((item) => {
-      joinPoints.add(item.a);
-      joinPoints.add(item.b);
-    });
-
-    if (joinPoints.size === 3) {
-      const prev = checkLines.find((item) => item.b === p.id);
-      const next = checkLines.find((item) => item.a === p.id);
-      if (prev && next) {
-        addLineHandler({ ...prev, id: onlyId(), b: next.b });
-      } else {
-        const l = prev || next || checkLines[0];
-        const ps = [...joinPoints].filter((item) => item !== p.id);
-        addLineHandler({ ...l, id: onlyId(), a: ps[0], b: ps[1] });
-      }
-    }
-  }
-  checkLines.forEach(delLineHandler);
-  ctx.del.points[p.id] = p;
-  const ndx = props.data.points.findIndex((pn) => pn.id === p.id);
-  ~ndx && props.data.points.splice(ndx, 1);
+  delPoint(props.data, p.id, ctx)
 };
 
 const addPointHandler = (p: LineData["points"][0], l: LineData["lines"][0]) => {

+ 7 - 7
src/core/components/rectangle/index.ts

@@ -21,21 +21,21 @@ export const defaultStyle = {
 
 export const getMouseStyle = (data: RectangleData) => {
   const fillStatus = data.fill && getMouseColors(data.fill);
-  const strokeStatus = getMouseColors(data.stroke || defaultStyle.stroke);
-  const strokeWidth = data.strokeWidth || defaultStyle.strokeWidth;
+  const strokeStatus = data.stroke && getMouseColors(data.stroke);
+  const strokeWidth = data.strokeWidth ;
 
   return {
     default: {
       fill: data.fill,
-      stroke: data.stroke || defaultStyle.stroke,
+      stroke: data.stroke ,
       strokeWidth,
     },
-    hover: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus.hover },
-    focus: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus.hover },
-    press: { fill: fillStatus && fillStatus.press, stroke: strokeStatus.press },
+    hover: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus && strokeStatus.hover },
+    focus: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus && strokeStatus.hover },
+    press: { fill: fillStatus && fillStatus.press, stroke: strokeStatus && strokeStatus.press },
     select: {
       fill: fillStatus && fillStatus.select,
-      stroke: strokeStatus.select,
+      stroke: strokeStatus && strokeStatus.select,
     },
   };
 };

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

@@ -26,7 +26,7 @@ export const defaultTableStyle = {
   valueColWidth: 180,
   repColCount: 1,
 };
-export const shapeName = "图例";
+export const shapeName = "序号";
 export const addMode = "dot";
 export const defaultStyle = {
   ...circleDefaultStyle,

+ 14 - 12
src/core/components/share/edit-point.vue

@@ -3,7 +3,11 @@
     :config="{ ...style, ...position, hitStrokeWidth: style.strokeWidth }"
     ref="circle"
   />
-  <Operate :target="circle" :menus="[{ label: '删除', handler: () => emit('delete') }]" />
+  <Operate
+    :target="circle"
+    :menus="[{ label: '删除', handler: () => emit('delete') }]"
+    v-if="!notDelete"
+  />
 </template>
 
 <script lang="ts" setup>
@@ -101,17 +105,15 @@ const offset = useShapeDrag(circle);
 const hResult = useShapeIsHover(circle);
 const isHover = hResult[0];
 const cursor = useCursor();
-if (!props.notDelete) {
-  watch(
-    isHover,
-    (hover, _, onCleanup) => {
-      if (hover) {
-        onCleanup(cursor.push("./icons/m_move.png"));
-      }
-    },
-    { immediate: true }
-  );
-}
+watch(
+  isHover,
+  (hover, _, onCleanup) => {
+    if (hover) {
+      onCleanup(cursor.push("./icons/m_move.png"));
+    }
+  },
+  { immediate: true }
+);
 const dragIng = ref(false);
 let init: Pos;
 watch(offset, (offset, oldOffsert) => {

+ 5 - 1
src/core/components/table/table.vue

@@ -40,7 +40,7 @@ import {
   useTransformer,
 } from "@/core/hook/use-transformer.ts";
 import { copy, getResizeCorsur } from "@/utils/shared.ts";
-import { computed, ref, watch, watchEffect } from "vue";
+import { computed, nextTick, ref, watch, watchEffect } from "vue";
 import { Group } from "konva/lib/Group";
 import { DC } from "@/deconstruction.js";
 import { Rect } from "konva/lib/shapes/Rect";
@@ -309,6 +309,7 @@ const menuShowHandler = () => {
         );
         data.value.height += temprow[0].height;
         sync(data.value);
+        nextTick(() => shape.value?.getNode().fire("bound-change"));
         emit("updateShape", { ...data.value });
       },
     });
@@ -321,6 +322,7 @@ const menuShowHandler = () => {
         const temprow = data.value.content[config.rowNdx];
         data.value.content.splice(config.rowNdx, 1);
         data.value.height -= temprow[0].height;
+        nextTick(() => shape.value?.getNode().fire("bound-change"));
         if (data.value.content.length === 0) {
           emit("delShape");
         } else {
@@ -341,6 +343,7 @@ const menuShowHandler = () => {
           row.splice(config.colNdx, 0, { ...tempCol, content: "" });
         }
         data.value.width += tempCol.width;
+        nextTick(() => shape.value?.getNode().fire("bound-change"));
         sync(data.value);
         emit("updateShape", data.value);
       },
@@ -358,6 +361,7 @@ const menuShowHandler = () => {
           row.splice(config.colNdx, 1);
         }
         data.value.width -= tempCol.width;
+        nextTick(() => shape.value?.getNode().fire("bound-change"));
         if (data.value.content[0].length === 0) {
           emit("delShape");
         } else {

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

@@ -37,7 +37,7 @@ const { shape, tData, operateMenus, describes, data } = useComponentStatus({
   emit,
   props,
   getMouseStyle,
-  defaultStyle,
+  defaultStyle: {},
   transformType: "line",
   // alignment(data, mat) {
   //   return matResponse({ mat, data, increment: true });

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

@@ -56,6 +56,7 @@ watch(
       data.value.height = maxWidth;
       data.value.width = (svg.width / svg.height) * maxWidth;
     }
+    console.log(data.value);
   },
   { immediate: true }
 );
@@ -82,6 +83,7 @@ const [style] = useAnimationMouseStyle({
 
 let currentRotation = store.config.compass.rotation;
 const describes = mergeDescribes(data, {}, ["rotate"]);
+console.log(describes);
 describes.rotate = {
   ...describes.rotate,
   sort: 3,

+ 27 - 23
src/core/helper/split-line.vue

@@ -24,7 +24,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, onUnmounted, watchEffect } from "vue";
+import { computed, onUnmounted } from "vue";
 import { components } from "../components";
 import { useComponentsAttach } from "../hook/use-component";
 import {
@@ -121,18 +121,7 @@ const rectAxisSteps = computed(() => {
   };
   if (!center.value || !rang.value) return axis;
 
-  const pushStep = (axis: number[], cur: number, pixel: Pos) => {
-    if (
-      !(
-        pixel.x >= rang.value!.lt.x &&
-        pixel.x <= rang.value!.rb.x &&
-        pixel.y >= rang.value!.lt.y &&
-        pixel.y <= rang.value!.rb.y
-      )
-    ) {
-      return;
-    }
-
+  const pushStep = (axis: number[], cur: number, isLast: boolean) => {
     const can = (data: number) => {
       return Math.abs(data - cur) > config.labelLineConfig.splitOffset;
     };
@@ -150,18 +139,33 @@ const rectAxisSteps = computed(() => {
       axis.splice(i + 1, 0, cur);
     } else if (i === -1) {
       axis[0] = cur;
-    } else if (i === axis.length - 1) {
-      // axis[i] = cur;
+    } else if (isLast) {
+      axis[axis.length - 1] = cur;
     }
   };
-  const pixels = points.value.map((p) => viewMat.value.point(p));
-  const ph = [...pixels].sort((a, b) => a.x - b.x);
-  const pv = [...pixels].sort((a, b) => a.y - b.y);
-  for (const point of pv) {
-    pushStep(point.x > center.value.x ? axis.right : axis.left, point.y, point);
-  }
-  for (const point of ph) {
-    pushStep(point.y > center.value.y ? axis.bottom : axis.top, point.x, point);
+  const pixels = points.value
+    .map((p) => viewMat.value.point(p))
+    .filter(
+      (pixel) =>
+        pixel.x >= rang.value!.lt.x &&
+        pixel.x <= rang.value!.rb.x &&
+        pixel.y >= rang.value!.lt.y &&
+        pixel.y <= rang.value!.rb.y
+    );
+  const configs = [
+    ["x", (x: number) => x <= center.value!.x, "left", "y"],
+    ["x", (x: number) => x > center.value!.x, "right", "y"],
+    ["y", (y: number) => y <= center.value!.y, "top", "x"],
+    ["y", (y: number) => y > center.value!.y, "bottom", "x"],
+  ] as const;
+  for (const c of configs) {
+    const datas = pixels
+      .filter((p) => c[1](p[c[0]]))
+      .map((item) => item[c[3]])
+      .sort((a, b) => a - b);
+    datas.forEach((n, i) => {
+      pushStep(axis[c[2]], n, i === datas.length - 1);
+    });
   }
   return axis;
 });

+ 37 - 31
src/core/hook/use-component.ts

@@ -115,7 +115,7 @@ export const useComponentMenus = <T extends DrawItem>(
   // 隐藏
   operateMenus.push(
     reactive({
-      label: '隐藏',
+      label: "隐藏",
       handler() {
         data.value.hide = true;
         emit("updateShape", { ...data.value });
@@ -149,12 +149,14 @@ export const useComponentMenus = <T extends DrawItem>(
     });
   }
 
-  operateMenus.push({
-    label: `删除`,
-    handler() {
-      emit("delShape");
-    },
-  });
+  if (!data.value.disableDelete) {
+    operateMenus.push({
+      label: `删除`,
+      handler() {
+        emit("delShape");
+      },
+    });
+  }
 
   return operateMenus;
 };
@@ -170,7 +172,7 @@ export type UseComponentStatusProps<
   getMouseStyle: any;
   defaultStyle: any;
   propertys: PropertyKeys;
-  debug?: boolean
+  debug?: boolean;
   noJoinZindex?: boolean;
   noOperateMenus?: boolean;
   transformType?: "line" | "mat" | "custom";
@@ -278,9 +280,9 @@ export const useComponentsAttach = <T>(
   for (const type of types) {
     cleanups.push(
       globalWatch(
-        () => store.getTypeItems(type).length,
+        () => store.getTypeItems(type),
         (_a, _, onCleanup) => {
-          const items = store.getTypeItems(type)
+          const items = store.getTypeItems(type);
           if (!items) return;
           for (const item of items) {
             const attachWatchStop = watchEffect((onCleanup) => {
@@ -303,7 +305,7 @@ export const useComponentsAttach = <T>(
             });
           }
         },
-        { immediate: true }
+        { immediate: true, deep: true }
       )
     );
   }
@@ -344,28 +346,32 @@ export const useOnComponentBoundChange = () => {
         shape.off("bound-change", () => syncBd);
       };
     };
-    const cleanups: (() => void)[] = []
+    const cleanups: (() => void)[] = [];
     if (viewListener) {
-      cleanups.push(watch(transform, () =>
-        $shapes.value.forEach(($shape) => update($shape, "transform"))
-      ))
+      cleanups.push(
+        watch(transform, () =>
+          $shapes.value.forEach(($shape) => update($shape, "transform"))
+        )
+      );
     }
-    cleanups.push(watch(
-      $shapes,
-      ($shapes, _, onCleanup) => {
-        const cleanups = $shapes.flatMap(($shape) => {
-          const item = getComponentData($shape);
-          return [
-            watch(item, () => nextTick(() => update($shape, "data")), {
-              deep: true,
-            }),
-            shapeListener($shape),
-          ];
-        });
-        onCleanup(mergeFuns(cleanups));
-      },
-      { immediate: true }
-    ))
+    cleanups.push(
+      watch(
+        $shapes,
+        ($shapes, _, onCleanup) => {
+          const cleanups = $shapes.flatMap(($shape) => {
+            const item = getComponentData($shape);
+            return [
+              watch(item, () => nextTick(() => update($shape, "data")), {
+                deep: true,
+              }),
+              shapeListener($shape),
+            ];
+          });
+          onCleanup(mergeFuns(cleanups));
+        },
+        { immediate: true }
+      )
+    );
 
     const onDestroy = mergeFuns(cleanups);
     quitHooks.push(onDestroy);

+ 95 - 7
src/core/hook/use-event.ts

@@ -1,9 +1,12 @@
 import { Size } from "@/utils/math.ts";
 import { listener } from "../../utils/event.ts";
 import { installGlobalVar, useStage } from "./use-global-vars.ts";
-import { nextTick, ref, watchEffect } from "vue";
+import { nextTick, reactive, ref, watch, watchEffect } from "vue";
 import { KonvaEventObject } from "konva/lib/Node";
 import { debounce } from "@/utils/shared.ts";
+import { Shape } from "konva/lib/Shape";
+import { shapeTreeContain } from "@/utils/shape.ts";
+import { EntityShape } from "@/deconstruction.js";
 
 export const useListener = <
   T extends HTMLElement,
@@ -69,8 +72,8 @@ export const useGlobalResize = installGlobalVar(() => {
         }
       });
     }
-    fix.value = !!fixSize
-    setSize()
+    fix.value = !!fixSize;
+    setSize();
   };
 
   return {
@@ -80,7 +83,7 @@ export const useGlobalResize = installGlobalVar(() => {
       size,
       fix,
     },
-    onDestroy: listener(window, 'resize', debounce(setSize, 16))
+    onDestroy: listener(window, "resize", debounce(setSize, 16)),
   };
 }, Symbol("resize"));
 
@@ -143,6 +146,91 @@ export const usePreemptiveListener = installGlobalVar(() => {
 }, Symbol("preemptiveListener"));
 
 export const cancelBubbleEvent = (ev: KonvaEventObject<any>) => {
-  ev.cancelBubble = true
-  ev.evt.stopPropagation
-}
+  ev.cancelBubble = true;
+  ev.evt.stopPropagation;
+};
+
+export const useGlobalOnlyRightClickShape = installGlobalVar(() => {
+  const stage = useStage();
+  const checkShapes = reactive([]) as EntityShape[];
+  const events = [] as Array<((ev: MouseEvent) => void)[]>;
+  const cancels = [] as Array<(((ev: MouseEvent) => void) | undefined)[]>;
+
+  const addShape = (
+    shape: EntityShape,
+    fn: (ev: MouseEvent) => void,
+    cancel?: (ev: MouseEvent) => void
+  ) => {
+    const ndx = checkShapes.indexOf(shape);
+    if (~ndx) {
+      events[ndx].push(fn);
+      cancels[ndx].push(cancel);
+    } else {
+      checkShapes.push(shape);
+      events.push([fn]);
+      cancels.push([cancel]);
+    }
+    return () => delShape(shape, fn);
+  };
+  const delShape = (shape: EntityShape, fn: (ev: MouseEvent) => void) => {
+    const ndx = checkShapes.indexOf(shape);
+    if (!~ndx) return;
+    const evNdx = events[ndx].indexOf(fn);
+    if (!~evNdx) return;
+    events[ndx].splice(evNdx, 1);
+    cancels[ndx].splice(evNdx, 1);
+    if (events[ndx].length === 0) {
+      checkShapes.splice(ndx, 1);
+      cancels.splice(ndx, 1);
+      events.splice(ndx, 1);
+    }
+  };
+  const init = (dom: HTMLDivElement) => {
+    let prevShape: EntityShape;
+    const hasRClick = (ev: MouseEvent) => {
+      let clickShape: any
+      let ndx = -1
+
+      if (ev.button === 2) {
+        const pos = stage.value?.getNode().pointerPos;
+        if (!pos) return false;
+        clickShape = stage.value?.getNode().getIntersection(pos);
+        do {
+          ndx = checkShapes.indexOf(clickShape!);
+          if (~ndx) {
+            break;
+          }
+        } while ((clickShape = clickShape?.parent));
+      }
+      if (prevShape) {
+        const prevNdx = checkShapes.indexOf(prevShape!);
+        ~prevNdx && cancels[prevNdx].forEach((fn) => fn && fn(ev));
+      }
+      prevShape = clickShape;
+      return ~ndx && events[ndx].forEach((fn) => fn(ev));
+    };
+
+    dom.addEventListener("contextmenu", hasRClick);
+    dom.addEventListener("pointerdown", hasRClick);
+    return () => {
+      dom.removeEventListener("contextmenu", hasRClick);
+      dom.removeEventListener("pointerdown", hasRClick);
+    };
+  };
+
+  watch(
+    () => stage.value?.getNode().container(),
+    (dom, _, onCleanup) => {
+      console.log(dom);
+      if (dom) {
+        onCleanup(init(dom));
+      }
+    },
+    { immediate: true }
+  );
+
+  return {
+    add: addShape,
+    remove: delShape,
+  };
+});

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

@@ -3,7 +3,8 @@ import {
   useCursor,
   useInstanceProps,
   useLayers,
-  useMountMenusAttachs,
+  useMountMenusFilter,
+  useMouseMenusFilter,
   useStage,
   useTempStatus,
 } from "./use-global-vars.ts";
@@ -88,9 +89,7 @@ export const useShortcutKey = () => {
 
       const addCount = quitDrawShape();
       // 钢笔工具需要右键两次才退出,右键一次相当于完成
-      const isDots = ["dots", "single-dots"].includes(
-        components[iProps.type].addMode
-      );
+      const isDots = ["single-dots"].includes(components[iProps.type].addMode);
       if (isDots && addCount > 0) {
         setTimeout(() => {
           enterDrawShape(iProps.type, iProps.preset, iProps.operate?.single);
@@ -110,14 +109,18 @@ export const useShortcutKey = () => {
       if (ev.target !== document.body) return;
 
       if (ev.key === "z" && ev.ctrlKey) {
+        ev.preventDefault();
         history.hasUndo.value && history.undo();
       } else if (ev.key === "y" && ev.ctrlKey) {
+        ev.preventDefault();
         history.hasRedo.value && history.redo();
       } else if (ev.key === "s" && ev.ctrlKey) {
+        ev.preventDefault();
         // 保存
         // history.saveLocal();
       } else if (ev.key === "Delete" || ev.key === "Backspace") {
         // 删除
+        ev.preventDefault();
 
         const isSelect = status.selects.length;
         const shapes = isSelect ? status.selects : status.actives;
@@ -147,6 +150,7 @@ export const useShortcutKey = () => {
           })
           .filter((item) => !!item);
         history.onceTrack(() => {
+          console.log(delItems);
           delItems.forEach(([type, item, childId]) => {
             if (!childId) {
               if (components[type as ShapeType].delItem) {
@@ -155,7 +159,11 @@ export const useShortcutKey = () => {
                 store.delItem(type as ShapeType, item!.id);
               }
             } else {
-              components[type as ShapeType].delItem!(store, item as any, childId);
+              components[type as ShapeType].delItem!(
+                store,
+                item as any,
+                childId
+              );
             }
           });
         });
@@ -167,6 +175,7 @@ export const useShortcutKey = () => {
           }
         }
       } else if (operMode.value.mulSelection && ev.key === "A") {
+        ev.preventDefault();
         if (status.selects.length) {
           status.selects = [];
         } else {
@@ -301,7 +310,8 @@ export const useExpose = installGlobalVar(() => {
     viewer,
     presetAdd: interactiveProps,
     config: useConfig(),
-    mountMenus: useMountMenusAttachs(),
+    mountFilter: useMountMenusFilter(),
+    menusFilter: useMouseMenusFilter(),
     proportion: useProportion(),
     getDXF: useGetDXF(),
   };

+ 59 - 15
src/core/hook/use-global-vars.ts

@@ -376,32 +376,76 @@ export const useTempStatus = installGlobalVar(() => {
 });
 
 
-export const useMountMenusAttachs = installGlobalVar(() => {
-  const globalAttachs = ref<{[key in ShapeType]?: PropertyDescribes}>({})
-  const shapeAttachs = ref<Record<string, PropertyDescribes>>({})
-  const setShapeAttachs = (id: string, descs?: PropertyDescribes) => {
+export const useMountMenusFilter = installGlobalVar(() => {
+  type Val = (d: PropertyDescribes) => PropertyDescribes
+  const globalFilter = ref<{[key in ShapeType]?: Val}>({})
+  const shapeFilter = ref<Record<string, Val>>({})
+  const setShapeFilter = (id: string, descs?: Val) => {
     if (!descs) {
-      delete shapeAttachs.value[id]
+      delete shapeFilter.value[id]
     } else {
-      shapeAttachs.value[id] = descs
+      shapeFilter.value[id] = descs
     }
   }
-  const setAttachs = (type: ShapeType, descs?: PropertyDescribes,) => {
+  const setFilter = (type: ShapeType, descs?: Val) => {
     if (!descs) {
-      delete globalAttachs.value[type]
+      delete globalFilter.value[type]
     } else {
-      globalAttachs.value[type] = descs
+      globalFilter.value[type] = descs
     }
     
   }
 
   return {
-    setShapeAttachs,
-    setAttachs,
-    getAttachs(type: ShapeType, id: string) {
-      return {
-        ...(globalAttachs.value[type] || {}),
-        ...(shapeAttachs.value[id] || {})
+    setMenusFilter: setFilter,
+    setShapeMenusFilter: setShapeFilter,
+    getFilter(type: ShapeType, id: string) {
+      return (menus: PropertyDescribes) => {
+        if (globalFilter.value[type]) {
+          menus = globalFilter.value[type](menus)
+        }
+        if (shapeFilter.value[id]) {
+          menus = shapeFilter.value[id](menus)
+        }
+        return menus
+      }
+    }
+  }
+})
+
+
+export const useMouseMenusFilter = installGlobalVar(() => {
+  type Menu = { icon?: any; label?: string; handler: () => void }
+  const globalFilter = ref<{[key in ShapeType]?: (menus: Menu[]) => Menu[]}>({})
+  const shapeFilter = ref<Record<string, (menus: Menu[]) => Menu[]>>({})
+  const setShapeFilter = (id: string, filter?: (menus: Menu[]) => Menu[]) => {
+    if (!filter) {
+      delete shapeFilter.value[id]
+    } else {
+      shapeFilter.value[id] = filter
+    }
+  }
+  const setFilter = (type: ShapeType, filter?: (menus: Menu[]) => Menu[]) => {
+    if (!filter) {
+      delete globalFilter.value[type]
+    } else {
+      globalFilter.value[type] = filter
+    }
+    
+  }
+
+  return {
+    setMenusFilter: setFilter,
+    setShapeMenusFilter: setShapeFilter,
+    getFilter(type: ShapeType, id: string) {
+      return (menus: Menu[]) => {
+        if (globalFilter.value[type]) {
+          menus = globalFilter.value[type](menus)
+        }
+        if (shapeFilter.value[id]) {
+          menus = shapeFilter.value[id](menus)
+        }
+        return menus
       }
     }
   }

+ 2 - 2
src/core/hook/use-transformer.ts

@@ -1,5 +1,5 @@
 import { useMouseShapeStatus } from "./use-mouse-status.ts";
-import { computed, Ref, ref, toRaw, watch, watchEffect } from "vue";
+import { computed, nextTick, Ref, ref, toRaw, watch, watchEffect } from "vue";
 import { DC, EntityShape } from "../../deconstruction";
 import {
   installGlobalVar,
@@ -381,6 +381,7 @@ export const useShapeTransformer = <T extends EntityShape>(
     const boundHandler = () => {
       if (!selfFire) {
         rep.update && rep.update();
+        // transformer.forceUpdate()
       }
       rect.value = rep.tempShape.getClientRect();
     };
@@ -471,7 +472,6 @@ export const useShapeTransformer = <T extends EntityShape>(
         const stopLeaveUpdate = watch(
           data,
           () => {
-            console.log("update");
             rep.update && rep.update();
           },
           { flush: "post", deep: true }

+ 22 - 5
src/core/hook/use-viewer.ts

@@ -1,5 +1,5 @@
 import { Viewer } from "../viewer.ts";
-import { computed, ref, watch, watchEffect } from "vue";
+import { computed, nextTick, 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";
@@ -12,6 +12,7 @@ import { useFormalLayer } from "./use-layer.ts";
 import { Group } from "konva/lib/Group";
 import { DataGroupId } from "@/constant/index.ts";
 import { IRect } from "konva/lib/types";
+import { MathUtils } from "three";
 
 export const useViewer = installGlobalVar(() => {
   const stage = useStage();
@@ -19,7 +20,7 @@ export const useViewer = installGlobalVar(() => {
   const can = useCan();
   const size = useResize();
   const transform = ref(new Transform());
-  const disabled = ref(false)
+  const disabled = ref(false);
   const sizeMat = ref<Transform | null>(null);
 
   const init = (dom: HTMLDivElement) => {
@@ -63,7 +64,7 @@ export const useViewer = installGlobalVar(() => {
       transform: transform,
       viewer,
       sizeMat,
-      disabled
+      disabled,
     },
     onDestroy: globalWatch(
       () => can.viewMouseReact,
@@ -210,12 +211,29 @@ export const useGetViewBoxPositionPixel = () => {
 export const useSetViewport = () => {
   const formalLayer = useFormalLayer();
   const { viewer } = useViewer();
+  const config = useViewerTransformConfig();
 
   const initViewport = () => {
     const rect = formalLayer
       .value!.findOne<Group>("#" + DataGroupId)!
       .getClientRect();
-    rect.width > 0 && rect.height > 0 && setViewport(rect);
+    if (!(rect.width > 0 && rect.height > 0)) return;
+    const center = {
+      x: rect.x + rect.width / 2,
+      y: rect.y + rect.height / 2,
+    };
+    const mat = new Transform()
+      .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 end = mat.point({
+      x: rect.x + rect.width,
+      y: rect.y + rect.height,
+    });
+    let width = end.x - start.x;
+    let height = end.y - start.y;
+    setViewport({ ...start, width, height });
   };
   const setViewport = (rect: IRect, padding = 20, isPixel = true) => {
     const invMat = viewer.transform.invert();
@@ -230,7 +248,6 @@ export const useSetViewport = () => {
           y: rect.y + rect.height,
         };
 
-    viewer.setViewMat(new Transform())
     viewer.setBound({
       targetBound: {
         ...lt,

+ 38 - 51
src/core/html-mount/propertys/hover-operate.vue

@@ -19,17 +19,17 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, nextTick, ref, watch, watchEffect } from "vue";
-import { useStage } from "../../hook/use-global-vars.ts";
+import { computed, nextTick, ref, watch } from "vue";
+import { useMouseMenusFilter, useStage } from "../../hook/use-global-vars.ts";
 import { useMode } from "../../hook/use-status.ts";
 import { DC, EntityShape } from "@/deconstruction.js";
 import { useViewerTransformConfig } from "../../hook/use-viewer.ts";
 import { Transform } from "konva/lib/Util";
 import { DomMountId } from "@/constant/index.ts";
 import { ElMenu, ElMenuItem } from "element-plus";
-import { useFormalLayer } from "../../hook/use-layer.ts";
-import { shapeTreeContain } from "@/utils/shape.ts";
 import { Mode } from "@/constant/mode.ts";
+import { useGlobalOnlyRightClickShape } from "@/core/hook/use-event.ts";
+import { useStore } from "@/core/store/index.ts";
 
 const props = defineProps<{
   target: DC<EntityShape> | undefined;
@@ -42,60 +42,48 @@ const emit = defineEmits<{
 
 const layout = ref<HTMLDivElement>();
 const stage = useStage();
-// const status = useMouseShapeStatus(computed(() => props.target));
-const layer = useFormalLayer();
-const rightClick = ref(false);
-
-const hasRClick = (ev: MouseEvent) => {
-  if (ev.button !== 2) return false;
-  const shape = props.target?.getNode();
-  const pos = stage.value?.getNode().pointerPos;
-  if (!shape || !pos || !layer.value) return false;
-  let clickShape = stage.value?.getNode().getIntersection(pos);
-  return !!clickShape && shapeTreeContain(shape, clickShape) === shape;
-};
+const { getFilter } = useMouseMenusFilter();
+const store = useStore();
+const id = computed(() => props.target?.getNode().id());
+const type = computed(() => id.value && store.getType(id.value));
+const menus = computed(() => {
+  if (id.value && type.value) {
+    return getFilter(type.value, id.value)(props.menus);
+  } else {
+    return props.menus;
+  }
+});
 
+const show = ref(false);
+const { add } = useGlobalOnlyRightClickShape();
 const mode = useMode();
-watchEffect((onCleanup) => {
-  const dom = stage.value?.getStage().container();
-  if (!dom || mode.value.has(Mode.draw)) return;
-
-  const clickHandler = async (ev: MouseEvent) => {
-    const show = hasRClick(ev);
-    if (show && rightClick.value) {
-      rightClick.value = false;
-      await nextTick();
-    }
-    rightClick.value = show;
-  };
-
-  dom.addEventListener("contextmenu", clickHandler);
-  dom.addEventListener("pointerdown", clickHandler);
-  onCleanup(() => {
-    dom.removeEventListener("contextmenu", clickHandler);
-    dom.removeEventListener("pointerdown", clickHandler);
-  });
-});
+watch(
+  () => !mode.value.has(Mode.draw) && props.target?.getNode(),
+  (shape, _, onCleanup) => {
+    if (!shape) return;
+    const del = add(
+      shape,
+      async () => {
+        await nextTick();
+        show.value = true;
+      },
+      () => {
+        show.value = false;
+      }
+    );
+    onCleanup(del);
+  }
+);
 
 const clickHandler = (handler: () => void) => {
   handler();
-  rightClick.value = false;
+  show.value = false;
 };
 
-const hidden = computed(
-  () =>
-    !stage.value?.getStage() ||
-    !props.target?.getNode() ||
-    // !status.value.hover ||
-    // status.value.active ||
-    // transformIngShapes.value.length !== 0
-    !rightClick.value
-);
-
 const move = new Transform();
 const pointer = ref<string | null>(null);
 const calcPointer = async () => {
-  if (hidden.value) {
+  if (!show.value) {
     pointer.value = null;
     return;
   } else if (pointer.value) {
@@ -153,7 +141,7 @@ watch(
 
 let timeout: any;
 const resetPointer = () => {
-  if (hidden.value) {
+  if (!show.value) {
     pointer.value = null;
     return;
   } else if (pointer.value) {
@@ -163,12 +151,11 @@ const resetPointer = () => {
   timeout = setTimeout(calcPointer, 16);
 };
 
-watch(hidden, resetPointer);
+watch(show, resetPointer);
 watch(useViewerTransformConfig(), () => {
   pointer.value = null;
   resetPointer();
 });
-// watch(() => props.data, resetPointer);
 </script>
 
 <style lang="scss" scoped>

+ 1 - 1
src/core/html-mount/propertys/index.ts

@@ -8,7 +8,7 @@ import Proportion from "./components/proportion.vue";
 import Text from "./components/text.vue";
 import FixProportion from "./components/fix-proportion.vue";
 import originDescribes from "./describes.json";
-import { Ref } from "vue";
+import { ref, Ref } from "vue";
 
 export const colorType = "color";
 export const selectType = "select";

+ 7 - 8
src/core/html-mount/propertys/mount.vue

@@ -46,7 +46,7 @@
 
 <script lang="ts" setup>
 import { computed, ref, watch } from "vue";
-import { useMountMenusAttachs, useStage } from "../../hook/use-global-vars.ts";
+import { useMountMenusFilter, useStage } from "../../hook/use-global-vars.ts";
 import { useMode } from "../../hook/use-status.ts";
 import { PropertyDescribes, propertyComponents } from "./index.ts";
 import { DC, EntityShape } from "@/deconstruction.js";
@@ -85,14 +85,13 @@ const getPredefine = (key: string) => {
   const predefine = getPredefine && (getPredefine as any)(key as keyof DrawItem);
   return predefine || {};
 };
-const { getAttachs } = useMountMenusAttachs();
+const { getFilter } = useMountMenusFilter();
 const describes = computed(() => {
-  const attachs = (type.value && id.value && getAttachs(type.value, id.value)) || {};
-  const describes = {
-    ...props.describes,
-    ...attachs,
-  };
-  return describes;
+  if (type.value && id.value) {
+    return getFilter(type.value, id.value)(props.describes)
+  } else {
+    return props.describes
+  }
 });
 
 const describeItems = computed(() => {

+ 1 - 1
src/example/components/container/container.vue

@@ -13,7 +13,7 @@
           @click="draw.quitDrawShape()"
           circle
           class="h-bottom"
-          v-if="draw?.drawType === 'single-dots' || draw?.drawType === 'dots'"
+          v-if="draw?.drawType === 'single-dots'"
         >
           <template #icon>
             <icon name="pic_yes" size="32px" color="#41c732" />

+ 3 - 3
src/example/components/header/actions.ts

@@ -99,7 +99,7 @@ export const getHeaderActions = (draw: Draw) => {
               draw.config.showGrid = oldShowGrid
             })
           },
-          text: "jpg",
+          text: "JPG",
           icon: "a-visible",
         },
         {
@@ -118,7 +118,7 @@ export const getHeaderActions = (draw: Draw) => {
               ElMessage.success("导出成功");
             })
           },
-          text: "png",
+          text: "PNG",
           icon: "a-visible",
         },
         {
@@ -128,7 +128,7 @@ export const getHeaderActions = (draw: Draw) => {
               saveAs(dxf, "canvas.zip");
             })
           },
-          text: "dxf",
+          text: "DXF",
           icon: "a-visible",
         },
       ],

+ 11 - 1
src/example/components/slide/actions.ts

@@ -10,6 +10,7 @@ import { Draw } from "../container/use-draw";
 import { MenuItem } from "./menu";
 import { copy } from "@/utils/shared";
 import { ElMessage } from "element-plus";
+import { getRealPixel } from "@/example/fuse/views/tabulation/gen-tab";
 
 
 export type PresetAdd<T extends ShapeType = ShapeType> = {
@@ -77,7 +78,13 @@ export const imp: MenuItem = {
       icon: "local_i",
       name: "本地",
       handler: async (draw: Draw) => {
-        const files = await selectFile(false, '.png, .jpg, .jpeg')
+        let files: File[]
+        try {
+          files = await selectFile(false, '.png, .jpg, .jpeg')
+        } catch (e: any) {
+          ElMessage.error(e.message)
+          return;
+        }
         const url = await window.platform.uploadResourse(files[0])
         const image = await getImage(url)
         ElMessage.warning('请在画图面板中选择放置位置,鼠标右键取消')
@@ -96,6 +103,9 @@ export const getPaperConfig = (p: number[], scale: number) => {
   const pad = 5 * scale;
   const size = { width: p[0] * scale, height: p[1] * scale };
   const margin = [pad, pad, pad, pad * 5];
+  console.log(margin)
+  const a = getRealPixel(5, 'a4')
+  console.log([a, getRealPixel(25, 'a4')])
   return {size, margin}
 }
 export const paperConfigs = {

+ 3 - 3
src/example/constant.ts

@@ -94,8 +94,8 @@ export const iconGroups = [
           {
             icon: "xuepo_o",
             name: "血泊",
-            color: "#FF0000",
-            parse: { fill: "#FF0000", stroke: undefined },
+            color: "#DD2C2C",
+            parse: { fill: "#DD2C2C", stroke: undefined },
           },
           {
             icon: "xueji_o",
@@ -119,5 +119,5 @@ export const aiIconMap = {
   SingleWindow: "cad-chuang",
   BayWindow: "cad-piaochuang",
   FrenchWindow: "cad-luodichuang",
-  Chair: "Desk",
+  Chair: "BalconyChair",
 };

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

@@ -15,7 +15,6 @@
       remote-show-suffix
       :loading="loading"
     >
-      <template #empty> 123123 </template>
       <el-option
         v-for="item in scenes"
         :key="item.m"
@@ -69,6 +68,7 @@ const searchSenects = async (keyword: string) => {
 
 searchSenects("");
 defineExpose({
+  disabled: () => !value.value,
   submit: () => value.value,
 });
 </script>

+ 16 - 8
src/example/env.ts

@@ -39,16 +39,18 @@ export const urlGetQuery = (link: string) => {
   return rParams;
 };
 
-export const params = ref<Record<string, string | undefined>>({});
+export const params = ref<Record<string, any>>({});
 const sParams = new URLSearchParams(location.search);
 [...sParams.entries()].forEach((item) => {
   params.value[item[0]] = item[1];
 });
 const updateParams = () => {
   const rParams = urlGetQuery(location.href);
-  console.log("updateParams", rParams);
-  if (inRevise(rParams, params.value)) {
-    params.value = rParams;
+  params.value = rParams;
+  for (const key in params.value) {
+    if (['tabulationId', 'overviewId'].includes(key)) {
+      params.value[key] = Number(params.value[key])
+    }
   }
 };
 updateParams();
@@ -66,10 +68,16 @@ watch(
         "?" +
         sParams.toString()
     );
-    if (inRevise(nParams, oParams)) {
-      setTimeout(() => {
-        location.reload();
-      }, 1000);
+    const keys =  new Set([...Object.keys(nParams), ...Object.keys(oParams)])
+    for (const key of keys) {
+      if (nParams[key] && oParams[key]) {
+        if (nParams[key] !== oParams[key]) {
+          setTimeout(() => {
+            location.reload();
+          }, 100);
+          return;
+        }
+      }
     }
   },
   { deep: true }

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

@@ -8,6 +8,7 @@ export const tableCoverKey = '__tableCoverKey'
 export const tableCoverScaleKey = '__tableCoverScaleKey'
 export const tableTableKey = '__tableTableKey'
 export const tableTitleKey = '__tableTitleKey'
+export const tableCompassKey = '__tableCompassKey'
 export const tableCoverWidth = 370
 export const tableCoverHeight = 306
 

+ 2 - 1
src/example/fuse/views/overview/header.vue

@@ -150,8 +150,9 @@ const saveHandler = async () => {
   await refreshTabulationData();
   const tabStore = await repTabulationStore(
     tabulationData.value.paperKey,
+    storeData.config.compass.rotation,
     cover,
-    tabulationData.value.isAutoGen ? undefined : tabulationData.value.store
+    tabulationData.value.isAutoGen ? undefined : tabulationData.value.store,
   );
 
   tabStore.config.compass = storeData.config.compass;

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

@@ -36,7 +36,7 @@ const vrScene = ref<Scene>();
 
 const init = async (draw: Draw) => {
   await refreshOverviewData();
-  draw.config.showCompass = false;
+  draw.config.showCompass = import.meta.env.DEV;
   draw.config.showLabelLine = true;
   draw.config.showComponentSize = false;
   draw.config.back = { color: "#f0f2f5", opacity: 1 };

+ 1 - 1
src/example/fuse/views/overview/slide-icons.vue

@@ -16,7 +16,7 @@
             v-for="item in typeChildren.children"
             @click="drawIcon(`./icons/${item.icon}.svg`, item.name, item)"
           >
-            <Icon :name="item.icon" size="32px" />
+            <Icon :name="item.icon" size="32px" :color="item.color" />
             <span>{{ item.name }}</span>
           </div>
         </div>

+ 53 - 5
src/example/fuse/views/tabulation/gen-tab.ts

@@ -6,6 +6,7 @@ import {
 } from "@/example/components/slide/actions";
 import {
   TabCover,
+  tableCompassKey,
   tableCoverHeight,
   tableCoverKey,
   tableCoverScaleKey,
@@ -21,6 +22,8 @@ import { ImageData } from "@/core/components/image";
 import { DrawData } from "@/core/components";
 import { getEmptyStoreData, StoreData } from "@/core/store/store";
 import { defaultLayer } from "@/constant";
+import { IconData } from "@/core/components/icon";
+import { MathUtils } from "three";
 
 export const getRealPixel = (real: number, paperKey: PaperKey) => {
   const realPixelScale = paperConfigs[paperKey].scale;
@@ -48,7 +51,7 @@ export const setCoverPaperScale = (cover: ImageData, paperKey: PaperKey, scale:
   cover.height = (cover.heightRaw! / cover.widthRaw!) * cover.width
 }
 
-export const genTabulationData = async (paperKey: PaperKey, cover?: TabCover | null) => {
+export const genTabulationData = async (paperKey: PaperKey, compass?: number, cover?: TabCover | null) => {
   const { margin, size } = getPaperConfig(
     paperConfigs[paperKey].size,
     paperConfigs[paperKey].scale
@@ -118,6 +121,7 @@ export const genTabulationData = async (paperKey: PaperKey, cover?: TabCover | n
       ...getBaseItem(),
       cornerRadius: 0,
       strokeWidth: 0,
+      disableDelete: true,
       url: cover.url,
       key: tableCoverKey,
       proportion: cover.proportion,
@@ -141,7 +145,6 @@ export const genTabulationData = async (paperKey: PaperKey, cover?: TabCover | n
     coverData.mat[4] = pos.x;
     coverData.mat[5] = pos.y;
 
-    console.log(pos, coverData, size)
     const scale = getCoverPaperScale(coverData, paperKey);
     setCoverPaperScale(coverData, paperKey, Math.round(scale / 10) * 10)
     return coverData;
@@ -188,21 +191,54 @@ export const genTabulationData = async (paperKey: PaperKey, cover?: TabCover | n
     return title;
   };
 
+  const getCompass = () => {
+    const mat = new Transform().rotate(MathUtils.degToRad(compass!))
+    
+    const data: IconData = {
+      ...getBaseItem(),
+      width: getRealPixel(37.3366 / 2.3, paperKey),
+      height: getRealPixel(60 / 2.3, paperKey),
+      url: './icons/compass.svg',
+      fill: "#000000",
+      disableDelete: true,
+      coverOpcatiy: 0,
+      strokeScaleEnabled: false,
+      key: tableCompassKey,
+      mat: mat.m,
+      name: '指南针'
+    };
+    
+    const pos = getFixPosition(
+      {
+        right: getRealPixel(8, paperKey) + margin[1] ,
+        top: getRealPixel(42, paperKey) + margin[2],
+      },
+      data,
+      size
+    );
+    data.mat[4] = pos.x;
+    data.mat[5] = pos.y;
+    return data;
+  }
+
   const data: DrawData = {
     table: [getTable()],
-    text: [getTitle()]
+    text: [getTitle()],
   }
   const image = await getCover()
   if (image) {
     data.image = [image]
     data.text!.push(getCoverScale(image))
   }
+  if (compass !== undefined) {
+    data.icon = [getCompass()]
+  }
 
   return data
 };
 
-export const repTabulationStore = async (paperKey: PaperKey, cover: TabCover, store?: StoreData) => {
-  const repData = await genTabulationData(paperKey, cover)
+export const repTabulationStore = async (paperKey: PaperKey, compass?: number, cover?: TabCover, store?: StoreData) => {
+  const repData = await genTabulationData(paperKey, compass, cover)
   const layer = store?.layers && store?.layers[defaultLayer];
 
   if (!layer) {
@@ -244,6 +280,18 @@ export const repTabulationStore = async (paperKey: PaperKey, cover: TabCover, st
     }
     layer.text = texts
   }
+  if (repData.icon) {
+    const iconData = repData.icon[0]
+    const icons = layer.icon || []
+    const iconNdx = icons.findIndex(item => item.key === iconData.key)
+    if (~iconNdx) {
+      icons[iconNdx] = iconData
+    } else {
+      icons.push(iconData)
+    }
+    layer.icon = icons
+  }
+
   store.layers[defaultLayer] = layer
 
   return store;

+ 54 - 7
src/example/fuse/views/tabulation/index.vue

@@ -19,7 +19,7 @@
 import Header from "./header.vue";
 import Slide from "./slide.vue";
 import Container from "../../../components/container/container.vue";
-import { computed, ref, watch } from "vue";
+import { computed, nextTick, ref, watch } from "vue";
 import { Draw } from "../../../components/container/use-draw";
 import Dialog from "../../../dialog/dialog.vue";
 import {
@@ -27,11 +27,16 @@ import {
   refreshTabulationData,
   tableCoverKey,
   tableCoverScaleKey,
+  tableCompassKey,
 } from "../../store";
 import { ImageData } from "@/core/components/image";
 import { TextData } from "@/core/components/text";
 import { getCoverPaperScale, getRealPixel, setCoverPaperScale } from "./gen-tab";
 import { defaultTableStyle } from "@/core/components/serial";
+import { IconData } from "@/core/components/icon";
+import { Transform } from "konva/lib/Util";
+import { MathUtils } from "three";
+import { useStage } from "@/core/hook/use-global-vars";
 
 const uploadResourse = window.platform.uploadResourse;
 const full = ref(false);
@@ -40,7 +45,7 @@ const draw = ref<Draw>();
 const inited = ref(false);
 const init = async (draw: Draw) => {
   await refreshTabulationData();
-  draw.config.showCompass = true;
+  draw.config.showCompass = false;
   draw.config.showGrid = false;
   draw.config.showLabelLine = false;
   draw.config.showComponentSize = false;
@@ -80,9 +85,14 @@ const coverScale = computed({
 watch(cover, (cover, _, onCleanup) => {
   if (!cover || !draw.value || !cover.widthRaw || !cover.heightRaw || !cover.proportion)
     return;
-  const mountMenus = draw.value.mountMenus;
+  const mountMenus = draw.value.mountFilter;
+  const menusFilter = draw.value.menusFilter;
 
-  mountMenus.setShapeAttachs(cover.id, {
+  menusFilter.setShapeMenusFilter(cover.id, (menus) => {
+    return menus.filter((item) => item.label !== "对齐");
+  });
+  mountMenus.setShapeMenusFilter(cover.id, (des) => ({
+    ...des,
     scale: {
       type: "fixProportion",
       label: "缩放比例",
@@ -93,7 +103,7 @@ watch(cover, (cover, _, onCleanup) => {
       set value(val) {
         coverScale.value = val;
       },
-      props: {},
+      props: { min: 1 },
     },
     showScale: {
       type: "check",
@@ -107,9 +117,10 @@ watch(cover, (cover, _, onCleanup) => {
       },
       props: {},
     },
-  });
+  }));
   onCleanup(() => {
-    mountMenus.setShapeAttachs(cover.id);
+    mountMenus.setShapeMenusFilter(cover.id);
+    menusFilter.setShapeMenusFilter(cover.id);
   });
 });
 
@@ -142,4 +153,40 @@ watch(
   },
   { flush: "post" }
 );
+
+const compass = computed(
+  () => draw.value?.store.items.find((item) => item.key === tableCompassKey) as IconData
+);
+watch(compass, (compass, _, onCleanup) => {
+  if (!compass || !draw.value) return;
+  const mountMenus = draw.value.mountFilter;
+  mountMenus.setShapeMenusFilter(compass.id, (des) => ({
+    ...des,
+    rotate: {
+      type: "num",
+      label: "旋转角度",
+      default: 0,
+      props: {
+        min: 0,
+        max: 360,
+      },
+      "layout-type": "column",
+      sort: 3,
+      get value() {
+        return (new Transform(compass.mat).decompose().rotation + 360) % 360;
+      },
+      set value(val) {
+        const config = new Transform(compass.mat).decompose();
+        compass.mat = new Transform()
+          .translate(config.x, config.y)
+          .scale(config.scaleX, config.scaleY)
+          .rotate(MathUtils.degToRad(val)).m;
+        nextTick(() => {
+          draw.value?.stage!.findOne(`#${compass.id}`)?.fire("bound-change");
+        });
+      },
+    },
+  }));
+  onCleanup(() => mountMenus.setShapeMenusFilter(compass.id));
+});
 </script>

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

@@ -58,7 +58,11 @@ const setPaperAfterHandler = async (paperKey: PaperKey, oldPaperKey?: PaperKey)
   await nextTick();
   if (!isUpdate) return;
 
-  const data = await genTabulationData(paperKey, tabulationData.value.cover);
+  const data = await genTabulationData(
+    paperKey,
+    tabulationData.value.store.config.compass.rotation,
+    tabulationData.value.cover
+  );
 
   draw.history.clear();
   draw.store.setStore({

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

@@ -14,7 +14,7 @@ const scaleResource = (info: AIExposeData, scale: number) => {
     ...item,
     geos: item.geos.map((geo) =>
       geo.map((p) => {
-        return { x: p.x * scale, y: p.y * scale, z: p.z * scale }
+        return { x: p.x * scale, y: p.y * scale, z: p.z && p.z * scale };
       })
     ),
     box: item.box && {
@@ -38,7 +38,7 @@ const scaleResource = (info: AIExposeData, scale: number) => {
           position: {
             x: item.position.x * scale,
             y: item.position.y * scale,
-            z: item.position.z * scale,
+            z: item.position.z && item.position.z * scale,
           },
           size: item.size
             ? {
@@ -52,7 +52,6 @@ const scaleResource = (info: AIExposeData, scale: number) => {
         if (!floor || !floor.box) return;
         const w = floor.box.bound.x_max - floor.box.bound.x_min;
         const h = floor.box.bound.y_max - floor.box.bound.y_min;
-
         return {
           ...item,
           position: {
@@ -70,7 +69,6 @@ const scaleResource = (info: AIExposeData, scale: number) => {
       }
     })
     .filter((item) => !!item);
-
   return {
     ...info,
     taggings,
@@ -115,8 +113,9 @@ const getResourceLayers = (data: AIExposeData) => {
             //     item.position.z <= box.bound.z_max
             // );
             return (
-              item.position.z > box.bound.z_min &&
-              item.position.z <= box.bound.z_max
+              item.position.z === undefined ||
+              (item.position.z > box.bound.z_min &&
+                item.position.z <= box.bound.z_max)
             );
           })
           .map((item) => ({
@@ -206,10 +205,9 @@ const drawLayerResource = (
   images.push(
     ...layerResource.taggings.map((item, ndx) => {
       bound.update(item.position);
-      const tf = new Transform()
-        .translate(item.position.x, item.position.y)
+      const tf = new Transform().translate(item.position.x, item.position.y);
       if (item.rotate) {
-        tf.rotate(item.rotate)
+        tf.rotate(item.rotate);
       }
       return {
         ...getBaseItem(),
@@ -262,10 +260,11 @@ export const drawPlatformResource = (data: AIExposeData, draw: Draw) => {
       // draw.store.setCurrentLayer(layer.name);
       layerBounds.push(drawLayerResource(layer, draw)!);
     }
+    console.log(data.compass)
     if (typeof data.compass === "number") {
       draw.store.setConfig({
         compass: {
-          rotation: MathUtils.radToDeg(data.compass),
+          rotation: data.compass,
           url: draw.store.config.compass.url,
         },
       });

+ 30 - 22
src/example/platform/platform-resource.ts

@@ -42,7 +42,7 @@ export type Taging = {
   url: string;
   position: Pos & { z: number };
   size?: Size;
-  rotate?: number
+  rotate?: number;
   name?: string;
   pixel?: boolean;
   subgroup?: string;
@@ -56,7 +56,7 @@ export const SceneTypeNames = {
 
 export const getSceneApi = async (type: string, url: string) => {
   if (url[0] === "/") {
-    url = url.substring(1, url.length );
+    url = url.substring(1, url.length);
   }
   let origin = window.platform.resourceURLS[type];
   if (origin[origin.length - 1] !== "/") {
@@ -205,13 +205,15 @@ export const taggingGets = {
         .then((res) => res.json())
         .catch(() => []);
 
-
       const reqs = signages.map((signage: any) => {
         if (!validNum(signage.pos[0]) || !validNum(signage.pos[2])) return;
         const getIcon =
           signage.icon.indexOf("style-") === 0
-            ? getSceneApi('ossRoot', `/sdk/images/billboard/${signage.icon}.png`)
-            : getSceneApi('oss', `${prev}/user/${signage.icon}`);
+            ? getSceneApi(
+                "ossRoot",
+                `/sdk/images/billboard/${signage.icon}.png`
+              )
+            : getSceneApi("oss", `${prev}/user/${signage.icon}`);
         return getIcon
           .then((url) => {
             tags.push({
@@ -219,19 +221,22 @@ export const taggingGets = {
               position: {
                 x: signage.pos[0],
                 y: signage.pos[2],
-                z: signage.pos[1] < 0 ? (Math.ceil(signage.pos[1]) * 10) / 10 : (Math.floor(signage.pos[1]) * 10) / 10,
+                z:
+                  signage.pos[1] < 0
+                    ? (Math.ceil(signage.pos[1]) * 10) / 10
+                    : (Math.floor(signage.pos[1]) * 10) / 10,
               },
               rotate: 0,
               size: {
                 width: signage.width * (signage.scaleRatio / 100),
-                height: signage.height* (signage.scaleRatio / 100)
-              }
+                height: signage.height * (signage.scaleRatio / 100),
+              },
             });
           })
           .catch(() => {});
       });
 
-      await Promise.all(reqs)
+      await Promise.all(reqs);
     }
 
     await getSceneApi(
@@ -241,7 +246,6 @@ export const taggingGets = {
       .then((url) => fetch(url))
       .then((res) => res.json())
       .then((datas) => {
-        console.error(datas)
         for (const data of datas) {
           const reg = data.imagePath.match(/floor_(\d)\.png/);
           const subgroup = reg ? Number(reg[1]) : undefined;
@@ -249,7 +253,7 @@ export const taggingGets = {
             const pos = {
               x: (shape.bbox[0] + shape.bbox[2]) / 2 / data.imageWidth,
               y: (shape.bbox[1] + shape.bbox[3]) / 2 / data.imageHeight,
-              z: 0,
+              z: undefined,
             };
             const size = {
               width: (shape.bbox[2] - shape.bbox[0]) / data.imageWidth,
@@ -260,7 +264,7 @@ export const taggingGets = {
                 ? (aiIconMap as any)[shape.category]
                 : shape.category;
             let name = "";
-            console.log(icon)
+            console.log(shape.category);
             for (const group of iconGroups) {
               for (const itemGroup of group.children) {
                 for (const item of itemGroup.children) {
@@ -270,22 +274,26 @@ export const taggingGets = {
                 }
               }
             }
-            console.error(name, pos)
-            if (!name) return;
-            tags.push({
-              position: pos,
-              url: `./icons/${icon ? icon : "circle"}.svg`,
-              name,
-              pixel: true,
-              size,
-              subgroup,
-            } as any);
+            if (name) {
+              tags.push({
+                position: pos,
+                url: `./icons/${icon ? icon : "circle"}.svg`,
+                name,
+                pixel: true,
+                size,
+                subgroup,
+              } as any);
+            } else {
+              console.error("找不到ai家具", name, pos);
+            }
           }
         }
       })
       .catch((e) => {
         console.error(e);
       });
+
+    console.log(tags);
     return tags;
   },
 };

+ 9 - 1
src/utils/dom.ts

@@ -56,12 +56,20 @@ $fileInput.style = `
   display: none;
 `;
 export const selectFile = (multiple = false, accept = "") =>
-  new Promise<File[]>((resolve) => {
+  new Promise<File[]>((resolve, reject) => {
     $fileInput.multiple = multiple;
     $fileInput.accept = accept;
     $fileInput.onchange = () => {
       $fileInput.accept = "";
       $fileInput.multiple = false;
+      const accepts = accept.split(',').map(s => s.trim())
+      const files = [...$fileInput.files!]
+      for (const f of files) {
+        if (!accepts.some(ac => f.name.endsWith(ac))) {
+          reject(new Error(`仅支持${accept}格式文件`))
+          return;
+        }
+      }
       resolve(Array.from($fileInput.files!));
       $fileInput.value = "";
     };