Prechádzať zdrojové kódy

feat: 制作业务相关场景代码

bill 3 mesiacov pred
rodič
commit
bed53259fa
49 zmenil súbory, kde vykonal 739 pridanie a 222 odobranie
  1. 1 1
      public/icons/compass.svg
  2. 29 1
      public/icons/edit_compass.svg
  3. 14 16
      src/core/components/arrow/arrow.vue
  4. 17 11
      src/core/components/arrow/index.ts
  5. 30 5
      src/core/components/arrow/temp-arrow.vue
  6. 1 1
      src/core/components/circle/temp-circle.vue
  7. 41 2
      src/core/components/icon/icon.vue
  8. 3 2
      src/core/components/icon/temp-icon.vue
  9. 38 6
      src/core/components/image/image.vue
  10. 6 0
      src/core/components/image/index.ts
  11. 1 1
      src/core/components/image/temp-image.vue
  12. 5 1
      src/core/components/line/line.vue
  13. 1 1
      src/core/components/rectangle/temp-rectangle.vue
  14. 25 7
      src/core/components/serial/serial-group.vue
  15. 1 1
      src/core/components/serial/temp-serial.vue
  16. 3 1
      src/core/components/share/edit-line.vue
  17. 21 16
      src/core/components/share/edit-point.vue
  18. 2 1
      src/core/components/table/index.ts
  19. 1 1
      src/core/components/table/temp-table.vue
  20. 1 1
      src/core/components/text/temp-text.vue
  21. 4 4
      src/core/components/text/text.vue
  22. 1 1
      src/core/components/triangle/temp-triangle.vue
  23. 3 0
      src/core/components/util.ts
  24. 23 3
      src/core/helper/compass.vue
  25. 10 2
      src/core/hook/use-draw.ts
  26. 43 19
      src/core/hook/use-expose.ts
  27. 40 1
      src/core/hook/use-global-vars.ts
  28. 5 1
      src/core/hook/use-interactive.ts
  29. 3 1
      src/core/hook/use-mouse-status.ts
  30. 13 11
      src/core/hook/use-status.ts
  31. 30 15
      src/core/hook/use-transformer.ts
  32. 6 1
      src/core/hook/use-viewer.ts
  33. 15 7
      src/core/html-mount/propertys/components/checkbox.vue
  34. 29 0
      src/core/html-mount/propertys/components/fix-proportion.vue
  35. 3 0
      src/core/html-mount/propertys/index.ts
  36. 24 13
      src/core/html-mount/propertys/mount.vue
  37. 1 1
      src/core/store/store.ts
  38. 3 0
      src/example/components/header/actions.ts
  39. 9 11
      src/example/components/slide/actions.ts
  40. 7 3
      src/example/components/slide/slide.vue
  41. 35 20
      src/example/fuse/req.ts
  42. 4 1
      src/example/fuse/store.ts
  43. 23 8
      src/example/fuse/views/overview/header.vue
  44. 66 16
      src/example/fuse/views/tabulation/gen.ts
  45. 3 1
      src/example/fuse/views/tabulation/header.vue
  46. 87 2
      src/example/fuse/views/tabulation/index.vue
  47. 4 4
      src/example/platform/platform-draw.ts
  48. 1 0
      src/example/styles/global.scss
  49. 3 0
      需求

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 1
public/icons/compass.svg


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 29 - 1
public/icons/edit_compass.svg


+ 14 - 16
src/core/components/arrow/arrow.vue

@@ -28,8 +28,6 @@ import { Line } from "konva/lib/shapes/Line";
 import { Pos } from "@/utils/math.ts";
 import { Pos } from "@/utils/math.ts";
 import { Group } from "konva/lib/Group";
 import { Group } from "konva/lib/Group";
 import { flatPositions } from "@/utils/shared.ts";
 import { flatPositions } from "@/utils/shared.ts";
-import { useInteractiveDrawShapeAPI } from "@/core/hook/use-draw.ts";
-import { useStore } from "@/core/store/index.ts";
 import { themeColor } from "@/constant/help-style.ts";
 import { themeColor } from "@/constant/help-style.ts";
 
 
 const props = defineProps<{ data: ArrowData }>();
 const props = defineProps<{ data: ArrowData }>();
@@ -89,18 +87,18 @@ const { shape, tData, operateMenus, describes, data } = useComponentStatus<
   ],
   ],
 });
 });
 
 
-const draw = useInteractiveDrawShapeAPI();
-const store = useStore();
-operateMenus.push({
-  label: "钢笔编辑",
-  handler() {
-    draw.enterDrawShape("arrow", {
-      ...props.data,
-      getMessages: () => {
-        const line = store.getItemById(props.data.id) as ArrowData;
-        return line ? line.points : [];
-      },
-    });
-  },
-});
+// const draw = useInteractiveDrawShapeAPI();
+// const store = useStore();
+// operateMenus.push({
+//   label: "钢笔编辑",
+//   handler() {
+//     draw.enterDrawShape("arrow", {
+//       ...props.data,
+//       getMessages: () => {
+//         const line = store.getItemById(props.data.id) as ArrowData;
+//         return line ? line.points : [];
+//       },
+//     });
+//   },
+// });
 </script>
 </script>

+ 17 - 11
src/core/components/arrow/index.ts

