소스 검색

feat: 优化侧边栏逻辑

bill 1 개월 전
부모
커밋
4854b62a10
37개의 변경된 파일775개의 추가작업 그리고 190개의 파일을 삭제
  1. BIN
      src/core/assert/cursor/pic_pen.ico
  2. BIN
      src/core/assert/cursor/pic_pen_a.ico
  3. BIN
      src/core/assert/cursor/pic_pen_r.ico
  4. 0 1
      src/core/components/icon/icon.ts
  5. 1 8
      src/core/components/icon/icon.vue
  6. 28 6
      src/core/components/line-icon/icon.vue
  7. 9 10
      src/core/components/line-icon/index.ts
  8. 0 6
      src/core/components/line-icon/temp-icon.vue
  9. 170 7
      src/core/components/line/attach-server.ts
  10. 91 40
      src/core/components/line/attach-view.ts
  11. 96 25
      src/core/components/line/single-line.vue
  12. 7 3
      src/core/components/line/single-point.vue
  13. 1 3
      src/core/components/line/temp-line.vue
  14. 1 1
      src/core/hook/use-global-vars.ts
  15. 0 1
      src/core/hook/use-interactive.ts
  16. 1 1
      src/core/html-mount/propertys/index.ts
  17. 1 9
      src/core/renderer/draw-group.vue
  18. 1 1
      src/core/renderer/draw-shape.vue
  19. 15 0
      src/core/renderer/group-temp.vue
  20. 4 2
      src/core/renderer/renderer.vue
  21. 1 1
      src/example/components/header/actions.ts
  22. 4 2
      src/example/components/slide/actions.ts
  23. 0 1
      src/example/components/slide/menu.ts
  24. 37 8
      src/example/fuse/views/overview/slide-icons.vue
  25. 46 10
      src/example/components/slide/slide-item.vue
  26. 77 8
      src/example/components/slide/slide.vue
  27. 4 2
      src/example/constant.ts
  28. 1 0
      src/example/dialog/ai/index.ts
  29. 2 0
      src/example/dialog/dialog.vue
  30. 4 3
      src/example/fuse/views/overview/slide.vue
  31. 0 1
      src/example/fuse/views/tabulation/slide-icons.vue
  32. 5 3
      src/example/fuse/views/tabulation/slide.vue
  33. 54 2
      src/example/platform/platform-draw.ts
  34. 9 1
      src/example/platform/platform-resource.ts
  35. 73 24
      src/example/platform/resource-swkk.ts
  36. 4 0
      src/utils/dom.ts
  37. 28 0
      src/utils/shared.ts

BIN
src/core/assert/cursor/pic_pen.ico


BIN
src/core/assert/cursor/pic_pen_a.ico


BIN
src/core/assert/cursor/pic_pen_r.ico


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

@@ -142,7 +142,6 @@ export const interactiveToData: InteractiveTo<"icon"> = ({
   ...args
 }) => {
   if (info.cur) {
-    console.error(preset);
     return interactiveFixData({
       ...args,
       viewTransform,

+ 1 - 8
src/core/components/icon/icon.vue

@@ -77,13 +77,6 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus({
   copyHandler(mat, data) {
     return matResponse({ data, mat, increment: true });
   },
-  propertys: [
-    "name",
-    "fill",
-    "stroke",
-    "strokeWidth",
-    //  "dash", "opacity",
-    "strokeScaleEnabled",
-  ],
+  propertys: ["name", "fill", "stroke", "strokeWidth"],
 });
 </script>

+ 28 - 6
src/core/components/line-icon/icon.vue

@@ -36,7 +36,14 @@ import { usePointerPos } from "@/core/hook/use-global-vars.ts";
 import { useViewerInvertTransform } from "@/core/hook/use-viewer.ts";
 import { computed, nextTick, watch } from "vue";
 import { useHistory } from "@/core/hook/use-history.ts";
-import { eqPoint, line2IncludedAngle, lineInner, lineLen, Pos } from "@/utils/math.ts";
+import {
+  eqPoint,
+  line2IncludedAngle,
+  lineInner,
+  lineLen,
+  lineVector,
+  Pos,
+} from "@/utils/math.ts";
 import { copy } from "@/utils/shared.ts";
 
 const props = defineProps<{ data: LineIconData }>();
@@ -99,23 +106,38 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus({
         flipEnabled: true,
         rotateEnabled: false,
         enabledAnchors: ["middle-left", "middle-right"],
+        boundBoxFunc: (oldBox, newBox) => {
+          if (newBox.width < 5) {
+            return oldBox;
+          } else {
+            return newBox;
+          }
+        },
       },
     });
   },
   defaultStyle,
-  copyHandler(mat, data) {
-    return matResponse({ data, mat });
+  copyHandler(_, data) {
+    const snapLine = getSnapLine(store, data)!;
+    const line = getLineIconEndpoints(getSnapLine(store, data)!, data);
+    const vector = lineVector(line);
+    const move = vector.multiplyScalar(lineLen(line[0], line[1]));
+    const mat = new Transform()
+      .translate(move.x, move.y)
+      .multiply(getLineIconMat(snapLine, data));
+    return matResponse({ data, mat, store });
   },
-  propertys: ["name", "fill", "stroke", "strokeWidth", "strokeScaleEnabled"],
+  propertys: ["name", "fill", "stroke", "strokeWidth"],
 });
 
 const line = computed(() => getSnapLine(store, props.data));
 const history = useHistory();
 watch(
-  () => copy(line.value) as Pos[],
+  () => line.value && (copy(line.value) as Pos[]),
   (line, oldLine) => {
     history.preventTrack(() => {
       if (!line) {
+        console.error("找不到line", props.data);
         return emit("delShape");
       }
       if (!oldLine) return;
@@ -154,7 +176,7 @@ watch(
       });
     });
   },
-  { immediate: true }
+  { immediate: true, flush: "post" }
 );
 
 if (props.data.type === "align-bottom") {

+ 9 - 10
src/core/components/line-icon/index.ts

@@ -29,17 +29,19 @@ export type LineIconData = Omit<IconData, "mat" | "width"> & {
   lineId: string;
   openSide: "LEFT" | "RIGHT";
   type: "full" | "align-bottom";
+  __snapLine?: Pos[],
 };
 
 export const getSnapLine = (
   store: DrawStore,
-  data: Pick<LineIconData, "lineId">
+  data: Pick<LineIconData, "lineId" | '__snapLine'>,
+  lineData?: LineData
 ) => {
-  const lineData = store.getTypeItems("line")[0];
+  lineData = lineData || store.getTypeItems("line")[0];
   if (!lineData) return null;
 
   const wall = lineData.lines.find((line) => line.id === data.lineId);
-  if (!wall) return null;
+  if (!wall) return data.__snapLine
 
   return [
     lineData.points.find((p) => p.id === wall.a)!,
@@ -99,6 +101,7 @@ export const getLineIconMat = (
   if (lineLen(afterStart, line[0]) > lineLen(afterStart, line[1])) {
     mat.scale(-1, 1);
   }
+  // console.log(data.id, copy(data), copy(snapLine), mat.m)
   return mat;
 };
 
@@ -108,14 +111,11 @@ export const matResponse = ({
   store,
   operType,
   increment
-}: MatResponseProps<"lineIcon">) => {
+}: MatResponseProps<"lineIcon">, lineData?: LineData) => {
   if (!store || increment) return data;
-  const lineData = store.getTypeItems("line")[0];
+  lineData = lineData || store.getTypeItems("line")[0];
   if (!lineData) return data;
 
-  const wall = lineData.lines.find((line) => line.id === data.lineId);
-  if (!wall) return data;
-
   // 简单移动
   if (!operType) {
     const position = { x: mat.m[4], y: mat.m[5] };
@@ -163,7 +163,7 @@ export const matResponse = ({
   return data;
 };
 
-export const genGetLineIconAttach = (lineData: LineData, size: Size) => {
+export const genGetLineIconAttach = (lineData: LineData, size: Size, minLen = 200) => {
   const lines = lineData.lines.map((line) => [
     lineData.points.find((p) => p.id === line.a)!,
     lineData.points.find((p) => p.id === line.b)!,
@@ -179,7 +179,6 @@ export const genGetLineIconAttach = (lineData: LineData, size: Size) => {
       return { start, end, pjPoint, len: lineLen(pjPoint, position) };
     });
 
-    let minLen = 200;
     let ndx = -1;
     for (let i = 0; i < shapeLines.length; i++) {
       if (

+ 0 - 6
src/core/components/line-icon/temp-icon.vue

@@ -26,12 +26,6 @@ defineExpose({
 const props = defineProps<{ data: LineIconData; addMode?: boolean }>();
 const store = useStore();
 const line = computed(() => getSnapLine(store, props.data));
-const weight = computed(
-  () =>
-    store.getTypeItems("line")[0].lines.find((line) => line.id === props.data.lineId)
-      ?.strokeWidth
-);
-
 const mat = computed(() => {
   if (!line.value) return;
   return getLineIconMat(line.value, props.data).m;

+ 170 - 7
src/core/components/line/attach-server.ts

@@ -1,6 +1,7 @@
 import {
   SnapResultInfo,
   useCustomSnapInfos,
+  useSnap,
   useSnapConfig,
 } from "@/core/hook/use-snap";
 import { defaultStyle, getSnapInfos, LineData } from ".";
@@ -18,14 +19,35 @@ import {
   verticalVector,
   zeroEq,
 } from "@/utils/math";
-import { copy, onlyId, rangMod } from "@/utils/shared";
+import {
+  copy,
+  frameEebounce,
+  mergeFuns,
+  onlyId,
+  rangMod,
+} from "@/utils/shared";
 import { MathUtils, Vector2 } from "three";
-import { getBaseItem } from "../util";
+import { generateSnapInfos, getBaseItem } from "../util";
 import { ComponentSnapInfo } from "..";
 import { useStore } from "@/core/store";
-import { computed, Ref } from "vue";
-import { installGlobalVar } from "@/core/hook/use-global-vars";
+import { computed, onUnmounted, reactive, ref, Ref, watch } from "vue";
+import {
+  usePointerPos,
+  useRunHook,
+  useStage,
+} from "@/core/hook/use-global-vars";
 import { mergeDescribes, PropertyDescribes } from "@/core/html-mount/propertys";
+import { useMode } from "@/core/hook/use-status";
+import { useListener } from "@/core/hook/use-event";
+import { Mode } from "@/constant/mode";
+import { useViewerInvertTransform } from "@/core/hook/use-viewer";
+import { clickListener } from "@/utils/event";
+import {
+  genGetLineIconAttach,
+  getLineIconMat,
+  LineIconData,
+} from "../line-icon";
+import { useDrawIngData } from "@/core/hook/use-draw";
 
 export type NLineDataCtx = {
   del: {
@@ -460,7 +482,7 @@ export const useLineDataSnapInfos = () => {
   let snapInfos: ComponentSnapInfo[];
 
   const updateSnapInfos = (pointIds: string[]) => {
-    clear()
+    clear();
     snapInfos = getSnapInfos({
       ...lineData.value,
       lines: lineData.value.lines.filter(
@@ -566,8 +588,149 @@ export const useLineDescribes = (line: Ref<LineData["lines"][0]>) => {
       if (!d.isChange) {
         setLineVector = lineVector(points.value);
       }
-      updateLineLength(lineData.value, line.value, val, undefined, setLineVector)
+      updateLineLength(
+        lineData.value,
+        line.value,
+        val,
+        undefined,
+        setLineVector
+      );
     },
   };
-  return d as PropertyDescribes
+  return d as PropertyDescribes;
+};
+
+export const useDrawLinePoint = (
+  data: LineData,
+  line: LineData["lines"][0],
+  callback: (data: {
+    prev: LineData["lines"][0];
+    next: LineData["lines"][0];
+    point: LineData["points"][0];
+    oldIcons: LineIconData[];
+    newIcons: LineIconData[];
+  }) => void
+) => {
+  const mode = useMode();
+  let __leave: (() => void) | null;
+  const leave = () => {
+    if (__leave) {
+      __leave();
+      __leave = null;
+    }
+  };
+
+  useListener("contextmenu", (ev) => ev.button === 2 && setTimeout(leave));
+  onUnmounted(leave);
+
+  const pos = usePointerPos();
+  const viewInvMat = useViewerInvertTransform();
+  const drawProps = ref<{
+    data: LineData;
+    prev: LineData["lines"][0];
+    next: LineData["lines"][0];
+    point: LineData["points"][0];
+  }>();
+  const runHook = useRunHook();
+  const snapInfos = useLineDataSnapInfos();
+  const snap = useSnap();
+  const stage = useStage();
+  const store = useStore();
+  const icons = computed(() =>
+    store.getTypeItems("lineIcon").filter((item) => item.lineId === line.id)
+  );
+  const drawStore = useDrawIngData();
+  const enterDraw = () => {
+    const points = [
+      data.points.find((p) => p.id === line.a)!,
+      data.points.find((p) => p.id === line.b)!,
+    ];
+    const cdata: LineData = { ...data, points, lines: [] };
+    const point = reactive({ ...lineCenter(points), id: onlyId() });
+    const cIcons = icons.value.map(icon => ({...icon, id: onlyId()}));
+    const iconInfos = icons.value.map((icon) => {
+      const mat = getLineIconMat(points, icon);
+      return {
+        position: { x: mat.m[4], y: mat.m[5] },
+        size: {
+          width: icon.endLen - icon.startLen,
+          height: icon.height,
+        },
+      };
+    });
+
+    const prev = { ...line, id: onlyId(), b: point.id };
+    const next = { ...line, id: onlyId(), a: point.id };
+    cdata.lines.push(prev, next);
+    cdata.points.push(point);
+
+    drawProps.value = { data: cdata, prev, next, point };
+    let isStop = false;
+    const afterUpdate = frameEebounce((position: Pos) => {
+      if (isStop) return;
+      snap.clear();
+      position = viewInvMat.value.point(position);
+
+      const mat = snap.move(generateSnapInfos([position], true, true));
+      Object.assign(point, mat ? mat.point(position) : position);
+
+      drawStore.lineIcon = [];
+      cIcons.forEach((icon, ndx) => {
+        const getAttach = genGetLineIconAttach(cdata, iconInfos[ndx].size, 200);
+        const attach = getAttach(iconInfos[ndx].position);
+        if (attach) {
+          const line = cdata.lines.find((item) => item.id === attach.lineId)!;
+          const snapLine = [
+            cdata.points.find((p) => p.id === line.a)!,
+            cdata.points.find((p) => p.id === line.b)!,
+          ];
+          const iconData = { ...icon, ...attach, __snapLine: snapLine };
+          drawStore.lineIcon!.push(iconData);
+        }
+      });
+    });
+
+    snapInfos.update([]);
+    return mergeFuns(
+      runHook<() => void>(() =>
+        clickListener(stage.value!.getNode().container(), () => {
+          callback({
+            prev,
+            next,
+            point,
+            oldIcons: icons.value,
+            newIcons: drawStore.lineIcon!,
+          });
+          leave();
+        })
+      ),
+      watch(pos, (pos) => pos && afterUpdate(pos)),
+      () => {
+        drawProps.value = undefined;
+        snapInfos.clear();
+        snap.clear();
+        isStop = true;
+      }
+    );
+  };
+
+  const enter = () => {
+    __leave = mergeFuns(
+      () => (__leave = null),
+      mode.push(Mode.draw),
+      watch(
+        () => mode.include(Mode.draw),
+        (hasDraw, _, onCleanup) => {
+          hasDraw ? onCleanup(enterDraw()) : leave();
+        },
+        { immediate: true }
+      )
+    );
+  };
+
+  return {
+    leave,
+    enter,
+    drawProps,
+  };
 };

+ 91 - 40
src/core/components/line/attach-view.ts

@@ -1,4 +1,5 @@
 import {
+  eqPoint,
   getDiffPolygons,
   getVectorLine,
   isPolygonPointInner,
@@ -18,12 +19,12 @@ import { MathUtils } from "three";
 import { diffArrayChange, rangMod } from "@/utils/shared";
 import { globalWatch, installGlobalVar } from "@/core/hook/use-global-vars";
 import { useStore } from "@/core/store";
-import { computed, reactive, watchEffect } from "vue";
+import { computed, reactive, Ref, toRaw, watchEffect } from "vue";
 import { Transform } from "konva/lib/Util";
 import { sortFn } from "@/core/store/store";
 import { getLineIconEndpoints, getSnapLine } from "../line-icon";
-import { useTestPoints } from "@/core/hook/use-debugger";
 import { useDrawIngData } from "@/core/hook/use-draw";
+import { useTestPoints } from "@/core/hook/use-debugger";
 
 const minAngle = MathUtils.degToRad(0.1);
 const palAngle = MathUtils.degToRad(20);
@@ -212,71 +213,121 @@ export const useGetDiffIconPolygons = installGlobalVar(() => {
 });
 
 // 计算与icon相差的多边形
-export const useGetDiffLineIconPolygons = (line: LineData["lines"][0]) => {
+export const useGetDiffLineIconPolygons = (line: LineData["lines"][0], linePoints: Ref<Pos[]>) => {
   const store = useStore();
-  const drawStore = useDrawIngData()
-  const linePoints = computed(() => getSnapLine(store, { lineId: line.id }));
+  const drawStore = useDrawIngData();
   const linevv = computed(() => verticalVector(lineVector(linePoints.value!)));
   const icons = computed(() => {
-    if (drawStore.lineIcon) {
-      return drawStore.lineIcon.concat(store.getTypeItems("lineIcon"))
-    } else {
-      return store.getTypeItems("lineIcon")
-    }
+    const icons = store
+      .getTypeItems("lineIcon")
+      .concat(drawStore.lineIcon || []);
+    return icons.filter((item) => !item.hide);
   });
   const lineIcons = computed(() =>
     icons.value.filter((icon) => icon.lineId === line.id)
   );
-  const interPolygons = computed(() => {
+
+  const interSteps = computed(() => {
     const lineSteps = lineIcons.value
-      .map((icon) =>
-        icon.endLen > icon.startLen
-          ? [icon.startLen, icon.endLen]
-          : [icon.endLen, icon.startLen]
-      )
-      .sort((line1, line2) => line1[0] - line2[0]);
+      .map((icon) => {
+        const openSide = icon.openSide;
+        const endLen = icon.endLen || 0.001;
+        const startLen = icon.startLen || 0.001;
+        const inv = endLen < startLen;
+        return {
+          lens: inv ? [endLen, startLen] : [startLen, endLen],
+          openSide: inv ? (openSide === "LEFT" ? "RIGHT" : "LEFT") : openSide,
+        };
+      })
+      .sort((line1, line2) => line1.lens[0] - line2.lens[0]);
     if (!lineSteps.length) return [];
 
-    const interSteps: number[][] = [];
+    const interSteps: { lens: number[]; openSide: "LEFT" | "RIGHT" }[] = [];
     let i = 0;
     do {
-      const startStep = lineSteps[i][0];
-      let endStep = lineSteps[i][1];
+      const startStep = lineSteps[i].lens[0];
+      const openSide = lineSteps[i].openSide;
+      let endStep = lineSteps[i].lens[1];
       for (i++; i < lineSteps.length; i++) {
-        if (lineSteps[i][0] <= endStep) {
-          if (lineSteps[i][1] > endStep) {
-            endStep = lineSteps[i][1];
+        if (lineSteps[i].lens[0] <= endStep) {
+          if (lineSteps[i].lens[1] > endStep) {
+            endStep = lineSteps[i].lens[1];
           }
         } else {
           break;
         }
       }
-      interSteps.push([startStep, endStep]);
+      interSteps.push({
+        lens: [startStep, endStep],
+        openSide,
+      });
     } while (i < lineSteps.length);
 
-    const interLines = interSteps.map((steps) =>
-      getLineIconEndpoints(linePoints.value!, {
-        startLen: steps[0],
-        endLen: steps[1],
-      })
-    );
+    return interSteps;
+  });
+
+  const interLines = computed(() =>
+    interSteps.value.map((steps) => ({
+      openSide: steps.openSide,
+      points: getLineIconEndpoints(linePoints.value!, {
+        startLen: steps.lens[0],
+        endLen: steps.lens[1],
+      }),
+    }))
+  );
 
-    return interLines.map(interLine => {
-      const topOffset = linevv.value.clone().multiplyScalar(line.strokeWidth)
-      const botOffset = topOffset.clone().multiplyScalar(-1)
+  const stepLines = computed(() => {
+    if (!linePoints.value?.length || !interLines.value.length) return [];
+
+    const steps: Pos[][] = [];
+    if (!eqPoint(linePoints.value[0], interLines.value[0].points[0])) {
+      steps.push([linePoints.value[0], interLines.value[0].points[0]]);
+    }
+    let start = interLines.value[0].points[1];
+
+    let i = 1;
+    for (; i < interLines.value.length; i++) {
+      const iLine = interLines.value[i];
+      steps.push([start, iLine.points[0]]);
+      start = iLine.points[1];
+    }
+
+    if (!eqPoint(start, linePoints.value[1])) {
+      steps.push([start, linePoints.value[1]]);
+    }
+
+    return steps;
+  });
+
+  const subStepsLines = computed(() => {
+    return interLines.value.map((il) => {
+      return il.openSide === "RIGHT" ? il.points : [...il.points].reverse();
+    });
+  });
+
+  const interPolygons = computed(() => {
+    return interLines.value.map((il) => {
+      const interLine = il.points;
+      const topOffset = linevv.value.clone().multiplyScalar(line.strokeWidth);
+      const botOffset = topOffset.clone().multiplyScalar(-1);
       return [
         topOffset.clone().add(interLine[0]),
         topOffset.clone().add(interLine[1]),
         botOffset.clone().add(interLine[1]),
         botOffset.clone().add(interLine[0]),
-      ]
-    })
+      ];
+    });
   });
 
-  return (polygon: Pos[]) => {
-    if (!interPolygons.value.length) {
-      return [polygon];
-    }
-    return getDiffPolygons(polygon, interPolygons.value)
+  return {
+    diff: (polygon: Pos[]) => {
+      const result = interPolygons.value.length
+        ? getDiffPolygons(polygon, interPolygons.value)
+        : [polygon];
+
+      return result;
+    },
+    subSteps: subStepsLines,
+    steps: stepLines,
   };
 };

+ 96 - 25
src/core/components/line/single-line.vue

@@ -4,7 +4,7 @@
     @dragend="emit('dragLineEnd', props.line)" -->
   <EditLine
     :ref="(d: any) => shape = d?.shape"
-    :data="data"
+    :data="lineData"
     :opacity="0"
     :points="points"
     :closed="false"
@@ -22,29 +22,44 @@
     @add-point="addPoint"
   />
 
-  <SizeLine
-    v-if="
-      status.active ||
-      config.showComponentSize ||
-      isDrawIng ||
-      dragPointIds?.includes(line.a) ||
-      dragPointIds?.includes(line.b)
-    "
-    :points="points"
-    :strokeWidth="style.strokeWidth"
-    :stroke="style.stroke"
-  />
-
   <v-line
-    v-for="polygon in diffPolygons"
+    v-for="polygon in polygons"
     :config="{
+      opacity: drawProps ? 0.7 : 1,
       points: flatPositions(polygon),
       fill: isDrawIng ? themeColor : style.stroke,
       closed: true,
       listening: false,
     }"
-    v-if="diffPolygons"
   />
+
+  <template
+    v-if="
+      status.active ||
+      config.showComponentSize ||
+      isDrawIng ||
+      dragPointIds?.includes(line.a) ||
+      dragPointIds?.includes(line.b)
+    "
+  >
+    <v-group>
+      <template v-if="gd.steps.value.length">
+        <SizeLine
+          :points="line"
+          :strokeWidth="style.strokeWidth"
+          :stroke="style.stroke"
+          v-for="line in [...gd.steps.value, ...gd.subSteps.value]"
+        />
+      </template>
+      <SizeLine
+        :points="points"
+        :strokeWidth="style.strokeWidth"
+        :stroke="style.stroke"
+        v-else
+      />
+    </v-group>
+  </template>
+
   <PropertyUpdate
     :describes="describes"
     :data="line"
@@ -60,13 +75,37 @@
     @delete="delHandler"
   />
   <Operate :target="shape" :menus="menus" />
+
+  <template v-if="drawProps">
+    <SingleLine
+      :data="drawProps.data"
+      :line="drawProps.prev"
+      :drawMode="drawProps.point"
+    />
+    <singlePoint
+      :data="drawProps.data"
+      :line="drawProps.prev"
+      :drawMode="drawProps.point"
+    />
+    <SingleLine
+      :data="drawProps.data"
+      :line="drawProps.next"
+      :drawMode="drawProps.point"
+    />
+    <singlePoint
+      :data="drawProps.data"
+      :line="drawProps.next"
+      :drawMode="drawProps.point"
+    />
+  </template>
 </template>
 
 <script lang="ts" setup>
-import { computed, ref, watchEffect } from "vue";
+import EditLine from "../share/edit-line.vue";
+import singlePoint from "./single-point.vue";
+import { computed, ref } from "vue";
 import { getMouseStyle, LineData, shapeName } from "./index.ts";
 import { flatPositions, onlyId } from "@/utils/shared.ts";
-import EditLine from "../share/edit-line.vue";
 import { Pos } from "@/utils/math.ts";
 import { Line } from "konva/lib/shapes/Line";
 import { DC } from "@/deconstruction.js";
@@ -79,7 +118,13 @@ import {
 } from "@/core/hook/use-mouse-status.ts";
 import { themeColor } from "@/constant";
 import { useGetDiffLineIconPolygons, useGetExtendPolygon } from "./attach-view.ts";
-import { useLineDataSnapInfos, useLineDescribes } from "./attach-server.ts";
+import {
+  useDrawLinePoint,
+  useLineDataSnapInfos,
+  useLineDescribes,
+} from "./attach-server.ts";
+import { useStore } from "@/core/store/index.ts";
+import { useHistory } from "@/core/hook/use-history.ts";
 
 const props = defineProps<{
   line: LineData["lines"][number];
@@ -87,11 +132,13 @@ const props = defineProps<{
   canEdit?: boolean;
   data: LineData;
   dragPointIds?: string[];
+  drawMode?: LineData["points"][number];
 }>();
 
 const emit = defineEmits<{
   (e: "updatePoint", value: LineData["points"][number]): void;
   (e: "addPoint", value: LineData["points"][number]): void;
+  (e: "addLine", value: LineData["lines"][number]): void;
   (e: "delLine"): void;
   (e: "updateLine", value: LineData["lines"][number]): void;
   (e: "updateBefore", value: string[]): void;
@@ -104,14 +151,14 @@ const emit = defineEmits<{
 
 const getExtendPolygon = useGetExtendPolygon();
 const polygon = computed(() => getExtendPolygon(props.data, props.line, true));
-const getDiffPolygons = useGetDiffLineIconPolygons(props.line);
-const diffPolygons = computed(() => getDiffPolygons(polygon.value));
-
-const shape = ref<DC<Line>>();
 const points = computed(() => [
   props.data.points.find((p) => p.id === props.line.a)!,
   props.data.points.find((p) => p.id === props.line.b)!,
 ]);
+const gd = useGetDiffLineIconPolygons(props.line, points);
+const polygons = computed(() => gd.diff(polygon.value));
+
+const shape = ref<DC<Line>>();
 const lineData = computed(() => props.line);
 const describes = useLineDescribes(lineData);
 
@@ -120,7 +167,29 @@ const delHandler = () => {
   emit("delLine");
   emit("update");
 };
-const menus = [{ label: "删除", handler: delHandler }];
+
+const store = useStore();
+const history = useHistory();
+const { drawProps, enter: enterDrawLinePoint } = useDrawLinePoint(
+  props.data,
+  props.line,
+  (data) => {
+    emit("updateBefore", [props.line.a, props.line.b]);
+    emit("addPoint", data.point);
+    emit("addLine", data.prev);
+    emit("addLine", data.next);
+    emit("delLine");
+    history.preventTrack(() => {
+      data.oldIcons.forEach((icon) => store.delItem("lineIcon", icon.id));
+      store.addItems("lineIcon", data.newIcons);
+    });
+    emit("update");
+  }
+);
+const menus = [
+  { label: "加点", handler: enterDrawLinePoint },
+  { label: "删除", handler: delHandler },
+];
 
 const status = useMouseShapeStatus(shape);
 const [style] = useAnimationMouseStyle({
@@ -131,7 +200,9 @@ const [style] = useAnimationMouseStyle({
 
 const isDrawIng = computed(
   () =>
-    props.addMode && props.data.lines.indexOf(props.line) === props.data.lines.length - 1
+    (props.addMode &&
+      props.data.lines.indexOf(props.line) === props.data.lines.length - 1) ||
+    props.drawMode
 );
 
 const addPoint = (pos: Pos) => {

+ 7 - 3
src/core/components/line/single-point.vue

@@ -1,16 +1,17 @@
 <template>
   <template v-for="(point, ndx) in points" :key="point.id">
+    <!-- <v-text :text="point.id" :x="point.x" :y="point.y" :fontSize="18" /> -->
     <EditPoint
       v-if="showEditPoint"
       :ref="(r: any) => shapes[ndx] = r?.shape"
       :size="line.strokeWidth"
       :points="points"
       :opacity="1"
-      :drawIng="ndx === 1 && isDrawIng"
+      :drawIng="isDrawIng ? (drawMode ? point === drawMode : ndx === 1) : false"
       :ndx="ndx"
       :closed="false"
       :id="line.id"
-      :disable="addMode"
+      :disable="addMode || !!drawMode"
       :color="isDrawIng ? themeColor : style.stroke"
       @dragstart="dragstartHandler([point.id])"
       @update:position="(p) => emit('updatePoint', { ...point, ...p })"
@@ -37,6 +38,7 @@ const props = defineProps<{
   addMode?: boolean;
   data: LineData;
   dragPointIds?: string[];
+  drawMode?: LineData["points"][number];
 }>();
 const shapes = ref<DC<Circle>[]>([]);
 
@@ -64,7 +66,9 @@ const isPointHovera = useShapeIsHover(computed(() => shapes.value[0]))[0];
 const isPointHoverb = useShapeIsHover(computed(() => shapes.value[1]))[0];
 const isDrawIng = computed(
   () =>
-    props.addMode && props.data.lines.indexOf(props.line) === props.data.lines.length - 1
+    (props.addMode &&
+      props.data.lines.indexOf(props.line) === props.data.lines.length - 1) ||
+    !!props.drawMode
 );
 const showEditPoint = computed(
   () =>

+ 1 - 3
src/core/components/line/temp-line.vue

@@ -11,6 +11,7 @@
         :can-edit="!initData && !operMode.mulSelection"
         :dragPointIds="dragPointIds"
         @add-point="(p) => addPointHandler(p, item)"
+        @add-line="(l) => addLineHandler(l)"
         @update-point="updatePointHandler"
         @update-before="updateBeforeHandler"
         @update="updateHandler"
@@ -94,9 +95,6 @@ const delPointHandler = (p: LineData["points"][0]) => {
 const addPointHandler = (p: LineData["points"][0], l: LineData["lines"][0]) => {
   props.data.points.push(p);
   ctx.add.points[p.id] = p;
-  delLineHandler(l);
-  addLineHandler({ ...l, a: p.id, id: onlyId() });
-  addLineHandler({ ...l, b: p.id, id: onlyId() });
 };
 
 const updatePointHandler = (p: LineData["points"][0]) => {

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

@@ -69,7 +69,7 @@ export const installGlobalVar = <T>(
 
 export const useRunHook = installGlobalVar(() => {
   const instance = getCurrentInstance()
-  return <R, T extends () => R>(hook: T): R => {
+  return <R, T extends () => R = () => R>(hook: T): R => {
     const back = getInstance
     getInstance = () => instance
     const result = hook()

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

@@ -140,7 +140,6 @@ export const useInteractiveAreas = ({
     enter && enter();
 
     const upHandler = (ev: MouseEvent) => {
-      console.log('upHandler')
       if (downed) {
         mode.del(Mode.draging);
       }

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

@@ -47,7 +47,7 @@ export type PropertyDescribes = Record<
     label: string;
     default?: PropertyValue<PropertyType>;
     'layout-type'?: string;
-    isChange: boolean,
+    isChange?: boolean,
     props?: Partial<PropertyProps<PropertyType>>;
     value?: PropertyValue<PropertyType>;
     onChange?: () => void,

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

@@ -1,11 +1,4 @@
-<template>
-  <ShapeComponent
-    :data="(item as any)"
-    v-for="item in tempItems"
-    :key="item.id"
-    addMode
-  />
-</template>
+<template></template>
 
 <script setup lang="ts">
 import { onUnmounted } from "vue";
@@ -18,7 +11,6 @@ const drawStore = useDrawIngData();
 const tempItems = components[type].useDraw
   ? components[type].useDraw()
   : useInteractiveAdd(props.type);
-const ShapeComponent = components[type].TempComponent || components[type].Component;
 drawStore[type] = (tempItems || []) as any;
 
 onUnmounted(() => delete drawStore[type]);

+ 1 - 1
src/core/renderer/draw-shape.vue

@@ -22,7 +22,7 @@ watch(
   () => ({ canSnap: !props.temp && !props.item.ref, item: props.item }),
   ({ canSnap, item }) => {
     infos.forEach(customSnapInfos.remove);
-    if (!canSnap) return;
+    if (!canSnap || !component.value.getSnapInfos) return;
     infos = component.value.getSnapInfos(item as any);
     infos.forEach(customSnapInfos.add);
   },

+ 15 - 0
src/core/renderer/group-temp.vue

@@ -0,0 +1,15 @@
+<template>
+  <ShapeComponent :data="item" addMode :key="item.id" v-for="item in items" />
+</template>
+
+<script setup lang="ts">
+import { ShapeType, components } from "../components";
+import { computed } from "vue";
+import { useDrawIngData } from "../hook/use-draw";
+
+const props = defineProps<{ type: ShapeType }>();
+const store = useDrawIngData();
+const type = props.type as "arrow";
+const ShapeComponent = components[type].TempComponent || components[type].Component;
+const items = computed(() => store[type]);
+</script>

+ 4 - 2
src/core/renderer/renderer.vue

@@ -43,8 +43,9 @@
         <!--	临时组,提供临时绘画,以及高频率渲染	-->
         <v-layer :config="viewerConfig" id="temp">
           <template v-if="mode.include(Mode.draw)">
-            <TempShapeGroup v-for="type in types" :type="type" :key="type" />
+            <DrawShapeGroup v-for="type in types" :type="type" :key="type" />
           </template>
+          <TempShapeGroup v-for="type in types" :type="type" :key="type" />
         </v-layer>
         <v-layer id="helper">
           <!-- <ActiveBoxs /> -->
@@ -62,9 +63,10 @@
 
 <script lang="ts" setup>
 import ShapeGroup from "./group.vue";
+import TempShapeGroup from "./group-temp.vue";
 import Back from "../helper/back.vue";
 import Border from "../helper/facade.vue";
-import TempShapeGroup from "./draw-group.vue";
+import DrawShapeGroup from "./draw-group.vue";
 import SnapLines from "../helper/snap-lines.vue";
 import BackGrid from "../helper/back-grid.vue";
 import SplitLine from "../helper/split-line.vue";

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

@@ -82,7 +82,7 @@ export const getHeaderActions = (draw: Draw) => {
           }
         })
       },
-      text: "显示隐藏项",
+      text: "显示全部",
       icon: "a-visible",
     }),
     expose: reactive({

+ 4 - 2
src/example/components/slide/actions.ts

@@ -73,7 +73,7 @@ export const imp: MenuItem = {
       name: "场景",
       handler: async (draw: Draw) => {
         const aiData = await selectAI();
-        drawPlatformResource(aiData, draw);
+        await drawPlatformResource(aiData, draw);
       },
     },
     {
@@ -85,7 +85,9 @@ export const imp: MenuItem = {
         try {
           files = await selectFile(false, ".png, .jpg");
         } catch (e: any) {
-          ElMessage.error(e.message);
+          if (e.message) {
+            ElMessage.error(e.message);
+          }
           return;
         }
         if (files[0].size >= 100 * 1024 * 1024) {

+ 0 - 1
src/example/components/slide/menu.ts

@@ -6,7 +6,6 @@ export type MenuItem = {
   name: string;
   value: string;
   type?: string
-  active?: boolean
   children?: MenuItem[];
   mount?: any,
   payload?: any;

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

@@ -12,7 +12,11 @@
       <div class="type-children" v-for="typeChildren in group.children">
         <h3 v-if="typeChildren.name">{{ typeChildren.name }}</h3>
         <div class="icon-items">
-          <div v-for="item in typeChildren.children" @click="drawIcon(item)">
+          <div
+            v-for="item in typeChildren.children"
+            @click="drawIcon(item)"
+            :class="{ active: activeName === item.name }"
+          >
             <Icon :name="item.icon" size="32px" :color="item.color" />
             <span>{{ item.name }}</span>
           </div>
@@ -26,12 +30,19 @@
 <script lang="ts" setup>
 import { computed, ref } from "vue";
 import { ElCollapse, ElCollapseItem, ElEmpty } from "element-plus";
-import { Draw } from "../../../components/container/use-draw.ts";
-import { iconGroups as groups, IconItem } from "../../../constant";
 import { defaultStyle, getIconStyle } from "@/core/components/icon/index.ts";
+import { Draw } from "../container/use-draw";
+import { IconGroup, IconItem } from "@/example/constant";
 
-const props = defineProps<{ draw: Draw }>();
-const emit = defineEmits<{ (e: "exit"): void }>();
+const props = defineProps<{
+  draw: Draw;
+  groups: IconGroup[];
+  cref: (expose: any) => void;
+}>();
+const emit = defineEmits<{
+  (e: "exit"): void;
+  (e: "active", attr: { icon: string; name: string }): void;
+}>();
 
 const drawIcon = async (item: IconItem) => {
   const url = `./icons/${item.icon}.svg`;
@@ -52,12 +63,14 @@ const drawIcon = async (item: IconItem) => {
     true
   );
   emit("exit");
+  emit("active", item);
 };
+const activeName = computed(() => (props.draw.presetAdd?.preset as any)?.name);
 
-const activeGroups = ref(groups.map((item) => item.name));
+const activeGroups = computed(() => props.groups.map((item) => item.name));
 const keyword = ref("");
 const searchGroups = computed(() => {
-  return groups
+  return props.groups
     .map((typeChildren) => {
       const filterTypeChildren = typeChildren.children
         .map((type) => {
@@ -77,6 +90,14 @@ const searchGroups = computed(() => {
     })
     .filter((typeChildren) => typeChildren.children.length > 0);
 });
+
+props.cref &&
+  props.cref({
+    selectDefault() {
+      drawIcon(searchGroups.value[0].children[0].children[0]);
+      console.log("default");
+    },
+  });
 </script>
 
 <style lang="scss" scoped>
@@ -103,8 +124,11 @@ const searchGroups = computed(() => {
     margin-bottom: 20px;
   }
 }
-
+.type-children {
+  margin-left: 10px;
+}
 .icon-items {
+  margin-left: -10px;
   display: flex;
   flex-wrap: wrap;
 
@@ -114,8 +138,13 @@ const searchGroups = computed(() => {
     display: flex;
     flex-direction: column;
     align-items: center;
+    border-radius: 4px;
     justify-content: space-between;
     margin-bottom: 20px;
+
+    &.active {
+      background: var(--el-color-primary-light-7);
+    }
     span {
       margin-top: 10px;
     }

+ 46 - 10
src/example/components/slide/slide-item.vue

@@ -1,21 +1,35 @@
 <template>
   <el-sub-menu
-    :index="data.value || data.name"
+    :index="index"
     v-if="data.children?.length"
     :class="{ 'is-active': active }"
+    @click="clickHandler"
   >
     <template #title>
-      <div class="menu-layout">
-        <Icon :name="data.icon" size="24px" />
-        <span>{{ data.name }}</span>
+      <div
+        class="menu-layout"
+        @mouseenter="hover?.enterHandler(props.data)"
+        @mouseleave="hover?.leaveHandler(props.data)"
+      >
+        <Icon :name="showAttr.icon" size="24px" />
+        <span>{{ showAttr.name }}</span>
       </div>
     </template>
-    <SlideItem v-for="item in data.children" :data="item" :active="item.active" />
+    <SlideItem v-for="item in data.children" :data="item" />
   </el-sub-menu>
-  <el-menu-item v-else :index="data.value || data.name" :class="{ 'is-active': active }">
-    <div class="menu-layout">
-      <Icon :name="data.icon" size="22px" />
-      <span>{{ data.name }}</span>
+  <el-menu-item
+    v-else
+    :index="index"
+    :class="{ 'is-active': active }"
+    @click="clickHandler"
+  >
+    <div
+      class="menu-layout"
+      @mouseenter="hover?.enterHandler(props.data)"
+      @mouseleave="hover?.leaveHandler(props.data)"
+    >
+      <Icon :name="showAttr.icon" size="22px" />
+      <span>{{ showAttr.name }}</span>
     </div>
   </el-menu-item>
 </template>
@@ -23,7 +37,29 @@
 <script lang="ts" setup>
 import { ElSubMenu, ElMenuItem } from "element-plus";
 import { MenuItem } from "./menu";
-defineProps<{ data: MenuItem; active?: boolean }>();
+import { computed } from "vue";
+import { hoverManage } from "@/utils/shared";
+
+const props = defineProps<{
+  data: MenuItem;
+  viewData?: Pick<MenuItem, "icon" | "name">;
+  enterHandler?: (data: MenuItem) => (data: MenuItem) => void;
+}>();
+const emit = defineEmits<{
+  (e: "update:activeIndex", value: string): void;
+}>();
+
+const index = computed(() => props.data.value || props.data.name);
+const showAttr = computed(() => props.viewData || props.data);
+const active = computed(() => false);
+const clickHandler = () => {
+  if (props.data.children?.length) {
+    const item = props.data.children[0];
+    emit("update:activeIndex", item.value || item.name);
+  }
+};
+
+const hover = props.enterHandler && hoverManage(props.enterHandler);
 </script>
 
 <style lang="scss">

+ 77 - 8
src/example/components/slide/slide.vue

@@ -9,15 +9,28 @@
       :popper-class="childType || 'slide-menu-poper'"
       @open="openHandler"
     >
-      <SlideItem v-for="menu in menus" :data="menu" :active="menu.active" />
+      <SlideItem
+        v-for="menu in menus"
+        :data="menu"
+        :viewData="viewMap.get(menu)"
+        @update:activeIndex="selectHandler"
+        :enter-handler="enterItem"
+      />
     </el-menu>
 
-    <div class="ext">
+    <div
+      class="ext"
+      @click.stop
+      @mouseenter="hover.enterHandler(hoverMenu.value.item)"
+      @mouseleave="hover.leaveHandler(hoverMenu.value.item)"
+      v-if="hoverMenu.value?.item.mount"
+    >
       <component
-        v-if="activeMenu?.mount"
-        :is="activeMenu?.mount"
+        :is="hoverMenu.value.item.mount"
+        :cref="(r: any) => hoverMenu.value && (hoverMenu.value.ref = r)"
         :draw="draw"
-        @exit="active = undefined"
+        @active="hoverMenu.value.onactive"
+        @exit="hover.immediatelyLeave(hoverMenu.value)"
       />
     </div>
   </div>
@@ -27,8 +40,10 @@
 import { ElMenu } from "element-plus";
 import { getItem, getValue, MenuItem } from "./menu.ts";
 import SlideItem from "./slide-item.vue";
-import { computed, nextTick, ref, watch } from "vue";
+import { computed, nextTick, reactive, ref, watch, watchEffect } from "vue";
 import { Draw } from "../container/use-draw.ts";
+import { stackVar } from "@/core/hook/use-global-vars.ts";
+import { asyncTimeout, hoverManage } from "@/utils/shared.ts";
 
 const props = defineProps<{ menus: MenuItem[]; draw: Draw }>();
 const active = ref<string>();
@@ -36,13 +51,62 @@ const activeMenu = computed(() =>
   active.value === undefined ? null : getItem(active.value, props.menus)
 );
 
+type ShowAttr = Pick<MenuItem, "icon" | "name">;
+const hoverMenu = stackVar<{
+  item: MenuItem;
+  onactive: (s?: ShowAttr) => void;
+  ref: any;
+}>();
+const enterItem = (item: MenuItem) => {
+  const svar = {
+    item,
+    ref: undefined,
+    async onactive(attr?: ShowAttr) {
+      await asyncTimeout(0);
+      const index = item.value || item.name;
+      active.value = index;
+      await asyncTimeout(16);
+      viewMap.set(item, attr || item);
+      const preset = props.draw.presetAdd;
+      const stopWatch = watchEffect(() => {
+        if (active.value !== index || preset !== props.draw.presetAdd) {
+          if (active.value === index) {
+            active.value = undefined;
+          }
+          viewMap.set(item, item);
+          nextTick(() => stopWatch());
+        }
+      });
+    },
+  };
+  const cleanup = hoverMenu.push(svar);
+  return cleanup;
+};
+const hover = hoverManage(enterItem);
+
+const viewMap = reactive(new WeakMap<MenuItem, ShowAttr>());
+watch(active, (a) => {
+  for (const menu of props.menus) {
+    const index = menu.value || menu.name;
+    if (!menu.children?.length || !active.value || active.value === index) {
+      viewMap.set(menu, menu);
+    } else {
+      const activeItem = getItem(active.value, menu.children);
+      viewMap.set(menu, activeItem || menu);
+    }
+  }
+});
+
 const selectHandler = async (val: string) => {
   if (active.value) {
+    console.log(val, active.value);
     props.draw.quitDrawShape();
+    console.log(active.value);
     if (active.value === val) {
       active.value = undefined;
       return;
     }
+    console.log(val, active.value);
   }
   await nextTick();
   active.value = val;
@@ -50,10 +114,15 @@ const selectHandler = async (val: string) => {
   const menu = activeMenu.value;
   if (!menu) return;
   if (menu.handler) {
-    menu.handler(props.draw);
-    nextTick(() => (active.value = undefined));
+    try {
+      await menu.handler(props.draw);
+    } finally {
+      nextTick(() => (active.value = undefined));
+    }
   } else if (menu.payload) {
     props.draw.enterDrawShape(menu.payload.type, menu.payload.preset, menu.single);
+  } else if (menu.mount && hoverMenu.value.ref) {
+    hoverMenu.value.ref.selectDefault();
   }
 };
 

+ 4 - 2
src/example/constant.ts

@@ -1,11 +1,13 @@
+import { LineIconData } from "@/core/components/line-icon";
+
 export type IconItem = {
   wall?: boolean;
   icon: string;
   name: string;
   color?: string;
-  parse?: { fill?: string; stroke?: string; type?: string };
+  parse?: { fill?: string; stroke?: string; type?: LineIconData['type'] };
 };
-type IconGroup = {
+export type IconGroup = {
   name: string;
   children: {
     name: string;

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

@@ -29,6 +29,7 @@ export const selectAI = () =>
     };
     props.cancel = () => {
       reject("cancel");
+      console.log('cancel')
       props.visiable = false
     };
   });

+ 2 - 0
src/example/dialog/dialog.vue

@@ -60,6 +60,8 @@ watchEffect((onCleanup) => {
       () => {
         if (props.visiable) {
           showContents.value[i] = true;
+        } else {
+          props.cancel();
         }
       }
     );

+ 4 - 3
src/example/fuse/views/overview/slide.vue

@@ -4,7 +4,6 @@
 
 <script lang="ts" setup>
 import Slide from "../../../components/slide/slide.vue";
-import Icons from "./slide-icons.vue";
 import { v4 as uuid } from "uuid";
 import {
   draw as drawAction,
@@ -13,13 +12,15 @@ import {
   imp,
 } from "../../../components/slide/actions.ts";
 import { useDraw } from "../../../components/container/use-draw.ts";
-import { markRaw, reactive } from "vue";
+import { h, reactive } from "vue";
+import SlideIcons from "../../../components/slide/slide-icons.vue";
+import { iconGroups } from "../../../constant";
 
 const legend = {
   icon: "legend",
   name: "图例",
   value: uuid(),
-  mount: markRaw(Icons),
+  mount: (props: any) => h(SlideIcons, { ...props, groups: iconGroups }),
 };
 
 const draw = useDraw();

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

@@ -42,7 +42,6 @@ const drawIcon = async (url: string, name: string, item: any) => {
   props.draw.enterDrawShape(
     "icon",
     {
-      url,
       name,
       ...defaultStyle,
       ...(item.parse || {}),

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

@@ -14,13 +14,14 @@ import {
   draw as drawMenuRaw,
 } from "../../../components/slide/actions.ts";
 import { useDraw } from "../../../components/container/use-draw.ts";
-import { computed, markRaw, nextTick, reactive, watch } from "vue";
+import { computed, h, nextTick, reactive, watch } from "vue";
 import { tabulationData } from "../../store";
 import { genTabulationData } from "./gen-tab.ts";
 import { defaultLayer } from "@/constant/index.ts";
 import { copy } from "@/utils/shared.ts";
 import { v4 as uuid } from "uuid";
-import Icons from "./slide-icons.vue";
+import SlideIcons from "@/example/components/slide/slide-icons.vue";
+import { iconGroups } from "@/example/constant.ts";
 
 const draw = useDraw();
 const paper = reactive({
@@ -42,7 +43,8 @@ const menus = reactive([
     icon: "legend",
     name: "图例",
     value: uuid(),
-    mount: markRaw(Icons),
+    mount: (props: any) =>
+      h(SlideIcons, { ...props, groups: [iconGroups[iconGroups.length - 1]] }),
   },
   {
     ...text,

+ 54 - 2
src/example/platform/platform-draw.ts

@@ -12,10 +12,11 @@ import { defaultStyle as textDefaultStyle } from "@/core/components/text";
 import { Transform } from "konva/lib/Util";
 import { ElMessage } from "element-plus";
 import { ImageData } from "@/core/components/image";
-import { Pos, Size } from "@/utils/math";
+import { lineLen, Pos, Size } from "@/utils/math";
 import { watchEffect } from "vue";
 import { TextData } from "@/core/components/text";
 import { SelectSceneData } from "../dialog/ai";
+import { getLineIconEndpoints, LineIconData } from "@/core/components/line-icon";
 
 const getSizePlaceBoxImage = ({ width, height }: Size) => {
   const borderWidth = 10;
@@ -152,7 +153,7 @@ const getTaggingShapes = async (taggings: SceneResource["taggings"]) => {
         .catch(() => {})
     );
   }
-  await Promise.all(reqs)
+  await Promise.all(reqs);
   return {
     texts,
     images,
@@ -160,6 +161,55 @@ const getTaggingShapes = async (taggings: SceneResource["taggings"]) => {
   };
 };
 
+const getWallTaggingShapes = async (
+  taggings: SceneResource["wallTaggings"],
+  lineData: LineData
+) => {
+  const lineIcons: LineIconData[] = [];
+  const now = Date.now();
+  const reqs: Promise<any>[] = [];
+
+  for (let ndx = 0; ndx < taggings.length; ndx++) {
+    const item = taggings[ndx];
+    const points = item.pointIds.map(
+      (key) => lineData.points.find((p: any) => p.key === key)!
+    );
+    const line = lineData.lines.find(
+      (line) => line.a === points[0].id && line.b === points[1].id
+    )!;
+    if (!line) {
+      break;
+    }
+
+    const shape: LineIconData = {
+      ...getBaseItem(),
+      name: item.name,
+      createTime: now + ndx,
+      url: item.url,
+      zIndex: 1,
+      startLen: item.startLen,
+      endLen: item.endLen,
+      type: 'align-bottom',
+      openSide: item.openSide,
+      height: line.strokeWidth,
+      lineId: line.id,
+    };
+    const iconPoints = getLineIconEndpoints(points, shape)
+    reqs.push(
+      getIconStyle(item.url, lineLen(iconPoints[0], iconPoints[1]), 1000000)
+        .then((style) => {
+          if (shape.type === 'align-bottom') {
+            shape.height = style.height
+          }
+          lineIcons.push({ ...shape, fill: style.fill, stroke: style.stroke });
+        })
+        .catch(() => {})
+    );
+  }
+  await Promise.all(reqs);
+  return lineIcons;
+};
+
 const getDrawResourceOffset = (
   draw: Draw,
   bound: ReturnType<typeof genBound>,
@@ -224,6 +274,7 @@ const drawSceneResource = async (resource: SceneResource, draw: Draw) => {
       bound.update({ x: shape.mat[4], y: shape.mat[5] });
     }
   });
+  const lineIcons = await getWallTaggingShapes(resource.wallTaggings, geo);
 
   if (oldGeo) {
     oldGeo.points = oldGeo.points.concat(geo.points);
@@ -234,6 +285,7 @@ const drawSceneResource = async (resource: SceneResource, draw: Draw) => {
     draw.store.addItem("line", geo);
   }
 
+  draw.store.addItems("lineIcon", lineIcons);
   draw.store.addItems("icon", icons);
   draw.store.addItems("text", texts);
   draw.store.addItems("image", images);

+ 9 - 1
src/example/platform/platform-resource.ts

@@ -2,6 +2,7 @@ import { Pos, Size } from "@/utils/math";
 import { getResource as getCloudResource } from "./resource-cloud";
 import { getResource as getSWKKResource } from "./resource-swkk";
 import { getResource as getFuseResource } from "./resource-fuse";
+import { LineIconData } from "@/core/components/line-icon";
 
 export enum SCENE_TYPE {
   fuse = "fuse",
@@ -27,7 +28,7 @@ export type CoverLine = {
     z_min?: number;
     z_max?: number;
   };
-  geos: Pos[][];
+  geos: (Pos & {key: string})[][];
 };
 
 export type TaggingInfo = {
@@ -41,7 +42,14 @@ export type TaggingInfo = {
   fixed?: boolean;
 };
 
+export type WallTaggingInfo = {
+  url: string,
+  pointIds: string[]
+  name?: string
+} & Pick<LineIconData, 'startLen' | 'endLen' | 'type' | 'openSide'>
+
 export type SceneResource = {
+  wallTaggings: WallTaggingInfo[]
   taggings: TaggingInfo[];
   cover?: CoverLine;
   compass?: number;

+ 73 - 24
src/example/platform/resource-swkk.ts

@@ -6,8 +6,9 @@ import {
   Scene,
   SceneResource,
   TaggingInfo,
+  WallTaggingInfo,
 } from "./platform-resource";
-import { lineLen, Pos } from "@/utils/math";
+import { lineLen, lineVector, Pos, zeroEq } from "@/utils/math";
 import { aiIconMap, getIconItem, styleIconMap } from "../constant";
 import { MathUtils, Object3D, Quaternion, Vector3 } from "three";
 import { extractConnectedSegments } from "@/utils/polygon";
@@ -83,7 +84,7 @@ export const getCoverLine = async (
     const geos = extractConnectedSegments(floor.segment).map((geo) => {
       return geo.map((id) => {
         const p = floor["vertex-xy"].find((item: any) => item.id === id);
-        return { x: p.x * scale, y: -p.y * scale } as Pos;
+        return { x: p.x * scale, y: -p.y * scale, key: id.toString() };
       });
     });
 
@@ -313,10 +314,47 @@ export const getAITaggingInfos = async (
 export const getWallAITaggingInfos = async (
   scene: Scene,
   subgroup: any,
-  scale: number
+  scale: number,
+  cover: CoverLine
 ) => {
   const { floorplan } = await fetchResource(scene);
-  const infos: TaggingInfo[] = [];
+  const infos: WallTaggingInfo[] = [];
+
+  console.log(cover.geos);
+  const getCoverLine = (wall: Pos[]) => {
+    type Info = { p: Pos & { key: string }; len: number };
+    const infos: { start: Info; end: Info; inv: boolean }[] = [];
+
+    for (let i = 0; i < cover.geos.length; i++) {
+      const geo = cover.geos[i];
+      for (let j = 0; j < geo.length - 1; j++) {
+        const len00 = lineLen(geo[j], wall[0]);
+        const len10 = lineLen(geo[j + 1], wall[0]);
+        const len01 = lineLen(geo[j], wall[1]);
+        const len11 = lineLen(geo[j + 1], wall[1]);
+        let start: Info, end: Info, inv: boolean;
+
+        if (len00 + len11 < len10 + len01) {
+          inv = false;
+          start = { p: geo[j], len: len00 };
+          end = { p: geo[j + 1], len: len11 };
+        } else {
+          inv = true;
+          start = { p: geo[j + 1], len: len10 };
+          end = { p: geo[j], len: len01 };
+        }
+        if (zeroEq(start.len) && zeroEq(end.len)) {
+          return { points: [start.p, end.p], inv };
+        }
+        infos.push({ start, end, inv });
+      }
+    }
+    const sortInfos = [...infos].sort((a, b) => (a.start.len + a.end.len) - (b.start.len + b.end.len));
+    const target = sortInfos[0];
+    if (target.start.len + target.end.len < 10) {
+      return { points: [target.start.p, target.end.p], inv: target.inv };
+    }
+  };
 
   for (const floor of floorplan.floors) {
     if (floor.subgroup !== subgroup) continue;
@@ -333,27 +371,30 @@ export const getWallAITaggingInfos = async (
       const isWall = itemIcon && "wall" in itemIcon ? itemIcon.wall : false;
       if (!isWall) continue;
 
+      const cadWall = [
+        floor.points[floor.walls[item.parent].start],
+        floor.points[floor.walls[item.parent].end],
+      ].map((p) => ({
+        x: p.x * scale,
+        y: -p.y * scale,
+      }));
+      const wall = getCoverLine(cadWall);
       const line = [item.startPoint, item.endPoint].map((p) => ({
         x: p.x * scale,
         y: -p.y * scale,
       }));
-
-      const size = {
-        width: lineLen(line[0], line[1]),
-        height: item.len * scale,
-      };
-      const url = `./icons/${icon ? icon : "circle"}.svg`;
-      try {
-        const svg = parseSvgContent(await getSvgContent(url));
-        size.height = (svg.height / svg.width) * size.width;
-      } catch {}
-
+      if (!wall) {
+        continue;
+      }
+      const points = wall.inv ? wall.points.reverse() : wall.points
       infos.push({
         name: name,
-        mat: getLineIconMat(line, size, item.openSide).m,
-        size,
-        url,
-        fixed: true,
+        url: `./icons/${icon ? icon : "circle"}.svg`,
+        startLen: lineLen(points[0], line[0]),
+        endLen: lineLen(points[0], line[1]),
+        openSide: item.openSide,
+        type: itemIcon?.parse?.type || "full",
+        pointIds: points.map(item => item.key),
       });
     }
   }
@@ -373,6 +414,7 @@ export const getResource = async ({
   );
   const key = floor.subgroup;
   const taggings: TaggingInfo[] = [];
+  const wallTaggings: WallTaggingInfo[] = [];
   let coverLine: CoverLine;
 
   const compass = await getCompass(scene);
@@ -381,10 +423,16 @@ export const getResource = async ({
     getCompass(scene),
     getCoverLine(scene, key, scale)
       .then((lines) => (coverLine = lines))
-      .then(({ bound }) => {
-        getAITaggingInfos(scene, key, bound).then((ts) => taggings.push(...ts));
-      }),
-    getWallAITaggingInfos(scene, key, scale).then((ts) => taggings.push(...ts)),
+      .then((cover) =>
+        Promise.all([
+          getWallAITaggingInfos(scene, key, scale, cover).then((ts) =>
+            wallTaggings.push(...ts)
+          ),
+          getAITaggingInfos(scene, key, cover.bound).then((ts) =>
+            taggings.push(...ts)
+          ),
+        ])
+      ),
   ];
 
   if (syncs.includes("hot")) {
@@ -401,7 +449,8 @@ export const getResource = async ({
   await Promise.all(reqs);
 
   return {
-    taggings,
+    taggings: taggings,
+    wallTaggings,
     cover: coverLine!,
     compass: compass,
   } as SceneResource;

+ 4 - 0
src/utils/dom.ts

@@ -61,6 +61,7 @@ export const selectFile = (multiple = false, accept = "") =>
     const accepts = accept.split(',').flatMap(s => [s.trim().toLowerCase(), s.trim().toUpperCase()])
     $fileInput.accept = accepts.join(',');
     $fileInput.onchange = () => {
+      console.log('0.0')
       $fileInput.accept = "";
       $fileInput.multiple = false;
       const files = [...$fileInput.files!]
@@ -73,6 +74,9 @@ export const selectFile = (multiple = false, accept = "") =>
       resolve(Array.from($fileInput.files!));
       $fileInput.value = "";
     };
+    $fileInput.oncancel = () => {
+      reject(`取消选择`)
+    }
     $fileInput.click();
   });
 

+ 28 - 0
src/utils/shared.ts

@@ -447,3 +447,31 @@ export const genCache = <T, R>(
     return result;
   }
 };
+
+
+export const hoverManage = <T, K>(hoverHandler: (data: T) => (data: K) => void) => {
+  let enter = false;
+  let timeout: any;
+  let leave: undefined | ((data: K) => void);
+  const enterHandler = (data: T) => {
+    clearTimeout(timeout);
+    if (!enter) {
+      enter = true;
+      leave = hoverHandler(data);
+    }
+  };
+  const immediatelyLeave = (data: K) => {
+      enter = false;
+      leave && leave(data);
+  }
+  const leaveHandler = (data: K) => {
+    timeout = setTimeout(() => {
+      immediatelyLeave(data)
+    }, 160);
+  };
+  return {
+    enterHandler,
+    leaveHandler,
+    immediatelyLeave
+  }
+}