@@ -24,7 +24,7 @@ export const defaultStyle = {
 
 
 // export const fill
 // export const fill
 
 
-export const addMode = "dots";
+export const addMode = "area";
 
 
 export const getMouseStyle = (data: ArrowData) => {
 export const getMouseStyle = (data: ArrowData) => {
   const strokeStatus = getMouseColors(data.fill || defaultStyle.fill);
   const strokeStatus = getMouseColors(data.fill || defaultStyle.fill);
@@ -82,16 +82,22 @@ export const interactiveToData: InteractiveTo<"arrow"> = ({
 };
 };
 
 
 export const interactiveFixData: InteractiveFix<"arrow"> = ({ data, info }) => {
 export const interactiveFixData: InteractiveFix<"arrow"> = ({ data, info }) => {
-  // data.points = [...info.consumed, info.cur!];
-    const nv = [...info.consumed, info.cur!];
-    data.points.length = nv.length
-    for (let i = 0; i < nv.length; i++) {
-      if (inRevise(data.points[i], nv[i])) {
-        data.points[i] = nv[i]
-      }
-    }
-  
-  return data;
+  // const nv = [...info.consumed, info.cur!];
+  // data.points.length = nv.length
+  // for (let i = 0; i < nv.length; i++) {
+  //   if (inRevise(data.points[i], nv[i])) {
+  //     data.points[i] = nv[i]
+  //   }
+  // }
+  // return data;
+
+
+  if (info.cur) {
+    const area = info.cur!;
+    data.points = area
+    data.attitude = [1, 0, 0, 1, 0, 0];
+  }
+  return data
 };
 };
 
 
 
 

+ 30 - 5
src/core/components/arrow/temp-arrow.vue

@@ -18,17 +18,37 @@
       }"
       }"
     />
     />
 
 
-    <EditPolygon
+    <!-- <EditPolygon
       :data="{ ...data, stroke: data.fill, strokeWidth: data.strokeWidth + 5 }"
       :data="{ ...data, stroke: data.fill, strokeWidth: data.strokeWidth + 5 }"
       :shape="shape"
       :shape="shape"
-      :addMode="addMode"
+      :addMode="false"
       :canEdit="canEdit"
       :canEdit="canEdit"
       @update:position="(data) => emit('update:position', data)"
       @update:position="(data) => emit('update:position', data)"
       @update="emit('update')"
       @update="emit('update')"
       @deletePoint="(ndx) => emit('deletePoint', ndx)"
       @deletePoint="(ndx) => emit('deletePoint', ndx)"
-        @addPoint="(data) => emit('addPoint', data)"
+      @addPoint="(data) => emit('addPoint', data)"
       v-if="shape"
       v-if="shape"
-    />
+    /> -->
+
+    <v-group>
+      <template
+        v-if="(status.hover || status.active || addMode) && !operMode.mulSelection"
+      >
+        <Point
+          v-for="(_, ndx) in data.points"
+          :size="data.strokeWidth + 6"
+          :points="data.points"
+          :ndx="ndx"
+          :closed="false"
+          :id="data.id"
+          :disable="addMode"
+          :color="data.fill"
+          @update:position="(p) => emit('update:position', { ndx, val: p })"
+          @dragend="emit('update')"
+          :not-delete="true"
+        />
+      </template>
+    </v-group>
 
 
     <SizeLine
     <SizeLine
       v-if="config.showComponentSize"
       v-if="config.showComponentSize"
@@ -41,7 +61,8 @@
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import SizeLine from "../share/size-line.vue";
 import SizeLine from "../share/size-line.vue";
-import EditPolygon from "../share/edit-polygon.vue";
+// import EditPolygon from "../share/edit-polygon.vue";
+import Point from "../share/edit-point.vue";
 import { ArrowData, defaultStyle, PointerPosition } from "./index.ts";
 import { ArrowData, defaultStyle, PointerPosition } from "./index.ts";
 import { DC } from "@/deconstruction.js";
 import { DC } from "@/deconstruction.js";
 import { computed, ref, watchEffect } from "vue";
 import { computed, ref, watchEffect } from "vue";
@@ -51,6 +72,8 @@ import { Pos } from "@/utils/math.ts";
 import { LineConfig } from "konva/lib/shapes/Line";
 import { LineConfig } from "konva/lib/shapes/Line";
 import { Group } from "konva/lib/Group";
 import { Group } from "konva/lib/Group";
 import { useConfig } from "@/core/hook/use-config.ts";
 import { useConfig } from "@/core/hook/use-config.ts";
+import { useMouseShapeStatus } from "@/core/hook/use-mouse-status.ts";
+import { useOperMode } from "@/core/hook/use-status.ts";
 
 
 const props = defineProps<{ data: ArrowData; canEdit?: boolean; addMode?: boolean }>();
 const props = defineProps<{ data: ArrowData; canEdit?: boolean; addMode?: boolean }>();
 const emit = defineEmits<{
 const emit = defineEmits<{
@@ -72,6 +95,8 @@ const hitFunc: LineConfig["hitFunc"] = (con, shape) => {
   con.closePath();
   con.closePath();
   con.fillStrokeShape(shape);
   con.fillStrokeShape(shape);
 };
 };
+const status = useMouseShapeStatus(computed(() => shape.value));
+const operMode = useOperMode();
 
 
 watchEffect(
 watchEffect(
   (onCleanup) => {
   (onCleanup) => {

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

@@ -12,7 +12,7 @@
     <ShareText
     <ShareText
       :config="{ ...textConfig, ...textBound }"
       :config="{ ...textConfig, ...textBound }"
       :parent-id="data.id"
       :parent-id="data.id"
-      :editer="editer"
+      :editer="editer && !data.disableEditText"
       @update-text="(val) => emit('updateContent', val)"
       @update-text="(val) => emit('updateContent', val)"
       @update:is-edit="(val) => emit('update:isEdit', val)"
       @update:is-edit="(val) => emit('update:isEdit', val)"
     />
     />

+ 41 - 2
src/core/components/icon/icon.vue

@@ -14,11 +14,19 @@
 import TempIcon from "./temp-icon.vue";
 import TempIcon from "./temp-icon.vue";
 import { IconData, getMouseStyle, defaultStyle, matResponse } from "./index.ts";
 import { IconData, getMouseStyle, defaultStyle, matResponse } from "./index.ts";
 import { useComponentStatus } from "@/core/hook/use-component.ts";
 import { useComponentStatus } from "@/core/hook/use-component.ts";
-import { PropertyUpdate, Operate, mergeDescribes } from "../../html-mount/propertys/index.ts";
+import {
+  PropertyUpdate,
+  Operate,
+  mergeDescribes,
+} from "../../html-mount/propertys/index.ts";
 import { Transform } from "konva/lib/Util";
 import { Transform } from "konva/lib/Util";
 import originDescribes from "../../html-mount/propertys/describes.json";
 import originDescribes from "../../html-mount/propertys/describes.json";
 import { ref } from "vue";
 import { ref } from "vue";
 import { MathUtils } from "three";
 import { MathUtils } from "three";
+import { useCustomTransformer } from "@/core/hook/use-transformer.ts";
+import { Group } from "konva/lib/Group";
+import { Rect } from "konva/lib/shapes/Rect";
+import { setShapeTransform } from "@/utils/shape.ts";
 
 
 const props = defineProps<{ data: IconData }>();
 const props = defineProps<{ data: IconData }>();
 const emit = defineEmits<{
 const emit = defineEmits<{
@@ -31,7 +39,38 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus({
   emit,
   emit,
   props,
   props,
   getMouseStyle,
   getMouseStyle,
-  transformType: props.data.fixScreen ? undefined : "mat",
+  transformType: props.data.fixScreen ? undefined : "custom",
+  customTransform(callback, shape, data) {
+    let prevInvMat: Transform;
+    return useCustomTransformer(shape, data, {
+      beforeHandler(data, mat) {
+        return { ...data, mat: mat.copy().multiply(prevInvMat).m };
+      },
+      handler(data, mat) {
+        data.mat = mat.copy().multiply(prevInvMat).m;
+        // return true
+      },
+      getRepShape(shape) {
+        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);
+
+        return {
+          shape: rect,
+        } as any;
+      },
+      callback,
+      openSnap: false,
+    });
+  },
   defaultStyle,
   defaultStyle,
   alignment(data, mat) {
   alignment(data, mat) {
     return matResponse({ data, mat, increment: true });
     return matResponse({ data, mat, increment: true });

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

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

+ 38 - 6
src/core/components/image/image.vue

@@ -17,6 +17,10 @@ import { useComponentStatus } from "@/core/hook/use-component.ts";
 import { PropertyUpdate, Operate } from "../../html-mount/propertys/index.ts";
 import { PropertyUpdate, Operate } from "../../html-mount/propertys/index.ts";
 import { Group } from "konva/lib/Group";
 import { Group } from "konva/lib/Group";
 import { Rect } from "konva/lib/shapes/Rect";
 import { Rect } from "konva/lib/shapes/Rect";
+import { useCustomTransformer } from "@/core/hook/use-transformer.ts";
+import { setShapeTransform } from "@/utils/shape.ts";
+import { Image } from "konva/lib/shapes/Image";
+import { Transform } from "konva/lib/Util";
 
 
 const props = defineProps<{ data: ImageData }>();
 const props = defineProps<{ data: ImageData }>();
 const emit = defineEmits<{
 const emit = defineEmits<{
@@ -32,13 +36,41 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus<
   emit,
   emit,
   props,
   props,
   getMouseStyle,
   getMouseStyle,
-  transformType: "mat",
-  defaultStyle,
-  getRepShape() {
-    console.log("rect", shape.value?.getNode());
-    const rect = new Rect();
-    return rect;
+  transformType: "custom",
+  customTransform(callback, shape, data) {
+    let prevInvMat: Transform;
+    return useCustomTransformer(shape, data, {
+      beforeHandler(data, mat) {
+        return { ...data, mat: mat.copy().multiply(prevInvMat).m };
+      },
+      handler(data, mat) {
+        data.mat = mat.copy().multiply(prevInvMat).m;
+        // return true
+      },
+      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);
+
+        return {
+          shape: rect,
+        } as any;
+      },
+      callback,
+      openSnap: false,
+    });
   },
   },
+  // transformType: "mat",
+  defaultStyle,
   alignment(data, mat) {
   alignment(data, mat) {
     return matResponse({ data, mat, increment: true });
     return matResponse({ data, mat, increment: true });
   },
   },

+ 6 - 0
src/core/components/image/index.ts

@@ -54,6 +54,12 @@ export type ImageData = Partial<typeof defaultStyle> &
   BaseItem & Size & {
   BaseItem & Size & {
     fill?: string;
     fill?: string;
     stroke?: string;
     stroke?: string;
+    widthRaw?: number;
+    proportion?: {
+      scale: number;
+      unit: string;
+    };
+    heightRaw?: number
     strokeWidth?: number;
     strokeWidth?: number;
     cornerRadius: number;
     cornerRadius: number;
     url: string;
     url: string;

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

@@ -8,6 +8,7 @@
         zIndex: undefined,
         zIndex: undefined,
       }"
       }"
       v-if="image"
       v-if="image"
+      name="repShape"
     />
     />
   </v-group>
   </v-group>
 </template>
 </template>
@@ -73,5 +74,4 @@ const groupConfig = computed(() => {
     ...new Transform(data.value.mat).decompose(),
     ...new Transform(data.value.mat).decompose(),
   };
   };
 });
 });
-
 </script>
 </script>

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

@@ -64,7 +64,11 @@ const updatePosition = ({ ndx, val }: { ndx: number; val: Pos }) => {
 
 
 const deletePoint = (ndx: number) => {
 const deletePoint = (ndx: number) => {
   data.value.points.splice(ndx, 1);
   data.value.points.splice(ndx, 1);
-  shape.value?.getNode().fire("bound-change");
+  if (data.value.points.length <= 1) {
+    emit("delShape");
+  } else {
+    shape.value?.getNode().fire("bound-change");
+  }
 };
 };
 
 
 const addPoint = ({ ndx, val }: { ndx: number; val: Pos }) => {
 const addPoint = ({ ndx, val }: { ndx: number; val: Pos }) => {

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

@@ -14,7 +14,7 @@
     <ShareText
     <ShareText
       :config="{ ...textConfig, ...textBound }"
       :config="{ ...textConfig, ...textBound }"
       :parent-id="data.id"
       :parent-id="data.id"
-      :editer="editer"
+      :editer="editer&& !data.disableEditText"
       @update-text="(val) => emit('updateContent', val)"
       @update-text="(val) => emit('updateContent', val)"
       @update:is-edit="(val) => emit('update:isEdit', val)"
       @update:is-edit="(val) => emit('update:isEdit', val)"
     />
     />

+ 25 - 7
src/core/components/serial/serial-group.vue

@@ -21,6 +21,11 @@ import { useHistory } from "@/core/hook/use-history";
 import { ShapeType } from "..";
 import { ShapeType } from "..";
 import { TableData, interactiveToData as tableInteractiveToData } from "../table";
 import { TableData, interactiveToData as tableInteractiveToData } from "../table";
 import { getCurrentNdx, SerialData } from ".";
 import { getCurrentNdx, SerialData } from ".";
+import {
+  useGetViewBoxPositionPixel,
+  useViewerInvertTransform,
+} from "@/core/hook/use-viewer";
+import { useConfig } from "@/core/hook/use-config";
 
 
 defineProps<{ type?: ShapeType }>();
 defineProps<{ type?: ShapeType }>();
 const store = useStore();
 const store = useStore();
@@ -58,15 +63,28 @@ const jTable = computed(() =>
   store.getTypeItems("table").find((item) => item.key === joinKey)
   store.getTypeItems("table").find((item) => item.key === joinKey)
 );
 );
 
 
+const config = useConfig();
+const margin = computed(() => {
+  let margin = config.margin || 0;
+  if (!Array.isArray(margin)) {
+    margin = [margin, margin, margin, margin];
+  }
+  return margin;
+});
+const invMat = useViewerInvertTransform();
+const getPosition = useGetViewBoxPositionPixel();
 const addTable = () => {
 const addTable = () => {
-  const last = data.value[data.value.length - 1];
-  const pos = {
-    x: last.mat[4] + last.radiusX + 20,
-    y: last.mat[5] + last.radiusX + 20,
-  };
+  const w = 304;
+  const h = 32;
+
+  let pos = getPosition(
+    { right: 44 + margin.value[1], top: 175 + margin.value[0] },
+    { width: w, height: h }
+  );
+  pos = invMat.value.point(pos);
   const end = {
   const end = {
-    x: pos.x + 107 * 2,
-    y: pos.y + 32,
+    x: pos.x + w,
+    y: pos.y + h,
   };
   };
 
 
   return tableInteractiveToData({
   return tableInteractiveToData({

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

@@ -12,7 +12,7 @@
     <ShareText
     <ShareText
       :config="{ ...textConfig, ...textBound }"
       :config="{ ...textConfig, ...textBound }"
       :parent-id="data.id"
       :parent-id="data.id"
-      :editer="editer"
+      :editer="editer && !data.disableEditText"
       @update-text="(val) => emit('updateContent', val)"
       @update-text="(val) => emit('updateContent', val)"
       @update:is-edit="(val) => emit('update:isEdit', val)"
       @update:is-edit="(val) => emit('update:isEdit', val)"
     />
     />

+ 3 - 1
src/core/components/share/edit-line.vue

@@ -138,7 +138,9 @@ const pointStyle = computed(() => {
   const size = props.data.strokeWidth + 6 || 5;
   const size = props.data.strokeWidth + 6 || 5;
   return {
   return {
     radius: size / 2,
     radius: size / 2,
-    fill: color.pub,
+    fill: "#fff",
+    strokeWidth: size / 4,
+    stroke: color.pub,
   };
   };
 });
 });
 </script>
 </script>

+ 21 - 16
src/core/components/share/edit-point.vue

@@ -28,6 +28,7 @@ const props = defineProps<{
   size?: number;
   size?: number;
   disable?: boolean;
   disable?: boolean;
   closed?: boolean;
   closed?: boolean;
+  notDelete?: boolean;
   getSelfSnapInfos?: (point: Pos) => ComponentSnapInfo[];
   getSelfSnapInfos?: (point: Pos) => ComponentSnapInfo[];
 }>();
 }>();
 const emit = defineEmits<{
 const emit = defineEmits<{
@@ -44,7 +45,7 @@ const style = computed(() => {
   const size = props.size || 5;
   const size = props.size || 5;
   return {
   return {
     radius: size / 2,
     radius: size / 2,
-    fill: "#fff",
+    fill: dragIng.value ? "#fff" : color.pub,
     stroke: color.pub,
     stroke: color.pub,
     strokeWidth: size / 4,
     strokeWidth: size / 4,
     opacity: props.disable ? 0.5 : 1,
     opacity: props.disable ? 0.5 : 1,
@@ -90,23 +91,25 @@ const snap = useSnap(refSnapInfos);
 const circle = ref<DC<Circle>>();
 const circle = ref<DC<Circle>>();
 const offset = useShapeDrag(circle);
 const offset = useShapeDrag(circle);
 const hResult = useShapeIsHover(circle);
 const hResult = useShapeIsHover(circle);
-const isHover = hResult[0];
 const cursor = useCursor();
 const cursor = useCursor();
-watch(
-  isHover,
-  (hover, _, onCleanup) => {
-    if (hover) {
-      onCleanup(cursor.push("/icons/m_reduce.png"));
-    }
-  },
-  { immediate: true }
-);
-
-useShapeClick(circle, () => {
-  emit("delete");
-  isHover.value = false;
-});
+if (!props.notDelete) {
+  const isHover = hResult[0];
+  watch(
+    isHover,
+    (hover, _, onCleanup) => {
+      if (hover) {
+        onCleanup(cursor.push("/icons/m_reduce.png"));
+      }
+    },
+    { immediate: true }
+  );
 
 
+  useShapeClick(circle, () => {
+    emit("delete");
+    isHover.value = false;
+  });
+}
+const dragIng = ref(false);
 let init: Pos;
 let init: Pos;
 watch(offset, (offset, oldOffsert) => {
 watch(offset, (offset, oldOffsert) => {
   snap.clear();
   snap.clear();
@@ -115,6 +118,7 @@ watch(offset, (offset, oldOffsert) => {
     startHandler();
     startHandler();
     emit("dragstart");
     emit("dragstart");
     cursor.push("/icons/m_move.png");
     cursor.push("/icons/m_move.png");
+    dragIng.value = true;
   }
   }
   if (offset) {
   if (offset) {
     const point = {
     const point = {
@@ -130,6 +134,7 @@ watch(offset, (offset, oldOffsert) => {
     emit("dragend");
     emit("dragend");
     cursor.pop();
     cursor.pop();
     clearInfos();
     clearInfos();
+    dragIng.value = false;
   }
   }
 });
 });
 
 

+ 2 - 1
src/core/components/table/index.ts

@@ -202,6 +202,7 @@ export const matResponse = (
             col.width = minSize.w
             col.width = minSize.w
             if (rndx === 0) {
             if (rndx === 0) {
               minwNdxs.push(cndx)
               minwNdxs.push(cndx)
+              console.log('===>', col.width)
               w += col.width
               w += col.width
             }
             }
           }
           }
@@ -223,7 +224,7 @@ export const matResponse = (
       return updateColSize()
       return updateColSize()
     }
     }
     const needUpdateH = curMinhNdxs.length !== data.content.length
     const needUpdateH = curMinhNdxs.length !== data.content.length
-    const needUpdateW = curMinhNdxs.length !== data.content.length
+    const needUpdateW = curMinwNdxs.length !== data.content[0].length
     if (!needUpdateH && !needUpdateW) return;
     if (!needUpdateH && !needUpdateW) return;
 
 
     data.content.forEach((row, rndx) => {
     data.content.forEach((row, rndx) => {

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

@@ -30,7 +30,7 @@
             fill: col.fontColor,
             fill: col.fontColor,
           }"
           }"
           :parent-id="data.id"
           :parent-id="data.id"
-          :editer="editer && !col.readonly"
+          :editer="editer && !col.readonly && !data.disableEditText"
           @update-text="(val) => emit('updateContent', { rowNdx, colNdx, val })"
           @update-text="(val) => emit('updateContent', { rowNdx, colNdx, val })"
           @update:is-edit="(val) => emit('update:isEdit', { rowNdx, colNdx, val })"
           @update:is-edit="(val) => emit('update:isEdit', { rowNdx, colNdx, val })"
         />
         />

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

@@ -11,7 +11,7 @@
     }"
     }"
     :parent-id="data.id"
     :parent-id="data.id"
     :ref="(r: any) => shape = r?.shape"
     :ref="(r: any) => shape = r?.shape"
-    :editer="editer"
+    :editer="editer && !data.disableEditText"
     @update-text="(v) => emit('updateContent', v)"
     @update-text="(v) => emit('updateContent', v)"
   />
   />
 </template>
 </template>

+ 4 - 4
src/core/components/text/text.vue

@@ -91,10 +91,10 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus<
   },
   },
   propertys: [
   propertys: [
     "fill",
     "fill",
-    "stroke",
-    "strokeWidth",
-    "dash",
-    "opacity",
+    // "stroke",
+    // "strokeWidth",
+    // "dash",
+    // "opacity",
     "fontSize",
     "fontSize",
     "align",
     "align",
     "fontStyle",
     "fontStyle",

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

@@ -13,7 +13,7 @@
     <ShareText
     <ShareText
       :config="{ ...textConfig, ...textBound }"
       :config="{ ...textConfig, ...textBound }"
       :parent-id="data.id"
       :parent-id="data.id"
-      :editer="editer"
+      :editer="editer && !data.disableEditText"
       @update-text="(val) => emit('updateContent', val)"
       @update-text="(val) => emit('updateContent', val)"
       @update:is-edit="(val) => emit('update:isEdit', val)"
       @update:is-edit="(val) => emit('update:isEdit', val)"
     />
     />

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

@@ -8,6 +8,9 @@ export type BaseItem = {
   id: string;
   id: string;
   createTime: number;
   createTime: number;
   zIndex: number;
   zIndex: number;
+  disableTransformer?: boolean
+  disableEditText?: boolean
+  disableDelete?: boolean
   lock: boolean,
   lock: boolean,
   opacity: number
   opacity: number
   key?: string
   key?: string

+ 23 - 3
src/core/helper/compass.vue

@@ -27,21 +27,41 @@ import {
   useViewerTransformConfig,
   useViewerTransformConfig,
 } from "../hook/use-viewer.ts";
 } from "../hook/use-viewer.ts";
 import { useStore } from "../store/index.ts";
 import { useStore } from "../store/index.ts";
+import { getSvgContent, parseSvgContent } from "@/utils/resource.ts";
 
 
 const config = useConfig();
 const config = useConfig();
 const store = useStore();
 const store = useStore();
 
 
+const maxWidth = 60;
 const data = ref({
 const data = ref({
   coverOpcatiy: 0,
   coverOpcatiy: 0,
   strokeScaleEnabled: false,
   strokeScaleEnabled: false,
-  width: 80,
-  height: 80,
+  width: maxWidth,
+  fill: "#000000",
+  height: maxWidth,
+  disableDelete: true,
   rotation: 0,
   rotation: 0,
   url: store.config.compass.url,
   url: store.config.compass.url,
 });
 });
 
 
 watch(
 watch(
   () => store.config.compass.url,
   () => store.config.compass.url,
+  async (url) => {
+    const svgContent = await getSvgContent(url);
+    const svg = parseSvgContent(svgContent);
+    let height = (svg.height / svg.width) * maxWidth;
+    if (height <= maxWidth) {
+      data.value.height = height;
+    } else {
+      data.value.height = maxWidth;
+      data.value.width = (svg.width / svg.height) * maxWidth;
+    }
+  },
+  { immediate: true }
+);
+
+watch(
+  () => store.config.compass.url,
   (url) => {
   (url) => {
     data.value.url = url || "icons/edit_compass.svg";
     data.value.url = url || "icons/edit_compass.svg";
   }
   }
@@ -92,7 +112,7 @@ const margin = computed(() => {
 const mat = computed(() => {
 const mat = computed(() => {
   const tf = new Transform();
   const tf = new Transform();
   const pos = getPixel(
   const pos = getPixel(
-    { right: 20 + margin.value[1], top: 20 + margin.value[0] },
+    { right: 40 + margin.value[1], top: 80 + margin.value[0] },
     data.value
     data.value
   );
   );
   tf.translate(pos.x, pos.y);
   tf.translate(pos.x, pos.y);

+ 10 - 2
src/core/hook/use-draw.ts

@@ -55,20 +55,25 @@ export const useInteractiveDrawShapeAPI = installGlobalVar(() => {
   const conversion = useConversionPosition(true);
   const conversion = useConversionPosition(true);
   const currentZIndex = useCurrentZIndex();
   const currentZIndex = useCurrentZIndex();
   const store = useStore();
   const store = useStore();
+  let addCount = 0
 
 
   let isEnter = false;
   let isEnter = false;
+  let modePop: (() => void) | undefined = void 0
   const enter = () => {
   const enter = () => {
     if (!isEnter) {
     if (!isEnter) {
       isEnter = true;
       isEnter = true;
-      mode.push(Mode.draw);
+      addCount = 0
+      modePop = mode.push(Mode.draw);
     }
     }
   };
   };
   const leave = () => {
   const leave = () => {
     if (isEnter) {
     if (isEnter) {
       isEnter = false;
       isEnter = false;
-      mode.pop();
+      modePop && modePop()
+      addCount = 0
     }
     }
   };
   };
+  store.bus.on('addItemBefore', () => addCount++)
 
 
   return {
   return {
     delShape(id: string) {
     delShape(id: string) {
@@ -126,8 +131,10 @@ export const useInteractiveDrawShapeAPI = installGlobalVar(() => {
       enter();
       enter();
     },
     },
     quitDrawShape: () => {
     quitDrawShape: () => {
+      const currentAddCount = addCount
       leave();
       leave();
       interactiveProps.value = void 0;
       interactiveProps.value = void 0;
+      return currentAddCount
     },
     },
     drawing: computed(() => mode.include(Mode.draw))
     drawing: computed(() => mode.include(Mode.draw))
   };
   };
@@ -627,3 +634,4 @@ export const useInteractiveAdd = <T extends ShapeType>(type: T) => {
     return useInteractiveDrawDots(type);
     return useInteractiveDrawDots(type);
   }
   }
 };
 };
+

+ 43 - 19
src/core/hook/use-expose.ts

@@ -3,6 +3,7 @@ import {
   useCursor,
   useCursor,
   useInstanceProps,
   useInstanceProps,
   useLayers,
   useLayers,
+  useMountMenusAttachs,
   useStage,
   useStage,
   useTempStatus,
   useTempStatus,
 } from "./use-global-vars.ts";
 } from "./use-global-vars.ts";
@@ -14,7 +15,7 @@ import { useGetViewBoxPositionPixel, useViewer } from "./use-viewer.ts";
 import { useGlobalResize, useListener } from "./use-event.ts";
 import { useGlobalResize, useListener } from "./use-event.ts";
 import { useInteractiveDrawShapeAPI } from "./use-draw.ts";
 import { useInteractiveDrawShapeAPI } from "./use-draw.ts";
 import { useHistory } from "./use-history.ts";
 import { useHistory } from "./use-history.ts";
-import { watchEffect } from "vue";
+import { nextTick, watchEffect } from "vue";
 import { usePaste } from "./use-paste.ts";
 import { usePaste } from "./use-paste.ts";
 import { useMouseShapesStatus } from "./use-mouse-status.ts";
 import { useMouseShapesStatus } from "./use-mouse-status.ts";
 import { Mode } from "@/constant/mode.ts";
 import { Mode } from "@/constant/mode.ts";
@@ -28,6 +29,9 @@ import { useSelectionRevise } from "./use-selection.ts";
 import { useFormalLayer, useGetFormalChildren } from "./use-layer.ts";
 import { useFormalLayer, useGetFormalChildren } from "./use-layer.ts";
 import { DataGroupId } from "@/constant/index.ts";
 import { DataGroupId } from "@/constant/index.ts";
 import { Group } from "konva/lib/Group";
 import { Group } from "konva/lib/Group";
+import { components } from "../components/index.ts";
+import { useProportion } from "./use-proportion.ts";
+import { ShapeType } from "@/index.ts";
 
 
 // 自动粘贴服务
 // 自动粘贴服务
 export const useAutoPaste = () => {
 export const useAutoPaste = () => {
@@ -56,9 +60,7 @@ export const useAutoPaste = () => {
           const image = await getImage(url);
           const image = await getImage(url);
           drawAPI.addShape(
           drawAPI.addShape(
             "image",
             "image",
-            { url, 
-              // width: image.width, height: image.height 
-            },
+            { url, width: image.width, height: image.height },
             pos,
             pos,
             true
             true
           );
           );
@@ -72,12 +74,22 @@ export const useAutoPaste = () => {
 // 快捷键服务
 // 快捷键服务
 export const useShortcutKey = () => {
 export const useShortcutKey = () => {
   // 自动退出添加模式
   // 自动退出添加模式
-  const { quitDrawShape } = useInteractiveDrawShapeAPI();
+  const { quitDrawShape, enterDrawShape } = useInteractiveDrawShapeAPI();
+  const interactiveProps = useInteractiveProps();
+  const store = useStore();
   useListener(
   useListener(
     "contextmenu",
     "contextmenu",
     (ev) => {
     (ev) => {
-      if (ev.button === 2) {
-        quitDrawShape();
+      const iProps = interactiveProps.value;
+      if (!iProps?.type || ev.button !== 2) return;
+
+      const addCount = quitDrawShape();
+      // 钢笔工具需要右键两次才退出,右键一次相当于完成
+      const isDots = components[iProps.type].addMode === "dots";
+      if (isDots && addCount > 0) {
+        nextTick(() => {
+          enterDrawShape(iProps.type, iProps.preset, iProps.operate?.single);
+        });
       }
       }
     },
     },
     document.documentElement
     document.documentElement
@@ -86,11 +98,12 @@ export const useShortcutKey = () => {
   const history = useHistory();
   const history = useHistory();
   const status = useMouseShapesStatus();
   const status = useMouseShapesStatus();
   const getChildren = useGetFormalChildren();
   const getChildren = useGetFormalChildren();
-  const store = useStore();
   const operMode = useOperMode();
   const operMode = useOperMode();
   useListener(
   useListener(
     "keydown",
     "keydown",
     (ev) => {
     (ev) => {
+      if (ev.target !== document.body) return;
+      
       if (ev.key === "z" && ev.ctrlKey) {
       if (ev.key === "z" && ev.ctrlKey) {
         history.hasUndo.value && history.undo();
         history.hasUndo.value && history.undo();
       } else if (ev.key === "y" && ev.ctrlKey) {
       } else if (ev.key === "y" && ev.ctrlKey) {
@@ -100,21 +113,30 @@ export const useShortcutKey = () => {
         history.saveLocal();
         history.saveLocal();
       } else if (ev.key === "Delete" || ev.key === "Backspace") {
       } else if (ev.key === "Delete" || ev.key === "Backspace") {
         // 删除
         // 删除
+        
         const isSelect = status.selects.length;
         const isSelect = status.selects.length;
         const shapes = isSelect ? status.selects : status.actives;
         const shapes = isSelect ? status.selects : status.actives;
+        const delItems = shapes.map((shape) => {
+          const id = shape.id();
+          if (!id) return;
+          const item = store.getItemById(id)
+          const type = store.getType(id);
+          if (!item?.disableDelete && type) {
+            return [type, id]
+          }
+        }).filter(item => !!item);
+        console.log('del', delItems)
         history.onceTrack(() => {
         history.onceTrack(() => {
-          shapes.forEach((shape) => {
-            const id = shape.id();
-            const type = id && store.getType(id);
-            if (type) {
-              store.delItem(type, id);
-            }
-          });
+          delItems.forEach(([type, id]) => {
+            store.delItem(type as ShapeType, id);
+          })
         });
         });
-        if (isSelect) {
-          status.selects = [];
-        } else {
-          status.actives = [];
+        if (delItems.length) {
+          if (isSelect) {
+            status.selects = [];
+          } else {
+            status.actives = [];
+          }
         }
         }
       } else if (operMode.value.mulSelection && ev.key === "A") {
       } else if (operMode.value.mulSelection && ev.key === "A") {
         if (status.selects.length) {
         if (status.selects.length) {
@@ -273,5 +295,7 @@ export const useExpose = installGlobalVar(() => {
     viewer,
     viewer,
     presetAdd: interactiveProps,
     presetAdd: interactiveProps,
     config: useConfig(),
     config: useConfig(),
+    mountMenus: useMountMenusAttachs(),
+    proportion: useProportion()
   };
   };
 });
 });

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

@@ -24,6 +24,8 @@ import { StoreData } from "../store/store.ts";
 import { rendererMap, rendererName } from "@/constant/index.ts";
 import { rendererMap, rendererName } from "@/constant/index.ts";
 import { Shape, ShapeConfig } from "konva/lib/Shape";
 import { Shape, ShapeConfig } from "konva/lib/Shape";
 import { ElLoading } from "element-plus";
 import { ElLoading } from "element-plus";
+import { PropertyDescribes } from "../html-mount/propertys/index.ts";
+import { ShapeType } from "@/index.ts";
 
 
 export const useRendererInstance = () => {
 export const useRendererInstance = () => {
   let instance = getCurrentInstance()!;
   let instance = getCurrentInstance()!;
@@ -260,7 +262,9 @@ export const useDownKeys = installGlobalVar(() => {
   const mouseKeys = reactive(new Set<string>());
   const mouseKeys = reactive(new Set<string>());
   const cleanup = mergeFuns(
   const cleanup = mergeFuns(
     listener(window, "keydown", (ev) => {
     listener(window, "keydown", (ev) => {
-      keyKeys.add(ev.key);
+      if (ev.target === document.body) {
+        keyKeys.add(ev.key);
+      }
     }),
     }),
     listener(window, "keyup", (ev) => {
     listener(window, "keyup", (ev) => {
       keyKeys.delete(ev.key);
       keyKeys.delete(ev.key);
@@ -350,6 +354,9 @@ export const useTempStatus = installGlobalVar(() => {
         temp.value = false
         temp.value = false
         await nextTick()
         await nextTick()
         return data;
         return data;
+      }).catch(r => {
+        temp.value = false
+        throw r
       }) as T
       }) as T
     } else {
     } else {
       temp.value = false
       temp.value = false
@@ -367,3 +374,35 @@ export const useTempStatus = installGlobalVar(() => {
 
 
   return { tempStatus: temp, enterTemp }
   return { tempStatus: temp, enterTemp }
 });
 });
+
+
+export const useMountMenusAttachs = installGlobalVar(() => {
+  const globalAttachs = ref<{[key in ShapeType]?: PropertyDescribes}>({})
+  const shapeAttachs = ref<Record<string, PropertyDescribes>>({})
+  const setShapeAttachs = (id: string, descs?: PropertyDescribes) => {
+    if (!descs) {
+      delete shapeAttachs.value[id]
+    } else {
+      shapeAttachs.value[id] = descs
+    }
+  }
+  const setAttachs = (type: ShapeType, descs?: PropertyDescribes,) => {
+    if (!descs) {
+      delete globalAttachs.value[type]
+    } else {
+      globalAttachs.value[type] = descs
+    }
+    
+  }
+
+  return {
+    setShapeAttachs,
+    setAttachs,
+    getAttachs(type: ShapeType, id: string) {
+      return {
+        ...(globalAttachs.value[type] || {}),
+        ...(shapeAttachs.value[id] || {})
+      }
+    }
+  }
+})

+ 5 - 1
src/core/hook/use-interactive.ts

@@ -11,6 +11,7 @@ import { mergeFuns } from "../../utils/shared.ts";
 import { Mode } from "@/constant/mode.ts";
 import { Mode } from "@/constant/mode.ts";
 
 
 export type InteractivePreset<T extends ShapeType = ShapeType> = {
 export type InteractivePreset<T extends ShapeType = ShapeType> = {
+  key?: string
   type: T;
   type: T;
   callback?: () => void;
   callback?: () => void;
   preset?: Partial<DrawItem<T>> & { getMessages?: () => Pos[] | Area[] };
   preset?: Partial<DrawItem<T>> & { getMessages?: () => Pos[] | Area[] };
@@ -21,7 +22,10 @@ export type InteractivePreset<T extends ShapeType = ShapeType> = {
   };
   };
 };
 };
 export const useInteractiveProps = installGlobalVar(
 export const useInteractiveProps = installGlobalVar(
-  () => ref<InteractivePreset | undefined>(),
+  () => {
+    const props = ref<InteractivePreset | undefined>()
+    return props
+  },
   Symbol("interactiveProps")
   Symbol("interactiveProps")
 );
 );
 
 

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

@@ -84,6 +84,7 @@ export const useShapeClick = (
     let downTime: number;
     let downTime: number;
     let move = false;
     let move = false;
     const downHandler = (ev: KonvaEventObject<any>) => {
     const downHandler = (ev: KonvaEventObject<any>) => {
+      // ev.evt.button === 0
       ev.cancelBubble = true
       ev.cancelBubble = true
       downTime = Date.now();
       downTime = Date.now();
       move = false;
       move = false;
@@ -256,6 +257,7 @@ export const useMouseShapesStatus = installGlobalVar(() => {
     let downPos: Pos | null = null;
     let downPos: Pos | null = null;
     let downTarget: EntityShape | null;
     let downTarget: EntityShape | null;
     stage.on("pointerdown.mouse-status", (ev) => {
     stage.on("pointerdown.mouse-status", (ev) => {
+      if (ev.evt.button !== 0) return;
       downPos = { x: ev.evt.pageX, y: ev.evt.pageY };
       downPos = { x: ev.evt.pageX, y: ev.evt.pageY };
       downTime = Date.now();
       downTime = Date.now();
       if (prevent.value) return;
       if (prevent.value) return;
@@ -273,6 +275,7 @@ export const useMouseShapesStatus = installGlobalVar(() => {
         stage.container().parentElement as HTMLDivElement,
         stage.container().parentElement as HTMLDivElement,
         "pointerup",
         "pointerup",
         async (ev) => {
         async (ev) => {
+          if (ev.button !== 0) return;
           const target = downTarget;
           const target = downTarget;
           const moveDis = downPos
           const moveDis = downPos
             ? lineLen(downPos!, { x: ev.pageX, y: ev.pageY })
             ? lineLen(downPos!, { x: ev.pageX, y: ev.pageY })
@@ -320,7 +323,6 @@ export const useMouseShapesStatus = installGlobalVar(() => {
       //   }
       //   }
       // ),
       // ),
       () => {
       () => {
-        console.error("取消鼠标事件监听");
         listeners.value.forEach((shape) => {
         listeners.value.forEach((shape) => {
           shape.off("pointerleave.mouse-status");
           shape.off("pointerleave.mouse-status");
         });
         });

+ 13 - 11
src/core/hook/use-status.ts

@@ -1,4 +1,4 @@
-import { computed, reactive } from "vue";
+import { computed, reactive, watchEffect } from "vue";
 import { installGlobalVar, stackVar, useDownKeys, useStage } from "./use-global-vars";
 import { installGlobalVar, stackVar, useDownKeys, useStage } from "./use-global-vars";
 import { Mode } from "@/constant/mode";
 import { Mode } from "@/constant/mode";
 
 
@@ -26,14 +26,14 @@ export const useMode = installGlobalVar(() => {
       modes.forEach((mode) => modeStack.value.delete(mode));
       modes.forEach((mode) => modeStack.value.delete(mode));
     },
     },
   };
   };
-  // if (import.meta.env.DEV) {
-  //   watchEffect(
-  //     () => {
-  //       console.error([...modeStack.value.values()].join(","));
-  //     },
-  //     { flush: "sync" }
-  //   );
-  // }
+  if (import.meta.env.DEV) {
+    watchEffect(
+      () => {
+        console.error([...modeStack.value.values()].join(","));
+      },
+      { flush: "sync" }
+    );
+  }
   return modeStack;
   return modeStack;
 }, Symbol("mode"));
 }, Symbol("mode"));
 
 
@@ -87,12 +87,14 @@ export const useOperMode = installGlobalVar(() => {
 
 
   return computed(() => ({
   return computed(() => ({
     // 多选模式
     // 多选模式
-    mulSelection: keys.has('Shift') && !keys.has(' ') && !keys.has('Alt'),
+    // mulSelection: keys.has('Shift') && !keys.has(' ') && !keys.has('Alt'),
+    mulSelection: false,
     // 自由移动视图
     // 自由移动视图
     freeView: keys.has(' '),
     freeView: keys.has(' '),
     // 自由绘图,不吸附
     // 自由绘图,不吸附
     freeDraw: keys.has('Control'),
     freeDraw: keys.has('Control'),
     // 组操作模式
     // 组操作模式
-    group: keys.has('Alt')
+    // group: keys.has('Alt')
+    group: false
   }))
   }))
 })
 })

+ 30 - 15
src/core/hook/use-transformer.ts

@@ -1,5 +1,5 @@
 import { useMouseShapeStatus } from "./use-mouse-status.ts";
 import { useMouseShapeStatus } from "./use-mouse-status.ts";
-import {  Ref, ref, toRaw, watch } from "vue";
+import { computed, Ref, ref, toRaw, watch } from "vue";
 import { DC, EntityShape } from "../../deconstruction";
 import { DC, EntityShape } from "../../deconstruction";
 import {
 import {
   installGlobalVar,
   installGlobalVar,
@@ -47,14 +47,14 @@ export const useTransformer = installGlobalVar(() => {
     rotationSnapTolerance: 3,
     rotationSnapTolerance: 3,
     anchorStrokeWidth: 2,
     anchorStrokeWidth: 2,
     anchorStroke: themeMouseColors.pub,
     anchorStroke: themeMouseColors.pub,
-    anchorFill: '#fff',
+    anchorFill: "#fff",
     flipEnabled: false,
     flipEnabled: false,
     padding: 10,
     padding: 10,
     useSingleNodeRotation: true,
     useSingleNodeRotation: true,
   }) as TransformerExtends;
   }) as TransformerExtends;
   transformer.queueShapes = ref([]);
   transformer.queueShapes = ref([]);
 
 
-  transformer.on("transformstart.attachText", () => {
+  transformer.on("transformstart.attachText", (e) => {
     const operateType = transformer.getActiveAnchor() as TransformerVectorType;
     const operateType = transformer.getActiveAnchor() as TransformerVectorType;
     if (operateType !== "rotater") return;
     if (operateType !== "rotater") return;
     const $node = transformer.findOne<Rect>(".rotater")!;
     const $node = transformer.findOne<Rect>(".rotater")!;
@@ -90,7 +90,7 @@ export const useTransformer = installGlobalVar(() => {
 export const usePointerIsTransformerInner = () => {
 export const usePointerIsTransformerInner = () => {
   const transformer = useTransformer();
   const transformer = useTransformer();
   const stage = useStage();
   const stage = useStage();
-  const hitShapes = usePointerIntersections()
+  const hitShapes = usePointerIntersections();
   return () => {
   return () => {
     const $stage = stage.value!.getStage();
     const $stage = stage.value!.getStage();
     const pos = $stage.pointerPos;
     const pos = $stage.pointerPos;
@@ -226,6 +226,7 @@ export const useShapeDrag = (shape: Ref<DC<EntityShape> | undefined>) => {
     });
     });
 
 
     shape.on("pointerdown.mouse-drag", (ev) => {
     shape.on("pointerdown.mouse-drag", (ev) => {
+      if (ev.evt.button !== 0) return;
       enter(conversion(getOffset(ev.evt)));
       enter(conversion(getOffset(ev.evt)));
     });
     });
 
 
@@ -235,7 +236,8 @@ export const useShapeDrag = (shape: Ref<DC<EntityShape> | undefined>) => {
         shape.off("pointerdown.mouse-drag");
         shape.off("pointerdown.mouse-drag");
         start && leave();
         start && leave();
       },
       },
-      listener(document.documentElement, "pointerup", () => {
+      listener(document.documentElement, "pointerup", (ev) => {
+        if (ev.button !== 0) return;
         start && leave();
         start && leave();
       }),
       }),
     ]);
     ]);
@@ -270,6 +272,7 @@ export const useShapeTransformer = <T extends EntityShape>(
   const offset = useShapeDrag(shape);
   const offset = useShapeDrag(shape);
   const transform = ref<Transform>();
   const transform = ref<Transform>();
   const status = useMouseShapeStatus(shape);
   const status = useMouseShapeStatus(shape);
+  const getData = useGetComponentData();
   const mode = useMode();
   const mode = useMode();
   const transformer = useTransformer();
   const transformer = useTransformer();
   const transformIngShapes = useTransformIngShapes();
   const transformIngShapes = useTransformIngShapes();
@@ -311,10 +314,12 @@ export const useShapeTransformer = <T extends EntityShape>(
 
 
     rep.tempShape.on("transform.shapemer", updateTransform);
     rep.tempShape.on("transform.shapemer", updateTransform);
 
 
+    const rect = ref(rep.tempShape.getClientRect());
     const boundHandler = () => {
     const boundHandler = () => {
       if (!selfFire) {
       if (!selfFire) {
         rep.update && rep.update();
         rep.update && rep.update();
       }
       }
+      rect.value = rep.tempShape.getClientRect();
     };
     };
     $shape.on("bound-change", boundHandler);
     $shape.on("bound-change", boundHandler);
 
 
@@ -344,12 +349,16 @@ export const useShapeTransformer = <T extends EntityShape>(
       { immediate: true }
       { immediate: true }
     );
     );
 
 
+    const data = getData(computed(() => $shape));
     const stopTransformerWatch = watch(
     const stopTransformerWatch = watch(
-      () => status.value.active,
+      () =>
+        status.value.active &&
+        !data.value?.disableTransformer &&
+        rect.value.width > 0.5 &&
+        rect.value.height > 0.5,
       (active, _, onCleanup) => {
       (active, _, onCleanup) => {
         const parent = $shape.parent;
         const parent = $shape.parent;
-        const rect = rep.tempShape.getClientRect();
-        if (!active || !parent || rect.width === 0 || rect.height === 0) return;
+        if (!active || !parent) return;
         const oldConfig: TransformerConfig = {};
         const oldConfig: TransformerConfig = {};
 
 
         for (const key in transformerConfig) {
         for (const key in transformerConfig) {
@@ -405,8 +414,13 @@ export const useShapeTransformer = <T extends EntityShape>(
         );
         );
 
 
         onCleanup(() => {
         onCleanup(() => {
-          for (const key in oldConfig) {
-            (transformer as any)[key](oldConfig[key]);
+          try {
+            for (const key in oldConfig) {
+              ;(transformer as any)[key](oldConfig[key]);
+            }
+          } catch(e) {
+            console.error('页面销毁?')
+            console.error(e)
           }
           }
           stopTransformerForceUpdate();
           stopTransformerForceUpdate();
           stopPointupListener();
           stopPointupListener();
@@ -508,7 +522,7 @@ type GetRepShape<T extends EntityShape, K extends object> = (shape: T) => {
   shape: T;
   shape: T;
   update?: (data: K, shape: T) => void;
   update?: (data: K, shape: T) => void;
   init?: (data: K, shape: T) => void;
   init?: (data: K, shape: T) => void;
-  destory?: () => void
+  destory?: () => void;
 };
 };
 export type CustomTransformerProps<
 export type CustomTransformerProps<
   T extends BaseItem,
   T extends BaseItem,
@@ -552,8 +566,8 @@ export const useCustomTransformer = <T extends BaseItem, S extends EntityShape>(
             repResult.init && repResult.init(data.value, repResult.shape);
             repResult.init && repResult.init(data.value, repResult.shape);
           },
           },
           destory: () => {
           destory: () => {
-            repResult.destory && repResult.destory()
-            destory()
+            repResult.destory && repResult.destory();
+            destory();
           },
           },
         };
         };
       })
       })
@@ -663,7 +677,8 @@ export const useLineTransformer = <T extends LineTransformerData>(
 export const useMatCompTransformer = <T extends BaseItem & { mat: number[] }>(
 export const useMatCompTransformer = <T extends BaseItem & { mat: number[] }>(
   shape: Ref<DC<EntityShape> | undefined>,
   shape: Ref<DC<EntityShape> | undefined>,
   data: Ref<T>,
   data: Ref<T>,
-  callback: (data: T) => void
+  callback: (data: T) => void,
+  getRepShape = cloneRepShape
 ) => {
 ) => {
   return useCustomTransformer(shape, data, {
   return useCustomTransformer(shape, data, {
     beforeHandler(data, mat) {
     beforeHandler(data, mat) {
@@ -673,7 +688,7 @@ export const useMatCompTransformer = <T extends BaseItem & { mat: number[] }>(
       data.mat = mat.m;
       data.mat = mat.m;
       // return true
       // return true
     },
     },
-    getRepShape: cloneRepShape,
+    getRepShape,
     callback,
     callback,
     openSnap: false,
     openSnap: false,
   });
   });

+ 6 - 1
src/core/hook/use-viewer.ts

@@ -19,9 +19,14 @@ export const useViewer = installGlobalVar(() => {
   
   
 
 
   const init = (dom: HTMLDivElement) => {
   const init = (dom: HTMLDivElement) => {
+    let downEv: PointerEvent
     const onDestroy = mergeFuns(
     const onDestroy = mergeFuns(
       dragListener(dom, {
       dragListener(dom, {
-        move: ({ end, prev }) => {
+        down(_, ev) {
+          downEv = ev
+        },
+        move: ({ end, prev, ev }) => {
+          if (downEv.button === 2) return;
           if (can.viewMode) {
           if (can.viewMode) {
             viewer.movePixel({ x: end.x - prev.x, y: end.y - prev.y });
             viewer.movePixel({ x: end.x - prev.x, y: end.y - prev.y });
           }
           }

+ 15 - 7
src/core/html-mount/propertys/components/checkbox.vue

@@ -1,19 +1,27 @@
 <template>
 <template>
-  <el-checkbox
-    @update:model-value="(val: any) => $emit('update:value', val)"
-    @change="$emit('change')"
+  <el-switch
     :model-value="value"
     :model-value="value"
-    :label="label"
-    style="height: auto"
+    @update:model-value="(val: any) => $emit('update:value', val)"
+    @change="changeHandler"
+    size="large"
+    :active-text="label"
   />
   />
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { ElCheckbox } from "element-plus";
+import { debounce } from "@/utils/shared";
+import { ElSwitch } from "element-plus";
 
 
 defineProps<{
 defineProps<{
   value?: boolean;
   value?: boolean;
   label?: string;
   label?: string;
 }>();
 }>();
-defineEmits<{ (e: "update:value", val: boolean): void; (e: "change"): void }>();
+const emit = defineEmits<{
+  (e: "update:value", val: boolean): void;
+  (e: "change"): void;
+}>();
+
+const changeHandler = debounce(() => {
+  emit("change");
+}, 1000);
 </script>
 </script>

+ 29 - 0
src/core/html-mount/propertys/components/fix-proportion.vue

@@ -0,0 +1,29 @@
+<template>
+  <div>
+    1:
+    <el-input-number
+      :controls="false"
+      :model-value="value"
+      @update:model-value="(value: any) => $emit('update:value', value)"
+      @change="$emit('change')"
+      style="width: 98px"
+      :precision="0"
+      :min="min"
+      :max="max"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ElInputNumber } from "element-plus";
+
+defineProps<{
+  value?: number;
+  min?: number;
+  max?: number;
+}>();
+defineEmits<{
+  (e: "update:value", val: number): void;
+  (e: "change"): void;
+}>();
+</script>

+ 3 - 0
src/core/html-mount/propertys/index.ts

@@ -3,6 +3,7 @@ import Select from "./components/select.vue";
 import Num from "./components/num.vue";
 import Num from "./components/num.vue";
 import Checkbox from "./components/checkbox.vue";
 import Checkbox from "./components/checkbox.vue";
 import Proportion from "./components/proportion.vue";
 import Proportion from "./components/proportion.vue";
+import FixProportion from "./components/fix-proportion.vue";
 import originDescribes from "./describes.json";
 import originDescribes from "./describes.json";
 import { Ref } from "vue";
 import { Ref } from "vue";
 
 
@@ -11,6 +12,7 @@ export const selectType = "select";
 export const numType = "num";
 export const numType = "num";
 export const checkType = "check";
 export const checkType = "check";
 export const proportionType = "proportion";
 export const proportionType = "proportion";
+export const fixProportionType = "fixProportion"
 
 
 export const propertyComponents = {
 export const propertyComponents = {
   [colorType]: Color,
   [colorType]: Color,
@@ -18,6 +20,7 @@ export const propertyComponents = {
   [selectType]: Select,
   [selectType]: Select,
   [checkType]: Checkbox,
   [checkType]: Checkbox,
   [proportionType]: Proportion,
   [proportionType]: Proportion,
+  [fixProportionType]: FixProportion
 };
 };
 
 
 export type PropertyType = keyof typeof propertyComponents;
 export type PropertyType = keyof typeof propertyComponents;

+ 24 - 13
src/core/html-mount/propertys/mount.vue

@@ -20,9 +20,11 @@
           >
           >
             <span class="label">{{ val.label }}</span>
             <span class="label">{{ val.label }}</span>
             <component
             <component
-              v-bind="{ ...describes[key].props, ...getPredefine(key) }"
+              v-bind="{ ...(describes[key].props || {}), ...getPredefine(key) }"
               :value="
               :value="
-                'value' in describes[key] ? describes[key].value : data && data[key]
+                'value' in describes[key]
+                  ? describes[key].value
+                  : props.data && props.data[key]
               "
               "
               @update:value="(val: any) => updateValue(key, val)"
               @update:value="(val: any) => updateValue(key, val)"
               @change="changeHandler"
               @change="changeHandler"
@@ -31,7 +33,7 @@
             />
             />
           </div>
           </div>
         </div>
         </div>
-        <div class="mount-bottom">
+        <div class="mount-bottom" v-if="!data?.disableDelete">
           <el-button type="danger" plain @click="emit('delete')" class="del-btn">
           <el-button type="danger" plain @click="emit('delete')" class="del-btn">
             删除
             删除
           </el-button>
           </el-button>
@@ -42,8 +44,8 @@
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { computed, ref, watch, watchEffect } from "vue";
-import { useStage } from "../../hook/use-global-vars.ts";
+import { computed, ref, watch } from "vue";
+import { useMountMenusAttachs, useStage } from "../../hook/use-global-vars.ts";
 import { useMode } from "../../hook/use-status.ts";
 import { useMode } from "../../hook/use-status.ts";
 import { PropertyDescribes, propertyComponents } from "./index.ts";
 import { PropertyDescribes, propertyComponents } from "./index.ts";
 import { DC, EntityShape } from "@/deconstruction.js";
 import { DC, EntityShape } from "@/deconstruction.js";
@@ -71,11 +73,9 @@ const emit = defineEmits<{
 }>();
 }>();
 
 
 const store = useStore();
 const store = useStore();
-const type = computed(() => {
-  const id = props.target?.getNode().id();
-  if (!id) return;
-  return store.getType(id);
-});
+const id = computed(() => props.target?.getNode().id());
+const type = computed(() => id.value && store.getType(id.value));
+const data = computed(() => (id.value ? store.getItemById(id.value) : props.data));
 
 
 const getPredefine = (key: string) => {
 const getPredefine = (key: string) => {
   if (!type.value) return;
   if (!type.value) return;
@@ -83,8 +83,18 @@ const getPredefine = (key: string) => {
   const predefine = getPredefine && (getPredefine as any)(key as keyof DrawItem);
   const predefine = getPredefine && (getPredefine as any)(key as keyof DrawItem);
   return predefine || {};
   return predefine || {};
 };
 };
+const { getAttachs } = useMountMenusAttachs();
+const describes = computed(() => {
+  const attachs = (type.value && id.value && getAttachs(type.value, id.value)) || {};
+  const describes = {
+    ...props.describes,
+    ...attachs,
+  };
+  return describes;
+});
+
 const describeItems = computed(() => {
 const describeItems = computed(() => {
-  return Object.entries(props.describes).sort(
+  return Object.entries(describes.value).sort(
     ([_a, a], [_b, b]) => (b.sort || -1) - (a.sort || -1)
     ([_a, a], [_b, b]) => (b.sort || -1) - (a.sort || -1)
   );
   );
 });
 });
@@ -112,8 +122,8 @@ watch(
 
 
 let isUpdate = false;
 let isUpdate = false;
 const updateValue = (key: string, val: any) => {
 const updateValue = (key: string, val: any) => {
-  if ("value" in props.describes[key]) {
-    props.describes[key].value = val;
+  if ("value" in describes.value[key]) {
+    describes.value[key].value = val;
   } else {
   } else {
     props.data![key] = val;
     props.data![key] = val;
   }
   }
@@ -130,6 +140,7 @@ watch(hidden, (nHidden, oHidden) => {
 const changeHandler = () => {
 const changeHandler = () => {
   isUpdate = false;
   isUpdate = false;
   emit("change");
   emit("change");
+  console.log("change handler");
 };
 };
 </script>
 </script>
 
 

+ 1 - 1
src/core/store/store.ts

@@ -21,7 +21,7 @@ export type StoreData = {
 };
 };
 const defConfig: StoreData["config"] = {
 const defConfig: StoreData["config"] = {
   compass: { rotation: 0, url: 'icons/edit_compass.svg' },
   compass: { rotation: 0, url: 'icons/edit_compass.svg' },
-  proportion: {scale: 1, unit: ''}
+  proportion: {scale: 10, unit: ''}
 };
 };
 
 
 export const getEmptyStoreData = (): StoreData => {
 export const getEmptyStoreData = (): StoreData => {

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

@@ -36,6 +36,7 @@ export const getImage = (draw: Draw, format: string) =>
   }) as Promise<Blob>;
   }) as Promise<Blob>;
 
 
 export const getHeaderActions = (draw: Draw) => {
 export const getHeaderActions = (draw: Draw) => {
+  
   return {
   return {
     undo: reactive({
     undo: reactive({
       handler: () => draw.history.undo(),
       handler: () => draw.history.undo(),
@@ -51,6 +52,7 @@ export const getHeaderActions = (draw: Draw) => {
     }),
     }),
     clear: reactive({
     clear: reactive({
       handler: () => draw.store.clear(),
       handler: () => draw.store.clear(),
+      disabled: computed(() => draw.drawing),
       text: "清除",
       text: "清除",
       icon: "clear",
       icon: "clear",
     }),
     }),
@@ -80,6 +82,7 @@ export const getHeaderActions = (draw: Draw) => {
       icon: "a-visible",
       icon: "a-visible",
     }),
     }),
     expose: reactive({
     expose: reactive({
+      disabled: computed(() => draw.drawing),
       handler: () => {},
       handler: () => {},
       text: "导出",
       text: "导出",
       icon: "download",
       icon: "download",

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

@@ -10,6 +10,7 @@ import { Draw } from "../container/use-draw";
 import { MenuItem } from "./menu";
 import { MenuItem } from "./menu";
 import { uploadResourse } from "@/example/fuse/req";
 import { uploadResourse } from "@/example/fuse/req";
 import { copy } from "@/utils/shared";
 import { copy } from "@/utils/shared";
+import { ElMessage } from "element-plus";
 
 
 
 
 export type PresetAdd<T extends ShapeType = ShapeType> = {
 export type PresetAdd<T extends ShapeType = ShapeType> = {
@@ -75,19 +76,16 @@ export const imp: MenuItem = {
       icon: "local_i",
       icon: "local_i",
       name: "本地",
       name: "本地",
       handler: async (draw: Draw) => {
       handler: async (draw: Draw) => {
-        const files = await selectFile()
+        const files = await selectFile(false, 'image/jpeg,image/png')
         const url = await uploadResourse(files[0])
         const url = await uploadResourse(files[0])
         const image = await getImage(url)
         const image = await getImage(url)
-        draw.addShape(
-          "image", 
-          {
-            width: image.width,
-            height: image.height,
-            url
-          },
-          { x: window.innerWidth / 2, y: window.innerHeight / 2 },
-          true
-        );
+        ElMessage.warning('请在画图面板中选择放置位置,鼠标右键取消')
+
+        draw.enterDrawShape('image', {
+          width: image.width,
+          height: image.height,
+          url
+        }, true)
       }
       }
     }
     }
   ]
   ]

+ 7 - 3
src/example/components/slide/slide.vue

@@ -36,14 +36,18 @@ const activeMenu = computed(() =>
   active.value === undefined ? null : getItem(active.value, props.menus)
   active.value === undefined ? null : getItem(active.value, props.menus)
 );
 );
 
 
-watch(activeMenu, (menu, _, onCleanup) => {
-  if (!menu || menu.mount) return;
+watch(activeMenu, (menu, oldMenu) => {
+  if (!menu || menu.mount) {
+    if (oldMenu?.payload?.type) {
+      props.draw.quitDrawShape();
+    }
+    return;
+  }
   if (menu.handler) {
   if (menu.handler) {
     menu.handler(props.draw);
     menu.handler(props.draw);
     nextTick(() => (active.value = undefined));
     nextTick(() => (active.value = undefined));
   } else {
   } else {
     props.draw.enterDrawShape(menu.payload.type, menu.payload.preset);
     props.draw.enterDrawShape(menu.payload.type, menu.payload.preset);
-    onCleanup(() => props.draw.quitDrawShape());
   }
   }
 });
 });
 
 

+ 35 - 20
src/example/fuse/req.ts

@@ -1,53 +1,68 @@
-import { StoreData } from '@/core/store/store';
+import { StoreData } from "@/core/store/store";
 
 
 export const getOverviewData = async () => {
 export const getOverviewData = async () => {
   const storeStr = localStorage.getItem("draw-data");
   const storeStr = localStorage.getItem("draw-data");
-  const store = (storeStr ? JSON.parse(storeStr) : {}) as StoreData
+  const store = (storeStr ? JSON.parse(storeStr) : {}) as StoreData;
 
 
   const vportStr = localStorage.getItem("view-port");
   const vportStr = localStorage.getItem("view-port");
   const vport = (vportStr ? JSON.parse(vportStr) : null) as number[] | null;
   const vport = (vportStr ? JSON.parse(vportStr) : null) as number[] | null;
 
 
   return {
   return {
     store,
     store,
-    viewport: vport
-  }
-}
+    viewport: vport,
+  };
+};
 
 
-export const saveOverviewData = async (data: {store: StoreData, viewport: number[] | null}) => {
+export const saveOverviewData = async (data: {
+  store: StoreData;
+  viewport: number[] | null;
+}) => {
   localStorage.setItem("draw-data", JSON.stringify(data.store));
   localStorage.setItem("draw-data", JSON.stringify(data.store));
   localStorage.setItem("view-port", JSON.stringify(data.viewport));
   localStorage.setItem("view-port", JSON.stringify(data.viewport));
-}
+};
 
 
 export const getTabulationData = async () => {
 export const getTabulationData = async () => {
   const storeStr = localStorage.getItem("tab-draw-data");
   const storeStr = localStorage.getItem("tab-draw-data");
-  const store = (storeStr ? JSON.parse(storeStr) : {}) as StoreData
+  const store = (storeStr ? JSON.parse(storeStr) : {}) as StoreData;
 
 
   const vportStr = localStorage.getItem("tab-view-port");
   const vportStr = localStorage.getItem("tab-view-port");
   const vport = (vportStr ? JSON.parse(vportStr) : null) as number[] | null;
   const vport = (vportStr ? JSON.parse(vportStr) : null) as number[] | null;
 
 
   const paperKeyStr = localStorage.getItem("tab-paper-key");
   const paperKeyStr = localStorage.getItem("tab-paper-key");
-  const paperKey = paperKeyStr ? JSON.parse(paperKeyStr) : 'a4'
+  const paperKey = paperKeyStr ? JSON.parse(paperKeyStr) : "a4";
 
 
   return {
   return {
     store,
     store,
-    cover: tabCoverUrl,
+    cover: tabCover,
     paperKey,
     paperKey,
-    viewport: vport
-  }
-}
+    viewport: vport,
+  };
+};
 
 
-export const saveTabulationData = async (data: {store: StoreData, viewport: number[] | null, paperKey?: string}) => {
+export const saveTabulationData = async (data: {
+  store: StoreData;
+  viewport: number[] | null;
+  paperKey?: string;
+}) => {
   localStorage.setItem("tab-draw-data", JSON.stringify(data.store));
   localStorage.setItem("tab-draw-data", JSON.stringify(data.store));
   localStorage.setItem("tab-view-port", JSON.stringify(data.viewport));
   localStorage.setItem("tab-view-port", JSON.stringify(data.viewport));
   localStorage.setItem("tab-paper-key", JSON.stringify(data.paperKey));
   localStorage.setItem("tab-paper-key", JSON.stringify(data.paperKey));
-}
+};
 
 
-let tabCoverUrl: string | null = null
-export const saveTabulationCover = async (url: string) => {
-  tabCoverUrl = url
-}
+export type TabCover = {
+  url: string;
+  width: number;
+  height: number;
+  proportion: {
+    scale: number;
+    unit: string;
+  };
+};
+let tabCover: TabCover | null = null;
+export const saveTabulationCover = async (data: TabCover) => {
+  tabCover = data;
+};
 
 
 export const uploadResourse = async (file: File) => {
 export const uploadResourse = async (file: File) => {
   return URL.createObjectURL(file);
   return URL.createObjectURL(file);
 };
 };
-

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

@@ -3,8 +3,11 @@ import { getOverviewData, getTabulationData } from "./req";
 import { StoreData } from "@/core/store/store";
 import { StoreData } from "@/core/store/store";
 
 
 export const tableCoverKey = '__tableCoverKey'
 export const tableCoverKey = '__tableCoverKey'
+export const tableCoverScaleKey = '__tableCoverScaleKey'
 export const tableTableKey = '__tableTableKey'
 export const tableTableKey = '__tableTableKey'
 export const tableTitleKey = '__tableTitleKey'
 export const tableTitleKey = '__tableTitleKey'
+export const tableCoverWidth = 514
+export const tableCoverHeight = 425
 
 
 export const overviewData = ref() as Ref<{
 export const overviewData = ref() as Ref<{
   store: StoreData;
   store: StoreData;
@@ -16,7 +19,7 @@ export const refreshOverviewData = () => {
 
 
 export const tabulationData = ref() as Ref<{
 export const tabulationData = ref() as Ref<{
   store: StoreData;
   store: StoreData;
-  cover: string | null;
+  cover: {url: string, width: number, height: number} | null;
   paperKey: string;
   paperKey: string;
   viewport: number[] | null;
   viewport: number[] | null;
 }>
 }>

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

@@ -1,8 +1,12 @@
 <template>
 <template>
   <Header :action-groups="actions" title="绘图" no-back :draw="draw">
   <Header :action-groups="actions" title="绘图" no-back :draw="draw">
     <template #saves>
     <template #saves>
-      <el-button type="primary" @click="saveHandler">保存</el-button>
-      <el-button @click="gotoTabulation" color="#E6E6E6">图纸</el-button>
+      <el-button type="primary" @click="saveHandler" :disabled="draw.drawing">
+        保存
+      </el-button>
+      <el-button @click="gotoTabulation" color="#E6E6E6" :disabled="draw.drawing">
+        图纸
+      </el-button>
     </template>
     </template>
   </Header>
   </Header>
 </template>
 </template>
@@ -22,13 +26,14 @@ import {
 } from "../../req.ts";
 } from "../../req.ts";
 import { router } from "../../router.ts";
 import { router } from "../../router.ts";
 import { params } from "@/example/env.ts";
 import { params } from "@/example/env.ts";
-import { tabulationData, refreshTabulationData } from "../../store.ts";
+import { tabulationData, refreshTabulationData, tableCoverWidth, tableCoverHeight } from "../../store.ts";
 import { nextTick } from "vue";
 import { nextTick } from "vue";
 import { getRepTabulationStore } from "../tabulation/gen.ts";
 import { getRepTabulationStore } from "../tabulation/gen.ts";
 import { getPaperConfig, paperConfigs } from "@/example/components/slide/actions.ts";
 import { getPaperConfig, paperConfigs } from "@/example/components/slide/actions.ts";
 import { DataGroupId } from "@/constant/index.ts";
 import { DataGroupId } from "@/constant/index.ts";
 import { Group } from "konva/lib/Group";
 import { Group } from "konva/lib/Group";
 import { Mode } from "@/constant/mode.ts";
 import { Mode } from "@/constant/mode.ts";
+import { lineLen } from "@/utils/math.ts";
 
 
 const draw = useDraw();
 const draw = useDraw();
 
 
@@ -69,7 +74,7 @@ const setViewToTableCover = async () => {
   const pop = draw.mode.push(Mode.readonly);
   const pop = draw.mode.push(Mode.readonly);
   const rect = draw.stage!.findOne<Group>(`#${DataGroupId}`)!.getClientRect();
   const rect = draw.stage!.findOne<Group>(`#${DataGroupId}`)!.getClientRect();
   const rectScale = rect.width / rect.height;
   const rectScale = rect.width / rect.height;
-  const tableCoverScale = 540 / 425;
+  const tableCoverScale = tableCoverWidth / tableCoverHeight;
 
 
   let width: number, height: number;
   let width: number, height: number;
   if (rectScale > tableCoverScale) {
   if (rectScale > tableCoverScale) {
@@ -107,18 +112,28 @@ const setViewToTableCover = async () => {
 
 
 const saveHandler = async () => {
 const saveHandler = async () => {
   const storeData = draw.getData();
   const storeData = draw.getData();
-  const blob = await draw.enterTemp(async () => {
+  const [blob, scale] = await draw.enterTemp(async () => {
     const recover = await setViewToTableCover();
     const recover = await setViewToTableCover();
     await nextTick();
     await nextTick();
+    const mat = draw.viewer.transform.invert();
+    const scale =
+      lineLen(mat.point({ x: 1, y: 0 }), mat.point({ x: 0, y: 0 })) *
+      draw.store.config.proportion.scale;
+
     const blob = await getImage(draw, "image/png");
     const blob = await getImage(draw, "image/png");
     recover();
     recover();
     await nextTick();
     await nextTick();
-    return blob;
+    return [blob, scale] as const;
   });
   });
 
 
   await saveOverviewData({ store: storeData, viewport: draw!.viewer.transform.m });
   await saveOverviewData({ store: storeData, viewport: draw!.viewer.transform.m });
   const url = await uploadResourse(new File([blob], `tabulation-cover-${params.id}.png`));
   const url = await uploadResourse(new File([blob], `tabulation-cover-${params.id}.png`));
-  await saveTabulationCover(url);
+  const cover = {
+    url,
+    ...draw.viewer.size!,
+    proportion: { ...draw.store.config.proportion, scale },
+  };
+  await saveTabulationCover(cover);
   await refreshTabulationData();
   await refreshTabulationData();
 
 
   const paperKey = tabulationData.value.paperKey as "a4";
   const paperKey = tabulationData.value.paperKey as "a4";
@@ -130,7 +145,7 @@ const saveHandler = async () => {
     tabulationData.value.store,
     tabulationData.value.store,
     pConfig.size,
     pConfig.size,
     pConfig.margin,
     pConfig.margin,
-    url
+    cover
   );
   );
   tabStore.config.compass = storeData.config.compass;
   tabStore.config.compass = storeData.config.compass;
   await saveTabulationData({ ...tabulationData.value, paperKey, store: tabStore });
   await saveTabulationData({ ...tabulationData.value, paperKey, store: tabStore });

+ 66 - 16
src/example/fuse/views/tabulation/gen.ts

@@ -4,11 +4,12 @@ import { getEmptyStoreData, StoreData } from "@/core/store/store";
 import { getFixPosition } from "@/utils/bound";
 import { getFixPosition } from "@/utils/bound";
 import { Size } from "@/utils/math";
 import { Size } from "@/utils/math";
 import { getImage } from "@/utils/resource";
 import { getImage } from "@/utils/resource";
-import { tableCoverKey, tableTableKey, tableTitleKey } from "../../store";
+import { tableCoverHeight, tableCoverKey, tableCoverScaleKey, tableCoverWidth, tableTableKey, tableTitleKey } from "../../store";
 import { matResponse, TableData } from "@/core/components/table";
 import { matResponse, TableData } from "@/core/components/table";
 import { TextData } from "@/core/components/text";
 import { TextData } from "@/core/components/text";
 import { ImageData } from "@/core/components/image";
 import { ImageData } from "@/core/components/image";
 import { Transform } from "konva/lib/Util";
 import { Transform } from "konva/lib/Util";
+import { TabCover } from "../../req";
 
 
 const setCoverPosition = (
 const setCoverPosition = (
   size: Size,
   size: Size,
@@ -63,12 +64,16 @@ const setTitlePosition = (
   title.mat[4] = titlePos.x;
   title.mat[4] = titlePos.x;
   title.mat[5] = titlePos.y;
   title.mat[5] = titlePos.y;
 };
 };
+const setCoverScaleTextPosition = (cover: ImageData, title: TextData) => {
+  const x = cover.mat[4] - (title.width || 0) / 2;
+  const y = cover.mat[5] + cover.height / 2 + 10;
+  title.mat[4] = x;
+  title.mat[5] = y;
+};
 
 
 
 
-const genDefaultCover = async (cover: string) => {
-  const image = await getImage(cover);
-  const tableCoverWidth = 514
-  const tableCoverHeight = 425
+const genDefaultCover = async (cover: TabCover) => {
+  const image = await getImage(cover.url);
   const rectScale = image.width / image.height
   const rectScale = image.width / image.height
   const tableCoverScale = tableCoverWidth / tableCoverHeight
   const tableCoverScale = tableCoverWidth / tableCoverHeight
 
 
@@ -81,13 +86,24 @@ const genDefaultCover = async (cover: string) => {
     width = rectScale * height;
     width = rectScale * height;
   }
   }
 
 
+  // 缩放比例默认给10的倍数
+  let wScale = cover.width / width
+  let scale = Math.round((wScale * cover.proportion.scale) / 10) * 10
+  width = cover.width / (scale / cover.proportion.scale)
+  height = width / rectScale;
+
   const coverData = {
   const coverData = {
     ...getBaseItem(),
     ...getBaseItem(),
     cornerRadius: 0,
     cornerRadius: 0,
     strokeWidth: 0,
     strokeWidth: 0,
-    url: cover,
-    lock: true,
+    url: cover.url,
+    // lock: true,
     key: tableCoverKey,
     key: tableCoverKey,
+    proportion: cover.proportion,
+    disableTransformer: true,
+    widthRaw: cover.width,
+    showScale: true,
+    heightRaw: cover.height,
     zIndex: -1,
     zIndex: -1,
     width,
     width,
     height,
     height,
@@ -96,6 +112,22 @@ const genDefaultCover = async (cover: string) => {
   return coverData;
   return coverData;
 };
 };
 
 
+const genDefaultCoverScaleText = (cover: ImageData) => {
+  const scale = (cover.widthRaw! / cover.width) * cover.proportion!.scale;
+  return {
+    ...getBaseItem(),
+    content: `1:${scale}`,
+    width: 100,
+    heihgt: 16,
+    fontSize: 12,
+    disableEditText: true,
+    key: tableCoverScaleKey,
+    disableDelete: true,
+    align: "center",
+    mat: [1, 0, 0, 1, 0, 0],
+  };
+}
+
 const genDefaultTable = () => {
 const genDefaultTable = () => {
   const nameColl = {
   const nameColl = {
     width: 140,
     width: 140,
@@ -123,7 +155,6 @@ const genDefaultTable = () => {
     height: nameColl.height * 5,
     height: nameColl.height * 5,
     mat: [1, 0, 0, 1, 0, 0],
     mat: [1, 0, 0, 1, 0, 0],
   };
   };
-  console.log({...data})
   return matResponse({ data, mat: new Transform() })
   return matResponse({ data, mat: new Transform() })
 };
 };
 
 
@@ -145,7 +176,7 @@ export const getRepTabulationStore = async (
   store: StoreData,
   store: StoreData,
   size: Size,
   size: Size,
   margin: number | number[],
   margin: number | number[],
-  coverUrl?: string | null,
+  cover?: TabCover | null,
   place = true
   place = true
 ) => {
 ) => {
   const layers = store?.layers;
   const layers = store?.layers;
@@ -158,19 +189,25 @@ export const getRepTabulationStore = async (
     setTablePosition(size, margin, tableData);
     setTablePosition(size, margin, tableData);
     layer = { text: [titleData], table: [tableData] };
     layer = { text: [titleData], table: [tableData] };
 
 
-    if (coverUrl) {
-      const imageData = await genDefaultCover(coverUrl);
+    if (cover) {
+      const imageData = await genDefaultCover(cover);
       setCoverPosition(size, margin, imageData);
       setCoverPosition(size, margin, imageData);
       layer.image = [imageData];
       layer.image = [imageData];
+      layer.text!.push(genDefaultCoverScaleText(imageData))
     }
     }
   } else {
   } else {
     layer.image = layer.image || [];
     layer.image = layer.image || [];
     const coverNdx = layer.image.findIndex(
     const coverNdx = layer.image.findIndex(
       (item) => item.key === tableCoverKey
       (item) => item.key === tableCoverKey
     );
     );
+    layer.text = layer.text || []
+    const coverScaleNdx = layer.text.findIndex(
+      (item) => item.key === tableCoverScaleKey
+    );
 
 
-    if (coverUrl) {
-      const imageData = await genDefaultCover(coverUrl);
+    if (cover) {
+      const imageData = await genDefaultCover(cover);
+      const imageScaleData = genDefaultCoverScaleText(imageData)
       if (!~coverNdx) {
       if (!~coverNdx) {
         setCoverPosition(size, margin, imageData);
         setCoverPosition(size, margin, imageData);
         layer.image.push(imageData);
         layer.image.push(imageData);
@@ -178,22 +215,35 @@ export const getRepTabulationStore = async (
         layer.image[coverNdx].url = imageData.url
         layer.image[coverNdx].url = imageData.url
         layer.image[coverNdx].width = imageData.width
         layer.image[coverNdx].width = imageData.width
         layer.image[coverNdx].height = imageData.height
         layer.image[coverNdx].height = imageData.height
+        layer.image[coverNdx].widthRaw = imageData.widthRaw
+        layer.image[coverNdx].heightRaw = imageData.heightRaw
+        layer.image[coverNdx].proportion = imageData.proportion
       }
       }
-    } else if (~coverNdx) {
-      layer.image.splice(coverNdx, 1);
+
+      if (!~coverScaleNdx) {
+        setCoverScaleTextPosition(imageData, imageScaleData)
+      } else {
+        console.log(imageScaleData)
+        layer.text[coverScaleNdx].content = imageScaleData.content
+        setCoverScaleTextPosition(imageData, layer.text[coverScaleNdx])
+      }
+    } else {
+      ~coverNdx && layer.image.splice(coverNdx, 1);
+      ~coverScaleNdx && layer.image.splice(coverScaleNdx, 1);
     }
     }
   }
   }
 
 
   if (place) {
   if (place) {
     const imageData = layer.image?.find(item => item.key === tableCoverKey)
     const imageData = layer.image?.find(item => item.key === tableCoverKey)
     imageData && setCoverPosition(size, margin, imageData)
     imageData && setCoverPosition(size, margin, imageData)
+    const imageScaleData = layer.text?.find(item => item.key === tableCoverScaleKey)
+    imageScaleData && imageData && setCoverScaleTextPosition(imageData, imageScaleData)
     const titleData = layer.text?.find(item => item.key === tableTitleKey)
     const titleData = layer.text?.find(item => item.key === tableTitleKey)
     titleData && setTitlePosition(size, margin, titleData)
     titleData && setTitlePosition(size, margin, titleData)
     const tableData = layer.table?.find(item => item.key === tableTableKey)
     const tableData = layer.table?.find(item => item.key === tableTableKey)
     tableData && setTablePosition(size, margin, tableData)
     tableData && setTablePosition(size, margin, tableData)
   }
   }
 
 
-
   if (layers) {
   if (layers) {
     return {
     return {
       ...store,
       ...store,

+ 3 - 1
src/example/fuse/views/tabulation/header.vue

@@ -1,7 +1,9 @@
 <template>
 <template>
   <Header :action-groups="actions" title="图纸" :draw="draw">
   <Header :action-groups="actions" title="图纸" :draw="draw">
     <template #saves>
     <template #saves>
-      <el-button type="primary" @click="saveHandler">保存</el-button>
+      <el-button type="primary" @click="saveHandler" :disabled="draw.drawing">
+        保存
+      </el-button>
     </template>
     </template>
   </Header>
   </Header>
 </template>
 </template>

+ 87 - 2
src/example/fuse/views/tabulation/index.vue

@@ -20,10 +20,17 @@ import Header from "./header.vue";
 import Slide from "./slide.vue";
 import Slide from "./slide.vue";
 import Container from "../../../components/container/container.vue";
 import Container from "../../../components/container/container.vue";
 import { uploadResourse } from "../../req";
 import { uploadResourse } from "../../req";
-import { ref, watch } from "vue";
+import { computed, ref, watch } from "vue";
 import { Draw } from "../../../components/container/use-draw";
 import { Draw } from "../../../components/container/use-draw";
 import Dialog from "../../../dialog/dialog.vue";
 import Dialog from "../../../dialog/dialog.vue";
-import { tabulationData, refreshTabulationData } from "../../store";
+import {
+  tabulationData,
+  refreshTabulationData,
+  tableCoverKey,
+  tableCoverScaleKey,
+} from "../../store";
+import { ImageData } from "@/core/components/image";
+import { TextData } from "@/core/components/text";
 
 
 const full = ref(false);
 const full = ref(false);
 const draw = ref<Draw>();
 const draw = ref<Draw>();
@@ -36,7 +43,85 @@ const init = async (draw: Draw) => {
   draw.config.showLabelLine = false;
   draw.config.showLabelLine = false;
   draw.config.showComponentSize = false;
   draw.config.showComponentSize = false;
   draw.store.setStore(tabulationData.value.store);
   draw.store.setStore(tabulationData.value.store);
+  draw.store.config.compass.url = "/icons/compass.svg";
   inited.value = true;
   inited.value = true;
 };
 };
 watch(draw, (draw) => draw && init(draw));
 watch(draw, (draw) => draw && init(draw));
+
+const cover = computed(
+  () => draw.value?.store.items.find((item) => item.key === tableCoverKey) as ImageData
+);
+const coverScale = computed(
+  () =>
+    cover.value &&
+    Math.round(
+      (cover.value.widthRaw! / cover.value.width) * cover.value.proportion!.scale
+    )
+);
+watch(cover, (cover, _, onCleanup) => {
+  if (!cover || !draw.value || !cover.widthRaw || !cover.heightRaw || !cover.proportion)
+    return;
+  const mountMenus = draw.value.mountMenus;
+
+  mountMenus.setShapeAttachs(cover.id, {
+    scale: {
+      type: "fixProportion",
+      label: "缩放比例",
+      "layout-type": "row",
+      get value() {
+        return coverScale.value;
+      },
+      set value(val) {
+        const oldWidth = cover.width;
+        cover.width = cover.widthRaw! / (val / cover.proportion!.scale);
+        cover.height = (cover.width / oldWidth) * cover.height;
+      },
+      props: {},
+    },
+    showScale: {
+      type: "check",
+      label: "显示比例",
+      "layout-type": "row",
+      get value() {
+        return (cover as any).showScale || false;
+      },
+      set value(val) {
+        (cover as any).showScale = val;
+      },
+      props: {},
+    },
+  });
+  onCleanup(() => {
+    mountMenus.setShapeAttachs(cover.id);
+  });
+});
+
+const coverScaleText = computed(
+  () =>
+    draw.value?.store.items.find((item) => item.key === tableCoverScaleKey) as TextData
+);
+watch(
+  () => {
+    return {
+      text: coverScaleText.value,
+      exists: cover.value,
+      show: (cover.value as any)?.showScale,
+      content: coverScale.value ? `1:${coverScale.value}` : "",
+    };
+  },
+  ({ text, show, exists, content }) => {
+    if (!text) return;
+    draw.value!.history.preventTrack(() => {
+      if (!exists) {
+        draw.value!.store.delItem("text", text.id);
+      } else {
+        draw.value!.store.setItem("text", {
+          value: { hide: !show, content },
+          id: text.id,
+        });
+      }
+    });
+  },
+  { flush: "post" }
+);
 </script>
 </script>

+ 4 - 4
src/example/platform/platform-draw.ts

@@ -162,14 +162,14 @@ const drawLayerResource = (
   return bound.get();
   return bound.get();
 };
 };
 
 
-const unitScale = 100;
-const unit = "cm";
 export const drawPlatformResource = (data: AIExposeData, draw: Draw) => {
 export const drawPlatformResource = (data: AIExposeData, draw: Draw) => {
-  const layers = getResourceLayers(scaleResource(data, unitScale));
+  // 默认为米,为了方便绘图 一米转为100
+  const layers = getResourceLayers(scaleResource(data, 100));
   const layerBounds: ReturnType<typeof drawLayerResource>[] = [];
   const layerBounds: ReturnType<typeof drawLayerResource>[] = [];
 
 
   draw.history.onceTrack(() => {
   draw.history.onceTrack(() => {
-    draw.store.setConfig({ proportion: { scale: 1, unit } });
+    // draw.store.setConfig({ proportion: { scale: 10, unit: 'mm' } });
+    draw.store.setConfig({ proportion: { scale: 10, unit: '' } });
     for (const layer of layers) {
     for (const layer of layers) {
       // if (!draw.store.layers.includes(layer.name)) {
       // if (!draw.store.layers.includes(layer.name)) {
       //   draw.store.addLayer(layer.name);
       //   draw.store.addLayer(layer.name);

+ 1 - 0
src/example/styles/global.scss

@@ -33,6 +33,7 @@ body {
 }
 }
 .disabled {
 .disabled {
 	pointer-events: none;
 	pointer-events: none;
+	cursor: not-allowed;
 	opacity: 0.7;
 	opacity: 0.7;
 }
 }
 
 

+ 3 - 0
需求

@@ -0,0 +1,3 @@
+线段可删除
+
+设置比例