Browse Source

fix: 制作联通需求

bill 1 year ago
parent
commit
d4d57ea278
34 changed files with 1470 additions and 384 deletions
  1. 53 0
      src/app/liantong/icon.ts
  2. 74 0
      src/app/liantong/index.ts
  3. 5 0
      src/board/env.ts
  4. 54 16
      src/board/packages/container.ts
  5. 3 2
      src/board/packages/entity.ts
  6. 1 0
      src/board/packages/index.ts
  7. 13 0
      src/board/packages/poi/edit-poi.ts
  8. 3 0
      src/board/packages/poi/index.ts
  9. 35 0
      src/board/packages/poi/poi.ts
  10. 10 0
      src/board/packages/poi/temp.svg
  11. 38 0
      src/board/packages/poi/types.ts
  12. 70 56
      src/board/packages/whole-line/editable/edit-whole-line.ts
  13. 0 1
      src/board/packages/whole-line/service/getWholeLinePointMerge.ts
  14. 117 69
      src/board/packages/whole-line/service/whole-line-base.ts
  15. 69 37
      src/board/packages/whole-line/service/whole-line-edit.ts
  16. 78 15
      src/board/packages/whole-line/service/whole-line-helper.ts
  17. 15 11
      src/board/packages/whole-line/service/whole-line-mouse.ts
  18. 247 64
      src/board/packages/whole-line/service/whole-line-tear-merge.ts
  19. 10 2
      src/board/packages/whole-line/style.ts
  20. 230 0
      src/board/plugins/camera-plugin.ts
  21. 1 0
      src/board/plugins/index.ts
  22. 9 11
      src/board/register.ts
  23. 70 0
      src/board/shared/act.ts
  24. 6 3
      src/board/shared/entity-utils.ts
  25. 1 0
      src/board/shared/index.ts
  26. 40 10
      src/board/shared/math.ts
  27. 35 10
      src/board/shared/shape-mose.ts
  28. 26 0
      src/board/shared/util.ts
  29. 1 1
      src/board/type.d.ts
  30. 39 17
      src/components/query-board/index.vue
  31. 8 0
      src/components/query-board/storeData.json
  32. 86 57
      src/components/query-board/streData-merge.json
  33. 4 2
      tsconfig.node.json
  34. 19 0
      vite.config.ts

File diff suppressed because it is too large
+ 53 - 0
src/app/liantong/icon.ts


+ 74 - 0
src/app/liantong/index.ts

@@ -0,0 +1,74 @@
+import { Group } from "konva/lib/Group";
+import {
+  CameraQueryPlugin,
+  EditPoi,
+  EditWholeLine,
+  Entity,
+  HistoryPlugin,
+  PoiAttrib,
+  createLineByDire,
+  generateId,
+  getDireDist,
+  register,
+} from "../../board";
+import "./icon";
+import { Rect } from "konva/lib/shapes/Rect";
+
+const types = { rooms: EditWholeLine, pois: EditPoi };
+const initBoard = register(types, {
+  bound: new CameraQueryPlugin({ move: true, wheel: true }),
+  history: new HistoryPlugin(),
+});
+
+type Store = {
+  rooms: EditWholeLine["attrib"] | EditWholeLine["attrib"][];
+  pois: EditPoi["attrib"] | EditPoi["attrib"][];
+};
+
+export type CopyProps = {
+  entity: Entity;
+  count?: number;
+  dire?: number[];
+};
+export const createBoard = (dom: HTMLDivElement, store: Store) => {
+  const board = initBoard(dom, store);
+  const getRoom = () => board.tree.entrys.rooms[0] as EditWholeLine;
+  return {
+    ...board,
+    bus: board.tree.bus,
+    get room() {
+      return getRoom();
+    },
+    copy(props: CopyProps) {
+      const count = props.count || 1;
+      const dire = props.dire || [1, 0];
+      const group = props.entity.shape as Group;
+      const shape = group.findOne<Rect>(".rect");
+      const halfSize = shape.getSize();
+      const start = group.getPosition();
+      halfSize.width /= 2;
+      halfSize.height /= 2;
+      const unit = getDireDist([halfSize.width, halfSize.height]);
+      const pois = board.getData().pois as PoiAttrib[];
+      const temp = props.entity.attrib as PoiAttrib;
+
+      for (let i = 0; i < count; i++) {
+        const line = createLineByDire(dire, [start.x, start.y], unit * (i + 1));
+        const newPoiAttrib: PoiAttrib = {
+          id: generateId(pois),
+          x: line[2],
+          y: line[3],
+          type: temp.type,
+        };
+        pois.push(newPoiAttrib);
+      }
+    },
+    del(entity: Entity) {
+      const pois = board.getData().pois as PoiAttrib[];
+      const ndx = pois.findIndex(({ id }) => entity.attrib.id === id);
+      if (~ndx) {
+        pois.splice(ndx, 1);
+      }
+    },
+  };
+};

+ 5 - 0
src/board/env.ts

@@ -1 +1,6 @@
 export const DEV = false;
+export const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
+export const hasTouchEvents =
+  "ontouchstart" in window ||
+  navigator.maxTouchPoints > 0 ||
+  (navigator as any).msMaxTouchPoints > 0;

+ 54 - 16
src/board/packages/container.ts

@@ -16,37 +16,43 @@ export type ContainerProps<
   T extends string = string,
   R extends Attrib = Attrib,
   S extends ShapeType = ShapeType,
-  C extends EntityType<R, S> = EntityType<R, S>
+  C extends EntityType<R, S> = EntityType<R, S>,
+  TYPES extends { [key in T]: C } = { [key in T]: C },
+  DATA extends { [key in T]?: R | R[] } = { [key in T]?: R | R[] }
 > = {
-  types: {
-    [key in T]: C;
-  };
-  data?: { [key in T]?: R | R[] };
+  types: TYPES;
+  data?: DATA;
   dom: HTMLDivElement;
-} & Omit<EntityProps<Attrib & { data: { [key in T]?: R | R[] } }>, "attrib">;
+} & Omit<EntityProps<Attrib & { data: DATA }>, "attrib">;
 
 export class Container<
   T extends string = string,
   R extends Attrib = Attrib,
   S extends ShapeType = ShapeType,
-  C extends EntityType<R, S> = EntityType<R, S>
-> extends Entity<Attrib & { data: { [key in T]?: R | R[] } }, Layer> {
+  C extends EntityType<R, S> = EntityType<R, S>,
+  TYPES extends { [key in T]: C } = { [key in T]: C },
+  DATA extends { [key in T]?: R | R[] } = { [key in T]?: R | R[] },
+  ENTRYS extends { [key in T]: InstanceType<TYPES[key]>[] } = {
+    [key in T]: InstanceType<TYPES[key]>[];
+  }
+> extends Entity<Attrib & { data: DATA }, Layer> {
   bus: Emitter<{
+    active: Entity;
     dataChange: void;
     dataChangeBefore: void;
     dataChangeAfter: void;
   }>;
   tempLayer: Layer;
-  config: ContainerProps<T, R, S, C>;
-  entrys = {} as { [key in T]: InstanceType<C>[] };
+  config: ContainerProps<T, R, S, C, TYPES, DATA>;
+  entrys = {} as ENTRYS;
   stage: Stage;
   incFactorys = {} as {
     [key in T]: IncEntitysFactory<R, InstanceType<C>>;
   };
 
-  constructor(config: ContainerProps<T, R, S, C>) {
+  constructor(config: ContainerProps<T, R, S, C, TYPES, DATA>) {
     for (const key in config.types) {
-      config.data[key] = config.data[key] || [];
+      config.data[key as any] = config.data[key] || [];
     }
     for (const key in config.data) {
       if (!(key in config.types)) {
@@ -87,18 +93,50 @@ export class Container<
   }
 
   initIncFactory() {
-    const types = this.config.types;
+    const types = this.config.types as any;
+    let active: Entity | null = null;
     for (const key in types) {
       this.incFactorys[key] = incEntitysFactoryGenerate(
         types[key],
         this,
-        () => {}
+        (instance) => {
+          if (!this.entrys[key]) {
+            this.entrys[key] = [];
+          }
+          this.entrys[key].push(instance);
+        },
+        (instance) => {
+          if (!instance.actShape?.active) return;
+          const entity = instance as Entity;
+          entity.enableActive((actived) => {
+            console.log(actived);
+            if (actived) {
+              if (active !== entity) {
+                this.bus.emit("active", entity);
+                active = entity;
+              }
+            } else {
+              if (active === entity) {
+                this.bus.emit("active", null);
+                active = null;
+              }
+            }
+          });
+          const destory = entity.destory.bind(entity);
+          entity.destory = () => {
+            if (active === entity) {
+              this.bus.emit("active", null);
+              active = null;
+            }
+            return destory();
+          };
+        }
       );
     }
   }
 
   diffRedraw() {
-    const data = this.attrib.data;
+    const data = this.attrib.data as any;
     const result = {} as {
       [key in T]: ReturnType<IncEntitysFactory<R, InstanceType<C>>>;
     };
@@ -115,7 +153,7 @@ export class Container<
     return watch(
       () => {
         const result = {} as { [key in T]: string | string[] };
-        for (const key in this.attrib.data) {
+        for (const key in this.attrib.data as any) {
           const child = this.attrib.data[key];
           result[key] = Array.isArray(child)
             ? child.map(({ id }) => id)

+ 3 - 2
src/board/packages/entity.ts

@@ -88,7 +88,7 @@ export abstract class Entity<
     this.children.forEach((child) => child.redraw());
   }
 
-  setAttrib(newAttrib: Omit<T, "id">) {
+  setAttrib(newAttrib: Partial<T>) {
     this.attrib = depPartialUpdate(
       { ...newAttrib, id: this.attrib.id } as T,
       this.attrib
@@ -139,11 +139,12 @@ export abstract class Entity<
     if (this.props.reactive) {
       this.destoryReactive = this.initReactive();
     }
+    let raw = { ...this.attrib };
     watch(
       () => this.attrib.id,
       (newId, oldId) => {
         if (newId !== oldId) {
-          console.error("changeId", newId, oldId);
+          console.error("changeId", raw, this.attrib, newId, oldId);
         }
       },
       { flush: "sync" }

+ 1 - 0
src/board/packages/index.ts

@@ -1,3 +1,4 @@
 export * from "./whole-line";
 export * from "./container";
 export * from "./entity";
+export * from "./poi";

+ 13 - 0
src/board/packages/poi/edit-poi.ts

@@ -0,0 +1,13 @@
+import { Poi, PoiAttrib } from "./poi";
+
+export class EditPoi<T extends PoiAttrib = PoiAttrib> extends Poi<T> {
+  mounted(): void {
+    super.enableMouseAct(this.actShape);
+    super.enableDrag({
+      moveHandler: (move) => {
+        this.attrib.x = move[0];
+        this.attrib.y = move[1];
+      },
+    });
+  }
+}

+ 3 - 0
src/board/packages/poi/index.ts

@@ -0,0 +1,3 @@
+export * from "./poi";
+export * from "./edit-poi";
+export * from "./types";

+ 35 - 0
src/board/packages/poi/poi.ts

@@ -0,0 +1,35 @@
+import { Attrib, CustomizeShape, ShapeType } from "../../type";
+import { Entity, EntityProps } from "../entity";
+import { defaultPoiShapeFactory, poiTypes } from "./types";
+
+type PoiData = {
+  x: number;
+  y: number;
+  type: string;
+};
+export type PoiAttrib = Attrib & PoiData;
+
+export class Poi<T extends PoiAttrib = PoiAttrib> extends Entity<T, ShapeType> {
+  constructor(props: EntityProps<T>) {
+    props.zIndex = props.zIndex || 5;
+    props.name = props.name || "poi" + props.attrib.id;
+    super(props);
+  }
+
+  actShape: CustomizeShape<PoiData, ShapeType>;
+
+  initShape() {
+    const factory = poiTypes[this.attrib.type] || defaultPoiShapeFactory;
+    this.actShape = factory(this.attrib, this);
+    return this.actShape.shape;
+  }
+
+  diffRedraw() {
+    this.actShape.setData(this.attrib);
+  }
+
+  mounted(): void {
+    super.mounted();
+    this.actShape.common(null);
+  }
+}

File diff suppressed because it is too large
+ 10 - 0
src/board/packages/poi/temp.svg


+ 38 - 0
src/board/packages/poi/types.ts

@@ -0,0 +1,38 @@
+import { Circle } from "konva/lib/shapes/Circle";
+import { CustomizeShapeFactory, ShapeType } from "../../type";
+import { getRealAbsoluteSize } from "../../shared";
+import { PoiAttrib } from "./poi";
+
+export const defaultPoiShapeFactory = () => {
+  const p = new Circle({
+    radius: 3,
+    fill: "red",
+  });
+  const pub = () => {
+    const [size] = getRealAbsoluteSize(p, [1, 1]);
+    p.scale({ x: size, y: size });
+  };
+
+  return {
+    shape: p,
+    setData(data: { x: number; y: number }) {
+      pub();
+      p.position(data);
+    },
+  };
+};
+
+export const poiTypes: {
+  [key in string]: CustomizeShapeFactory<
+    PoiAttrib,
+    { x: number; y: number },
+    ShapeType
+  >;
+} = {};
+
+export const injectPoiType = (
+  type: string,
+  factory: CustomizeShapeFactory<PoiAttrib, { x: number; y: number }, ShapeType>
+) => {
+  poiTypes[type] = factory;
+};

+ 70 - 56
src/board/packages/whole-line/editable/edit-whole-line.ts

@@ -8,16 +8,23 @@ import {
   WholeLinePolygonAttrib,
 } from "../view/";
 import {
+  fixWholeLineConfig,
   getWholeLineLine,
   getWholeLineLinesRawByPoint,
   getWholeLinePolygonPoints,
   getWholeLinePolygonRaw,
   mergeWholeLinePointsByPoint,
-  moveWholeLineLine,
   penWholeLinePoygonsEditWithHelperShapesMouse,
+  repeatMoveWholeLineLine,
   spliceWholeLineLineByPoint,
+  wholeLineDelPolygon,
 } from "../service";
-import { EntityDragHandlers, openEntityDrag } from "../../../shared/shape-mose";
+import {
+  EntityDragHandlers,
+  disableMouse,
+  enableMouse,
+  openEntityDrag,
+} from "../../../shared/shape-mose";
 import { ShapeType } from "../../../type";
 import { getAdsorbPosition } from "../../../shared/adsorb";
 import { getRealAbsoluteSize } from "../../../shared";
@@ -32,7 +39,7 @@ export class EditWholeLine<
   lines: WholeLineLine[];
 
   lineDragHandler: Required<
-    EntityDragHandlers<WholeLineLineAttrib, number[][]>
+    EntityDragHandlers<WholeLineLineAttrib, (move: number[]) => void>
   >;
   pointDragHandler: Required<
     EntityDragHandlers<WholeLinePointAttrib, number[]>
@@ -75,58 +82,82 @@ export class EditWholeLine<
    */
   private endCreatePolygon: () => void;
   createPolygon() {
-    let prePolygonId: string;
+    disableMouse();
+    const prePolygonIds: Set<string> = new Set();
+    let interrupt = false;
+
     this.endCreatePolygon && this.endCreatePolygon();
     this.container.bus.emit("dataChangeBefore");
-    this.endCreatePolygon = penWholeLinePoygonsEditWithHelperShapesMouse(
-      {
-        quotePoint: true,
-        autoClose: false,
-        tree: this.container,
-        adsorbRadius: 10,
-        config: this.attrib,
-        changePolygon: (pid) => {
-          if (prePolygonId && prePolygonId !== pid) {
-            const polygonAttrib = getWholeLinePolygonRaw(
-              this.attrib,
-              prePolygonId
-            );
-            polygonAttrib && this.polygonAfterHandler(polygonAttrib);
-          }
-          prePolygonId = pid;
-          console.log("changP", [...this.attrib.polygons]);
-        },
-        quitHandler: () => {
-          console.log("quit!");
-          this.container.bus.emit("dataChangeAfter");
+
+    const promise = new Promise<string>((resolve) => {
+      this.endCreatePolygon = penWholeLinePoygonsEditWithHelperShapesMouse(
+        {
+          quotePoint: true,
+          autoClose: false,
+          tree: this.container,
+          adsorbRadius: 10,
+          config: this.attrib,
+          changePolygon: (pid) => {
+            if (pid) {
+              prePolygonIds.add(pid);
+            }
+          },
+          quitHandler: () => {
+            this.container.bus.emit("dataChangeAfter");
+            for (const pid of prePolygonIds) {
+              const polygonAttrib = getWholeLinePolygonRaw(this.attrib, pid);
+              if (!polygonAttrib) return;
+
+              if (!interrupt) {
+                this.polygonAfterHandler(polygonAttrib);
+              } else {
+                wholeLineDelPolygon(this.attrib, pid);
+              }
+
+              resolve(interrupt ? "cancel" : "submit");
+            }
+            enableMouse();
+          },
         },
+        this.shape
+      );
+    });
+
+    return {
+      submit: this.endCreatePolygon.bind(this),
+      cancel: () => {
+        interrupt = true;
+        this.endCreatePolygon();
       },
-      this.shape
-    );
+      promise,
+    };
   }
 
   pointAfterHandler(pointAttrib: WholeLinePointAttrib) {
     const merge = mergeWholeLinePointsByPoint(this.attrib, pointAttrib.id);
-    return spliceWholeLineLineByPoint(
+    spliceWholeLineLineByPoint(
       this.attrib,
       merge?.info.main.id || pointAttrib.id
     );
+    fixWholeLineConfig(this.attrib);
   }
 
   lineAfterHandler(lineAttrib: WholeLineLineAttrib) {
-    const points = getWholeLineLine(this.attrib, lineAttrib.id);
-    this.pointAfterHandler(points[0]);
-    this.pointAfterHandler(points[1]);
+    for (let i = 0; i < lineAttrib.pointIds.length; i++) {
+      spliceWholeLineLineByPoint(this.attrib, lineAttrib.pointIds[i]);
+    }
+    fixWholeLineConfig(this.attrib);
   }
 
   polygonAfterHandler(polygonAttrib: WholeLinePolygonAttrib) {
     getWholeLinePolygonPoints(this.attrib, polygonAttrib.id).forEach(
       (attrib) => {
         if (this.attrib.points.includes(attrib)) {
-          console.log(this.pointAfterHandler(attrib));
+          spliceWholeLineLineByPoint(this.attrib, attrib.id);
         }
       }
     );
+    fixWholeLineConfig(this.attrib);
   }
 
   init() {
@@ -135,31 +166,15 @@ export class EditWholeLine<
         this.container.bus.emit("dataChangeBefore");
         this.container.tempDraw();
         const pointAttribs = getWholeLineLine(this.attrib, lineAttrib.id);
-        return pointAttribs.map(({ x, y }) => [x, y]);
-      },
-      moveHandler: (lineAttrib, move, initPositions, ev) => {
-        moveWholeLineLine(this.attrib, lineAttrib, initPositions, move);
-        return;
-        const points = getWholeLineLine(this.attrib, lineAttrib.id);
-        console.log(move);
-        const currentPositions = initPositions.map((pos) => [
-          pos[0] + move[0],
-          pos[1] + move[1],
-        ]);
-
-        this.pointDragHandler.moveHandler(
-          points[0],
-          currentPositions[0],
-          initPositions[0],
-          ev
-        );
-        this.pointDragHandler.moveHandler(
-          points[1],
-          currentPositions[1],
-          initPositions[1],
-          ev
+        return repeatMoveWholeLineLine(
+          this.attrib,
+          lineAttrib,
+          pointAttribs.map(({ x, y }) => [x, y])
         );
       },
+      moveHandler: (_, move, moveHandler) => {
+        moveHandler(move);
+      },
       endHandler: (attrib) => {
         this.lineAfterHandler(attrib);
         this.container.bus.emit("dataChangeAfter");
@@ -205,6 +220,5 @@ export class EditWholeLine<
 
   mounted(): void {
     super.mounted();
-    // this.createPolygon();
   }
 }

+ 0 - 1
src/board/packages/whole-line/service/getWholeLinePointMerge.ts

@@ -1,5 +1,4 @@
 import { WholeLineAttrib } from "../view/whole-line";
-import { getWholeLinePolygonLinesByPoint } from "./whole-line-base";
 
 /**
  * 重复获取点合并结果

+ 117 - 69
src/board/packages/whole-line/service/whole-line-base.ts

@@ -11,8 +11,17 @@ export const generateWholeLineLineId = (config: WholeLineAttrib) =>
 export const generateWholeLinePointId = (config: WholeLineAttrib) =>
   (Math.max(1, ...config.points.map(({ id }) => Number(id))) + 1).toString();
 
-export const generateWholeLinePoygonId = (config: WholeLineAttrib) =>
-  (Math.max(1, ...config.polygons.map(({ id }) => Number(id))) + 1).toString();
+export const generateWholeLinePoygonId = (
+  config: WholeLineAttrib,
+  eIds: string[] = []
+) =>
+  (
+    Math.max(
+      1,
+      ...config.polygons.map(({ id }) => Number(id)),
+      ...eIds.map(Number)
+    ) + 1
+  ).toString();
 
 export const getWholeLinePoint = (config: WholeLineAttrib, id: string) =>
   config.points.find((point) => point.id === id);
@@ -33,12 +42,17 @@ export const getWholeLineLinesRawByPointId = (
 };
 export const getWholeLineLinesRawByPointIds = (
   config: WholeLineAttrib,
-  qpointIds: string[]
+  qpointIds: string[],
+  will = false
 ) => {
-  return config.lines.filter(
-    ({ pointIds }) =>
-      pointIds.includes(qpointIds[0]) && pointIds.includes(qpointIds[1])
-  );
+  return config.lines.filter(({ pointIds }) => {
+    let eq = pointIds.includes(qpointIds[0]) && pointIds.includes(qpointIds[1]);
+    if (eq) return eq;
+    if (will) {
+      eq = pointIds.includes(qpointIds[1]) && pointIds.includes(qpointIds[0]);
+    }
+    return eq;
+  });
 };
 
 export const getWholeLineLinesRawByPoint = (
@@ -274,6 +288,30 @@ export const wholeLineDelLineByPointIds = (
 };
 
 /**
+ * 删除多边形
+ * @param config
+ * @param polygonIds
+ */
+export const wholeLineDelPolygon = (
+  config: WholeLineAttrib,
+  polygonId: string
+) => {
+  const ndx = config.polygons.findIndex(({ id }) => polygonId === id);
+
+  if (~ndx) {
+    config.polygons.splice(ndx, 1);
+
+    const polygon = config.polygons[ndx];
+    const lines = polygon.lineIds.map((lineId) =>
+      getWholeLineLineRaw(config, lineId)
+    );
+    lines.forEach((line) => {
+      wholeLineDelLineByPointIds(config, line.pointIds);
+    });
+  }
+};
+
+/**
  * 删除点,会影响polygons 和lines points
  * @param config WholeLine
  * @param delPointIds 要删除的点ids
@@ -507,18 +545,19 @@ export const wholeLineReplacePoint = (
     },
   };
 
+  const prepareDelLines: string[][] = [];
   for (let i = 0; i < config.polygons.length; i++) {
     const polygon = config.polygons[i];
     let initPolygonLineIds: string[];
 
     for (const originPointId of originPointIds) {
-      const lineIds = polygon.lineIds;
-      for (let j = 0; j < lineIds.length; j++) {
-        const line = getWholeLineLineRaw(config, lineIds[j]);
+      if (originPointId === replacePointId) break;
+
+      for (let j = 0; j < polygon.lineIds.length; j++) {
+        const line = getWholeLineLineRaw(config, polygon.lineIds[j]);
         let ndx = line.pointIds.indexOf(originPointId);
         if (!~ndx) continue;
         initPolygonLineIds = initPolygonLineIds || [...polygon.lineIds];
-
         const repPoints = [...line.pointIds];
         repPoints[ndx] = replacePointId;
 
@@ -531,9 +570,7 @@ export const wholeLineReplacePoint = (
           change.lineChange.del.push(...adL.change.lineChange.add);
         }
 
-        const dl = wholeLineDelLineByPointIds(config, line.pointIds);
-        change.lineChange.del.push(...dl.change.lineChange.del);
-        change.pointChange.del.push(...dl.change.pointChange.del);
+        prepareDelLines.push(line.pointIds);
         j--;
       }
       // 如果合并后只剩一个点则直接删除整个polygon
@@ -542,6 +579,7 @@ export const wholeLineReplacePoint = (
         if (polygon.lineIds.length === 0) {
           change.polygonChange.del.push(beforePolygon);
           config.polygons.splice(i--, 1);
+          console.log("del polygon");
         } else {
           change.polygonChange.update.push({
             before: beforePolygon,
@@ -552,6 +590,12 @@ export const wholeLineReplacePoint = (
     }
   }
 
+  prepareDelLines.forEach((pointIds) => {
+    const dl = wholeLineDelLineByPointIds(config, pointIds);
+    change.lineChange.del.push(...dl.change.lineChange.del);
+    change.pointChange.del.push(...dl.change.pointChange.del);
+  });
+
   return change;
 };
 
@@ -576,76 +620,80 @@ export const wholeLineLineIsolatePoint = (
     polygonChange: {
       update: [],
     },
-    pointChange: {
-      add: [],
-      del: [],
-    },
   };
+  // [3, 4] [4, 3]
   const isolateLines = getWholeLineLinesRawByPointIds(
     config,
-    line.map(({ id }) => id)
+    line.map(({ id }) => id),
+    true
   );
 
-  const passiveLines = config.lines.filter((line) => {
-    if (isolateLines.includes(line)) return false;
-    const ndx = line.pointIds.indexOf(isolateId);
-    if (~ndx) {
-      const points = [...line.pointIds];
-      points[ndx] = addPointId;
-      change.lineChange.update.push({
-        before: { ...line, pointIds: [...line.pointIds] },
-        after: { ...line, pointIds: [...points] },
-      });
-      line.pointIds = points;
-      return true;
-    }
+  config.lines.forEach((line) => {
+    let ndx = -1;
+    if (
+      isolateLines.includes(line) ||
+      !~(ndx = line.pointIds.indexOf(isolateId))
+    )
+      return;
+    const points = [...line.pointIds];
+    points[ndx] = addPointId;
+    change.lineChange.update.push({
+      before: { ...line, pointIds: [...line.pointIds] },
+      after: { ...line, pointIds: [...points] },
+    });
+    line.pointIds = points;
   });
-  console.log(passiveLines);
-  const pointId = "0";
 
-  let addedPoints: string[] | null = null;
-  const lineIds = line.map((p) => p.id);
-  if (lineIds.includes(pointId)) {
-    return { addedPoints, change };
-  }
+  let inverseLine: WholeLineLineAttrib;
+  let alongLine: WholeLineLineAttrib;
 
   for (const polygon of config.polygons) {
     let initPolygonLineIds: string[];
     for (let ndx = 0; ndx < polygon.lineIds.length; ndx++) {
+      if (!isolateLines.some(({ id }) => id === polygon.lineIds[ndx])) continue;
+
       const lineRaw = getWholeLineLineRaw(config, polygon.lineIds[ndx]);
-      // 如果需要添加的点已经是线段的起点或终点则直接忽略
+      const along = lineRaw.pointIds.indexOf(isolateId) === 1;
+      const nextNdx =
+        ((along ? ndx + 1 : ndx - 1) + polygon.lineIds.length) %
+        polygon.lineIds.length;
+
+      const nextLineRaw = getWholeLineLineRaw(config, polygon.lineIds[nextNdx]);
       if (
-        lineIds.includes(lineRaw.pointIds[0]) &&
-        lineIds.includes(lineRaw.pointIds[1])
+        along
+          ? nextLineRaw.pointIds[0] === isolateId
+          : nextLineRaw.pointIds[1] === isolateId
       ) {
-        initPolygonLineIds = initPolygonLineIds || [...polygon.lineIds];
-        addedPoints = addedPoints || [
-          lineRaw.pointIds[0],
-          pointId,
-          lineRaw.pointIds[1],
-        ];
-        const addl1 = wholeLineAddLineByPointIds(config, [
-          lineRaw.pointIds[0],
-          pointId,
-        ]);
-        // [1,2][2,3] [2,4, 3]
-        // [1.2][2.4],[4.3]
-        const addl2 = wholeLineAddLineByPointIds(config, [
-          pointId,
-          lineRaw.pointIds[1],
-        ]);
-
-        change.lineChange.add.push(
-          ...addl1.change.lineChange.add,
-          ...addl2.change.lineChange.add
-        );
-        polygon.lineIds.splice(ndx, 1, addl1.line.id, addl2.line.id);
-
-        const dl = wholeLineDelLineByPointIds(config, lineRaw.pointIds);
-        change.lineChange.del.push(...dl.change.lineChange.del);
-        change.pointChange.del.push(...dl.change.pointChange.del);
+        continue;
       }
+      initPolygonLineIds = initPolygonLineIds || [...polygon.lineIds];
+
+      let addLine: WholeLineLineAttrib;
+      if (along) {
+        if (!alongLine) {
+          const { line, change } = wholeLineAddLineByPointIds(config, [
+            isolateId,
+            addPointId,
+          ]);
+          change.lineChange.add.push(...change.lineChange.add);
+          alongLine = line;
+        }
+        addLine = alongLine;
+      } else {
+        if (!inverseLine) {
+          const { line, change } = wholeLineAddLineByPointIds(config, [
+            addPointId,
+            isolateId,
+          ]);
+          change.lineChange.add.push(...change.lineChange.add);
+          inverseLine = line;
+        }
+        addLine = inverseLine;
+      }
+      const inertNdx = along ? ndx + 1 : ndx;
+      polygon.lineIds.splice(inertNdx, 0, addLine.id);
     }
+
     if (initPolygonLineIds) {
       change.polygonChange.update.push({
         before: { ...polygon, lineIds: initPolygonLineIds },
@@ -653,5 +701,5 @@ export const wholeLineLineIsolatePoint = (
       });
     }
   }
-  return { addedPoints, change };
+  return { change };
 };

+ 69 - 37
src/board/packages/whole-line/service/whole-line-edit.ts

@@ -6,7 +6,7 @@ import {
   WholeLinePolygonAttrib,
 } from "../view";
 import { KonvaEventObject } from "konva/lib/Node";
-import { shapeParentsEq } from "../../../shared";
+import { getFlatChildren, round, shapeParentsEq } from "../../../shared";
 import {
   wholeLineAddPoint,
   wholeLineFixLineAddPoint,
@@ -22,6 +22,7 @@ import {
   mergeChange,
   wholeLineAddLineByPointIds,
   wholeLineDelPointByPointIds,
+  wholeLineDelPolygon,
 } from "./whole-line-base";
 import { getAdsorbPosition } from "../../../shared/adsorb";
 
@@ -69,16 +70,18 @@ export const penWholeLinePoygonsEdit = <
         y: pos[1],
       } as P));
 
+  const useedIds: string[] = [];
   const getPolygonAttrib = (polygonId: string) => {
     if (!polygonId) {
-      const id = generateWholeLinePoygonId(config);
+      const id = generateWholeLinePoygonId(config, useedIds);
       config.polygons.push({
         id,
         lineIds: [],
       });
-
+      useedIds.push(id);
       return config.polygons[config.polygons.length - 1];
     } else {
+      useedIds.push(polygonId);
       return config.polygons.find(({ id }) => id === polygonId);
     }
   };
@@ -89,7 +92,6 @@ export const penWholeLinePoygonsEdit = <
 
   const start = (currentPolygonId: string | null) => {
     polyginAttrib = getPolygonAttrib(currentPolygonId);
-
     if (!polyginAttrib) {
       throw `${currentPolygonId}的多边形不存在!`;
     }
@@ -134,7 +136,7 @@ export const penWholeLinePoygonsEdit = <
         id.includes(WholeLinePoint.namespace)
       );
     });
-    const child = target && tree.find(target.id());
+    let child = target && tree.find(target.id());
     const pixel = [evt.evt.offsetX, evt.evt.offsetY];
 
     if (child instanceof WholeLineLine) {
@@ -151,35 +153,58 @@ export const penWholeLinePoygonsEdit = <
     }
 
     let pointAttrib: P & Attrib;
-    if (child instanceof WholeLinePoint) {
-      if (canOper && !canOper(child, evt.target)) {
-        return { change, isClose: false };
-      }
-      pointAttrib = quotePoint ? child.attrib : { ...child.attrib };
-
-      if (polyginAttrib.id === prevId) {
-        return { change, isClose: false };
-      }
-    }
     const polygonPoints = getWholeLinePolygonPoints(config, polyginAttrib.id);
-    if (!pointAttrib) {
-      let position: number[];
-      if (adsorbRadius) {
-        const points = polygonPoints.map(({ x, y }) => [x, y]);
-        if (prevId) {
-          const prev = getWholeLinePoint(config, prevId);
-          points.push([prev.x, prev.y]);
+
+    while (true) {
+      if (child instanceof WholeLinePoint) {
+        if (canOper && !canOper(child, evt.target)) {
+          return { change, isClose: false };
+        }
+        pointAttrib = quotePoint ? child.attrib : { ...child.attrib };
+
+        if (polyginAttrib.id === prevId) {
+          return { change, isClose: false };
         }
-        position = getAdsorbPosition({
-          tree,
-          pixel,
-          radius: adsorbRadius,
-          points,
-        });
       } else {
-        position = tree.getRealFromStage(pixel);
+        let position: number[];
+        if (adsorbRadius) {
+          const points = polygonPoints.map(({ x, y }) => [x, y]);
+          if (prevId) {
+            const prev = getWholeLinePoint(config, prevId);
+            points.push([prev.x, prev.y]);
+          }
+          position = getAdsorbPosition({
+            tree,
+            pixel,
+            radius: adsorbRadius,
+            points,
+          });
+        } else {
+          position = tree.getRealFromStage(pixel);
+        }
+
+        const flatChildren = getFlatChildren(tree);
+        let i = 0;
+        for (; i < flatChildren.length; i++) {
+          if (flatChildren[i] instanceof WholeLinePoint) {
+            const point = flatChildren[i] as WholeLinePoint;
+            if (
+              round(point.attrib.x - position[0], 4) === 0 &&
+              round(point.attrib.y - position[1], 4) === 0
+            ) {
+              child = tree.children[i];
+              break;
+            }
+          }
+        }
+
+        if (i !== flatChildren.length) {
+          child = flatChildren[i];
+          continue;
+        }
+        pointAttrib = pointAttribFactory(position) as P & Attrib;
       }
-      pointAttrib = pointAttribFactory(position) as P & Attrib;
+      break;
     }
 
     const curNdx = polygonPoints.findIndex(({ id }) => id === pointAttrib.id);
@@ -194,17 +219,14 @@ export const penWholeLinePoygonsEdit = <
       change = mergeChange(change, cChange);
 
       if (polyginAttrib.lineIds.length === 0) {
-        removePolygon();
+        wholeLineDelPolygon(config, polyginAttrib.id);
         const repPoint = cChange.pointChange.del.find(
           ({ id }) => id !== pointAttrib.id
         );
-        delete repPoint.id;
         start(null);
-        prevId = wholeLineAddPoint(config, repPoint).id;
-        change.pointChange.add.push({
-          ...repPoint,
-          id: prevId,
-        });
+        const prev = wholeLineAddPoint(config, { ...repPoint, id: undefined });
+        prevId = prev.id;
+        change.pointChange.add.push(prev);
       }
       return { change, isClose };
     }
@@ -228,6 +250,16 @@ export const penWholeLinePoygonsEdit = <
         return continuous(evt);
       }
     } else if (prevId) {
+      const prev = getWholeLinePoint(config, prevId);
+      if (
+        !(
+          Math.abs(pointAttrib.x - prev.x) > 4 ||
+          Math.abs(pointAttrib.y - prev.y) > 4
+        )
+      ) {
+        return { change, isClose };
+      }
+
       const { line, change: mChange } = wholeLineAddLineByPointIds(config, [
         prevId,
         wholeLineAddPoint(config, pointAttrib).id,

+ 78 - 15
src/board/packages/whole-line/service/whole-line-helper.ts

@@ -17,6 +17,7 @@ import { getRealAbsoluteSize } from "../../../shared";
 import { lineShapeFactory, pointShapeFactory } from "../style";
 import { Layer } from "konva/lib/Layer";
 import { Group } from "konva/lib/Group";
+import { hasTouchEvents } from "../../../env";
 
 export type WholeLineHelperInfo = {
   change: WholeLineChange;
@@ -60,7 +61,6 @@ export const penWholeLinePoygonsEditWithHelperMouse = <
   let downing = false;
   const downHandler = () => {
     downing = true;
-    helperInfo.value = undefined;
   };
   const moveHandler = (evt: KonvaEventObject<any>) => {
     if (downing) return;
@@ -72,18 +72,69 @@ export const penWholeLinePoygonsEditWithHelperMouse = <
       config,
     };
   };
+
   const upHandler = (evt: KonvaEventObject<any>) => {
     downing = false;
     moveHandler(evt);
   };
+
+  let prevOffset: { x: number; y: number };
   const clickHandler = (evt: KonvaEventObject<any>) => {
-    const { isClose } = edit.continuous(evt);
-    syncHelper();
-    if (isClose) {
-      leaveEditMode();
+    if (
+      prevOffset &&
+      Math.abs(evt.evt.offsetX - prevOffset.x) < 4 &&
+      Math.abs(evt.evt.offsetY - prevOffset.y) < 4
+    ) {
+      return;
+    } else {
+      const { isClose } = edit.continuous(evt);
+      syncHelper();
+      if (isClose) {
+        leaveEditMode();
+      }
+      prevOffset = null;
+    }
+  };
+
+  const touchAttachOffset = (ev: KonvaEventObject<any>) => {
+    const dom = ev.evt.target as HTMLElement;
+    const rect = dom.getBoundingClientRect();
+    const offsetX = ev.evt.changedTouches[0].pageX - rect.left;
+    const offsetY = ev.evt.changedTouches[0].pageY - rect.top;
+
+    ev.evt.offsetX = offsetX;
+    ev.evt.offsetY = offsetY;
+  };
+
+  const touchstartHandler = (ev: KonvaEventObject<any>) => {
+    ev.evt.preventDefault();
+    touchAttachOffset(ev);
+    moveHandler(ev);
+    if (
+      !helperInfo.value.change.lineChange.add.length &&
+      !helperInfo.value.change.lineChange.del.length
+    ) {
+      touchendHandler(ev);
+      prevOffset = {
+        x: ev.evt.offsetX,
+        y: ev.evt.offsetY,
+      };
     }
   };
 
+  const touchmoveHandler = (ev: KonvaEventObject<any>) => {
+    ev.evt.preventDefault();
+    touchAttachOffset(ev);
+    moveHandler(ev);
+  };
+
+  const touchendHandler = (ev: KonvaEventObject<any>) => {
+    ev.evt.preventDefault();
+    touchAttachOffset(ev);
+    clickHandler(ev);
+    helperInfo.value = undefined;
+  };
+
   const quitHandler = (ev: KeyboardEvent) => {
     if (ev.key === "Escape") {
       leaveEditMode();
@@ -91,11 +142,17 @@ export const penWholeLinePoygonsEditWithHelperMouse = <
   };
 
   let leaveEditMode = () => {
-    stage.off("click.editPolygonsMode", clickHandler);
-    stage.off("mousedown.editPolygonsMode", downHandler);
-    stage.off("mousemove.editPolygonsMode", moveHandler);
-    stage.off("mouseup.editPolygonsMode", upHandler);
-    document.removeEventListener("keydown", quitHandler);
+    if (hasTouchEvents) {
+      stage.off("touchstart.editPolygonsMode", touchstartHandler);
+      stage.off("touchmove.editPolygonsMode", touchmoveHandler);
+      stage.off("touchend.editPolygonsMode", touchendHandler);
+    } else {
+      stage.off("click.editPolygonsMode", clickHandler);
+      stage.off("mousedown.editPolygonsMode", downHandler);
+      stage.off("mousemove.editPolygonsMode", moveHandler);
+      stage.off("mouseup.editPolygonsMode", upHandler);
+      document.removeEventListener("keydown", quitHandler);
+    }
 
     helperInfo.value = undefined;
     edit.end();
@@ -103,11 +160,17 @@ export const penWholeLinePoygonsEditWithHelperMouse = <
     props.quitHandler && props.quitHandler();
   };
 
-  stage.on("click.editPolygonsMode", clickHandler);
-  stage.on("mousedown.editPolygonsMode", downHandler);
-  stage.on("mousemove.editPolygonsMode", moveHandler);
-  stage.on("mouseup.editPolygonsMode", upHandler);
-  document.addEventListener("keydown", quitHandler);
+  if (hasTouchEvents) {
+    stage.on("touchstart.editPolygonsMode", touchstartHandler);
+    stage.on("touchmove.editPolygonsMode", touchmoveHandler);
+    stage.on("touchend.editPolygonsMode", touchendHandler);
+  } else {
+    stage.on("click.editPolygonsMode", clickHandler);
+    stage.on("mousedown.editPolygonsMode", downHandler);
+    stage.on("mousemove.editPolygonsMode", moveHandler);
+    stage.on("mouseup.editPolygonsMode", upHandler);
+    document.addEventListener("keydown", quitHandler);
+  }
 
   return {
     helperInfo,

+ 15 - 11
src/board/packages/whole-line/service/whole-line-mouse.ts

@@ -15,18 +15,22 @@ export type MPoint = Omit<WholeLinePointAttrib, "id"> & { id?: string };
 export const wholeLineAddPoint = <T extends MPoint>(
   config: WholeLineAttrib,
   pointAttrib: T
-) => {
-  if ("id" in pointAttrib && pointAttrib["id"]) {
-    return pointAttrib;
-  }
+): WholeLinePointAttrib => {
+  const addId =
+    "id" in pointAttrib && pointAttrib["id"]
+      ? pointAttrib["id"]
+      : generateWholeLinePointId(config);
 
-  const id = generateWholeLinePointId(config);
-  const point = {
-    ...pointAttrib,
-    id,
-  };
-  config.points.push(point);
-  return point;
+  if (config.points.some(({ id }) => id === addId)) {
+    return pointAttrib as WholeLinePointAttrib;
+  } else {
+    const point = {
+      ...pointAttrib,
+      id: addId,
+    };
+    config.points.push(point);
+    return point;
+  }
 };
 
 /**

+ 247 - 64
src/board/packages/whole-line/service/whole-line-tear-merge.ts

@@ -9,6 +9,8 @@ import {
   getVerticalDire,
   createLineByDire,
   getLineProjection,
+  RelationshipEnum,
+  round,
 } from "../../../shared/math";
 import {
   WholeLineAttrib,
@@ -20,15 +22,21 @@ import {
   WHOLE_LINE_POINT_MERGE_DIST,
 } from "./constant";
 import {
+  generateWholeLineLineId,
   generateWholeLinePointId,
+  generateWholeLinePoygonId,
+  getWholeLineLineRaw,
   getWholeLineLinesRawByPointId,
   getWholeLinePoint,
   getWholeLinePoints,
   getWholeLinePolygonLines,
   getWholeLinePolygonLinesByPoint,
+  wholeLineDelLineByPointIds,
   wholeLineLineAddPoint,
+  wholeLineLineIsolatePoint,
   wholeLineReplacePoint,
 } from "./whole-line-base";
+import { wholeLineAddPoint } from "./whole-line-mouse";
 
 export const repeatablePushWholeLinePoint = (config: WholeLineAttrib) => {
   let maxId = Math.max(...config.points.map((point) => Number(point.id) || 0));
@@ -107,7 +115,8 @@ export const repeatableWholeLineLineIntersections = (
           const b = otherLine;
           const intersection = getLineIntersection(
             [a[0].x, a[0].y, a[1].x, a[1].y],
-            [b[0].x, b[0].y, b[1].x, b[1].y]
+            [b[0].x, b[0].y, b[1].x, b[1].y],
+            [RelationshipEnum.Intersect]
           );
           if (intersection) {
             if (DEV) {
@@ -455,23 +464,49 @@ export const mergeWholeLinePointsByPoint = (
  * @param initPosition 线段初始位置
  * @param move 移动向量
  */
-export const moveWholeLineLine = (
+export const repeatMoveWholeLineLine = (
   config: WholeLineAttrib,
   lineAttrib: WholeLineLineAttrib,
   initPosition: number[][],
-  move: number[],
-  maxAngle = 160
+  angleRang = [10, 170]
 ) => {
-  console.log(lineAttrib);
+  const cache: {
+    [key in string]: {
+      id: string;
+      split: boolean;
+      refDire: number[];
+      lineDire: number[];
+      isFirst: boolean;
+    };
+  } = {};
   const linePointIds = lineAttrib.pointIds;
-  const moveDire = new Vector2(move[0], move[1]).normalize().toArray();
-  const pointJointLineDires: (number[] | null)[] = [];
-  const poinJointLineIds: (string | null)[] = [];
-  const isSplitPoints: boolean[] = [];
-
-  // 获取参考线段,夹角最小,雨move向量夹角最小的优先
-  for (let i = 0; i < 2; i++) {
-    const curPointId = linePointIds[i];
+  const line0 = getWholeLinePoints(config, [linePointIds[0], linePointIds[1]]);
+  const lineDire0 = getLineDire([
+    line0[0].x,
+    line0[0].y,
+    line0[1].x,
+    line0[1].y,
+  ]);
+  const line1 = getWholeLinePoints(config, [linePointIds[1], linePointIds[0]]);
+  const lineDire1 = getLineDire([
+    line1[0].x,
+    line1[0].y,
+    line1[1].x,
+    line1[1].y,
+  ]);
+
+  const getPointRefInfo = (moveDire: number[], ndx: number) => {
+    let isFirst = true;
+    const curPointId = linePointIds[ndx];
+    if (!cache[curPointId]) {
+      cache[curPointId] = {} as any;
+    } else {
+      isFirst = false;
+      // if (cache[curPointId].split) {
+      return { ...cache[curPointId], isFirst };
+      // }
+    }
+
     const joinLines = getWholeLineLinesRawByPointId(config, curPointId).filter(
       (line) =>
         !(
@@ -479,12 +514,8 @@ export const moveWholeLineLine = (
           line.pointIds.includes(linePointIds[1])
         )
     );
-
-    const line = getWholeLinePoints(config, [
-      linePointIds[i],
-      linePointIds[i === 0 ? 1 : 0],
-    ]);
-    const lineDire = getLineDire([line[0].x, line[0].y, line[1].x, line[1].y]);
+    const joinLineDires: number[][] = [];
+    const lineDire = ndx === 0 ? lineDire0 : lineDire1;
 
     let invAngle = Number.MAX_VALUE;
     let invSelectLineId: string;
@@ -492,12 +523,12 @@ export const moveWholeLineLine = (
 
     let alongAngle = -Number.MAX_VALUE;
     let alongSelectLineId: string;
-    let alongSelectLineDire: number[] | null;
+    let alongSelectLineDire: number[] | null = null;
 
     for (let { pointIds: joinPointIds, id } of joinLines) {
       joinPointIds = [
-        linePointIds[i],
-        ...joinPointIds.filter((id) => id !== linePointIds[i]),
+        linePointIds[ndx],
+        ...joinPointIds.filter((id) => id !== linePointIds[ndx]),
       ];
       const joinLine = getWholeLinePoints(config, joinPointIds);
       const joinLineDire = getLineDire([
@@ -506,6 +537,7 @@ export const moveWholeLineLine = (
         joinLine[1].x,
         joinLine[1].y,
       ]);
+      joinLineDires.push(joinLineDire);
       // if (["10", "14"].includes(id)) {
       //   continue;
       // }
@@ -533,8 +565,8 @@ export const moveWholeLineLine = (
 
     if (!invSelectLineDire || !alongSelectLineDire) {
       pointJointLineDire = invSelectLineDire || alongSelectLineDire;
-      selectLineId = invSelectLineId || alongSelectLineId;
-      angle = invAngle | alongAngle;
+      selectLineId = invSelectLineDire ? invSelectLineId : alongSelectLineId;
+      angle = invSelectLineDire ? invAngle : alongAngle;
     } else if (
       Math.abs(getDire2Angle(moveDire, invSelectLineDire)) <
       Math.abs(getDire2Angle(moveDire, alongSelectLineDire))
@@ -548,59 +580,210 @@ export const moveWholeLineLine = (
       angle = alongAngle;
     }
 
-    let isSplitPoint = joinLines.length > 2;
+    let isSplitPoint = false;
     // 需要判定,如果参考线与当前线段夹角超过多少则不适合作为参考线
+    let refLineAngle = Math.abs(MathUtils.radToDeg(angle));
     if (
-      pointJointLineDire !== null &&
-      Math.abs(MathUtils.radToDeg(angle)) > maxAngle
+      (pointJointLineDire !== null && refLineAngle > angleRang[1]) ||
+      refLineAngle < angleRang[0]
     ) {
+      console.log(
+        "夹角不在范围,分割",
+        selectLineId,
+        ndx === 0 ? line0 : line1,
+        refLineAngle
+      );
       selectLineId = null;
       pointJointLineDire = getVerticalDire(pointJointLineDire);
       isSplitPoint = true;
+
+      // 如果有多条线段,判断是否方向一致,不一致则要分割
+    } else if (joinLines.length > 1) {
+      const temp = pointJointLineDire;
+      for (let i = 0; i < joinLines.length; i++) {
+        if (temp === joinLineDires[i]) {
+          continue;
+        }
+        const angle =
+          (getDire2Angle(temp, joinLineDires[i]) + Math.PI) % Math.PI;
+
+        if (round(angle, 3) > 0) {
+          console.log("方向不同,分割");
+          isSplitPoint = true;
+          break;
+        }
+      }
+    }
+
+    cache[curPointId].refDire = pointJointLineDire;
+    cache[curPointId].lineDire = lineDire;
+    cache[curPointId].id = selectLineId;
+    cache[curPointId].split = isSplitPoint;
+    cache[curPointId].isFirst = isFirst;
+
+    return cache[curPointId];
+  };
+
+  return (move: number[]) => {
+    const moveDire = new Vector2(move[0], move[1]).normalize().toArray();
+    const pointInfos = [
+      getPointRefInfo(moveDire, 0),
+      getPointRefInfo(moveDire, 1),
+    ];
+
+    // console.log(pointInfos);
+    const positions = [
+      [initPosition[0][0] + move[0], initPosition[0][1] + move[1]],
+      [initPosition[1][0] + move[0], initPosition[1][1] + move[1]],
+    ];
+    const pointAttribs = getWholeLinePoints(config, lineAttrib.pointIds);
+    if (pointInfos[0].refDire || pointInfos[1].refDire) {
+      const currentPositions: number[][] = [];
+      const joinDires = pointInfos.map(({ refDire }) =>
+        refDire === null
+          ? pointInfos[0].refDire || pointInfos[1].refDire
+          : refDire
+      );
+
+      for (let i = 0; i < pointInfos.length; i++) {
+        const joinDire = joinDires[i];
+        const isSplit = pointInfos[i].split && pointInfos[i].isFirst;
+        const joinLine = createLineByDire(joinDire, initPosition[i], 10);
+
+        if (isSplit) {
+          const addPointId = generateWholeLinePointId(config);
+          config.points.push({
+            ...pointAttribs[i],
+            id: addPointId,
+          });
+          wholeLineLineIsolatePoint(
+            config,
+            line0,
+            lineAttrib.pointIds[i],
+            addPointId
+          );
+        }
+        currentPositions[i] = getLineProjection(joinLine, positions[i]).point;
+      }
+      const firstMain =
+        getLineDist(currentPositions[0], initPosition[0]) <
+        getLineDist(currentPositions[1], initPosition[1]);
+      const lineDire = firstMain ? lineDire0 : lineDire1;
+      const lineStart = firstMain ? currentPositions[0] : currentPositions[1];
+      const bitDire = firstMain ? joinDires[1] : joinDires[0];
+      const bitStart = firstMain ? initPosition[1] : initPosition[0];
+
+      const main = createLineByDire(lineDire, lineStart, 10);
+      const bit = createLineByDire(bitDire, bitStart, 10);
+      const lineEnd = getLineIntersection(main, bit);
+
+      if (firstMain) {
+        positions[0] = lineStart;
+        positions[1] = lineEnd;
+      } else {
+        positions[1] = lineStart;
+        positions[0] = lineEnd;
+      }
     }
 
-    pointJointLineDires.push(pointJointLineDire);
-    poinJointLineIds.push(selectLineId);
-    isSplitPoints.push(isSplitPoint);
+    pointAttribs[0].x = positions[0][0];
+    pointAttribs[0].y = positions[0][1];
+    pointAttribs[1].x = positions[1][0];
+    pointAttribs[1].y = positions[1][1];
+  };
+};
+
+/**
+ * 修复config数据, 重合点删除,重合线删除
+ * @param config
+ */
+export const fixWholeLineConfig = (config: WholeLineAttrib) => {
+  for (let i = 0; i < config.lines.length; i++) {
+    const line = config.lines[i];
+    const points = getWholeLinePoints(config, line.pointIds);
+    const dist = round(
+      getLineDist([points[0].x, points[0].y], [points[1].x, points[1].y]),
+      4
+    );
+    if (dist !== 0) continue;
+    const change = wholeLineReplacePoint(config, [points[1].id], points[0].id);
+    i = Math.max(i - change.lineChange.del.length, 0);
   }
 
-  const positions = [
-    [initPosition[0][0] + move[0], initPosition[0][1] + move[1]],
-    [initPosition[1][0] + move[0], initPosition[1][1] + move[1]],
-  ];
-  const pointAttribs = getWholeLinePoints(config, lineAttrib.pointIds);
-  if (pointJointLineDires[0] || !pointJointLineDires[1]) {
-    for (let i = 0; i < pointJointLineDires.length; i++) {
-      const joinDire =
-        pointJointLineDires[i] === null
-          ? pointJointLineDires[0] || pointJointLineDires[1]
-          : pointJointLineDires[i];
-      const isSplit = isSplitPoints[i];
-      const joinLine = createLineByDire(joinDire, initPosition[i], 10);
-
-      if (isSplit) {
-        const addPointId = generateWholeLinePointId(config);
-        config.points.push({
-          ...pointAttribs[i],
-          id: addPointId,
-        });
-        config.lines.forEach((line) => {
-          if (line === lineAttrib) return;
-          const ndx = line.pointIds.indexOf(pointAttribs[i].id);
-          if (~ndx) {
-            line.pointIds[ndx] = addPointId;
-          }
-        });
-        console.log(wholeLineLineAddPoint(config, pointAttribs, addPointId));
+  // 将来回线段从多边形中剔除,额外建立多边形来存放
+  const delLines: WholeLinePointAttrib[][] = [];
+  for (let i = 0; i < config.polygons.length; i++) {
+    const polygon = config.polygons[i];
+
+    for (let j = 0; j < polygon.lineIds.length; j++) {
+      const current = getWholeLineLineRaw(config, polygon.lineIds[j]);
+      const nextNdx = (j + 1) % polygon.lineIds.length;
+      const next = getWholeLineLineRaw(config, polygon.lineIds[nextNdx]);
+
+      // 来回线段删除
+      if (
+        current.pointIds[0] === next.pointIds[1] &&
+        current.pointIds[1] === next.pointIds[0]
+      ) {
+        delLines.push(getWholeLinePoints(config, current.pointIds));
+        if (j > nextNdx) {
+          polygon.lineIds.splice(j, 1);
+          polygon.lineIds.splice(nextNdx, 1);
+        } else {
+          polygon.lineIds.splice(j, 2);
+          j -= 1;
+        }
+        wholeLineDelLineByPointIds(config, current.pointIds);
+        wholeLineDelLineByPointIds(config, next.pointIds);
+        continue;
+        // 小于一定阈值的也删除
       }
 
-      positions[i] = getLineProjection(joinLine, positions[i]).point;
+      const currentLine = getWholeLinePoints(config, current.pointIds);
+      if (
+        getLineDist(
+          [currentLine[0].x, currentLine[0].y],
+          [currentLine[1].x, currentLine[1].y]
+        ) < WHOLE_LINE_POINT_MERGE_DIST
+      ) {
+        wholeLineReplacePoint(config, [currentLine[0].id], currentLine[1].id);
+        j--;
+      }
+
+      // // 同向线段合并
+      // const currentLine = getWholeLinePoints(config, current.pointIds);
+      // const nextLine = getWholeLinePoints(config, current.pointIds);
+    }
+
+    if (polygon.lineIds.length === 0) {
+      console.log("del polygon");
+      config.polygons.splice(i--, 1);
     }
   }
 
-  // console.log(positions);
-  pointAttribs[0].x = positions[0][0];
-  pointAttribs[0].y = positions[0][1];
-  pointAttribs[1].x = positions[1][0];
-  pointAttribs[1].y = positions[1][1];
+  delLines.forEach((pointAttribs) => {
+    if (pointAttribs[0].id === pointAttribs[1].id) return;
+    const id = generateWholeLinePoygonId(config);
+    const lineId = generateWholeLineLineId(config);
+
+    pointAttribs = pointAttribs.map((point) => {
+      if (config.points.some(({ id }) => id === point.id)) {
+        return point;
+      } else {
+        return wholeLineAddPoint(config, { x: point.x, y: point.y });
+      }
+    });
+
+    config.lines.push({
+      id: lineId,
+      pointIds: pointAttribs.map(({ id }) => id),
+    });
+    console.log(lineId, id);
+    config.polygons.push({
+      id,
+      lineIds: [lineId],
+    });
+  });
+
+  console.log(delLines);
 };

+ 10 - 2
src/board/packages/whole-line/style.ts

@@ -4,6 +4,7 @@ import { getRealAbsoluteSize } from "../../shared";
 import { Group } from "konva/lib/Group";
 import { CustomizeShape } from "../../type";
 import { KonvaEventObject } from "konva/lib/Node";
+import { Color, MathUtils } from "three";
 
 export const point = {
   fill: "#ffffff",
@@ -99,9 +100,15 @@ type PolygonShapeProps = {
 };
 
 export const polygonShapeFactory = (props: PolygonShapeProps = {}) => {
+  const color = new Color();
+  color.setHSL(MathUtils.randFloat(0, 1), 1, 0.8);
+  const fill = "#" + color.getHexString();
+
+  console.log(fill);
   const group = new Group();
   const path = new Line({
     closed: true,
+    fill,
   });
   const closeLine = props.autoClose
     ? props.lineFactory
@@ -115,11 +122,11 @@ export const polygonShapeFactory = (props: PolygonShapeProps = {}) => {
   }
 
   const common = (ev?: KonvaEventObject<any>) => {
-    path.fill(polygon.fill);
+    // path.fill(polygon.fill);
     closeLine && closeLine.common(ev);
   };
   const active = (ev: KonvaEventObject<any>) => {
-    path.fill(polygon.activeFill);
+    // path.fill(polygon.activeFill);
     closeLine && closeLine.active && closeLine.active(ev);
   };
 
@@ -128,6 +135,7 @@ export const polygonShapeFactory = (props: PolygonShapeProps = {}) => {
     shape: group,
     setData(data: number[]) {
       path.points(data);
+
       if (closeLine) {
         if (data.length > 4) {
           const ndxs = [data.length - 2, data.length - 1, 0, 1];

+ 230 - 0
src/board/plugins/camera-plugin.ts

@@ -0,0 +1,230 @@
+import { Euler, MathUtils, Matrix4, Quaternion, Vector3 } from "three";
+import { Container } from "../packages";
+import { openShapeDrag } from "../shared";
+
+export type CameraQueryPluginProps = {
+  move?: boolean;
+  wheel?: boolean;
+  bound?: number[];
+  padding?: number | number[];
+  size?: number[];
+};
+
+export class CameraQueryPlugin {
+  tree: Container;
+  props: Omit<CameraQueryPluginProps, "bound">;
+  cameraMat: Matrix4;
+  clipMat: Matrix4 = new Matrix4();
+
+  constructor(props: CameraQueryPluginProps = {}) {
+    this.props = props;
+    this.props.move = props.move || false;
+    this.props.wheel = props.wheel || false;
+    this.cameraMat = new Matrix4();
+
+    if (props.size) {
+      this.setSize(props.size[0], props.size[1]);
+    }
+    if (props.bound) {
+      this.setBound(props.bound, props.padding);
+    }
+  }
+
+  private dragDestory?: () => void;
+  disableMove() {
+    this.props.move = false;
+    this.dragDestory && this.dragDestory();
+  }
+
+  enableMove() {
+    this.disableMove();
+    this.props.move = true;
+    if (!this.tree) return;
+
+    const { stage } = this.tree;
+    this.dragDestory = openShapeDrag(
+      stage,
+      {
+        readyHandler: () => this.cameraMat.clone(),
+        moveHandler: (move, initMat) => {
+          const mat = this.clipMat.clone().multiply(initMat).invert();
+          const v2 = new Vector3(move[0], move[1], 0).applyMatrix4(mat);
+          this.move([v2.x, v2.y], initMat);
+        },
+      },
+      false
+    );
+  }
+
+  private wheelDestory?: () => void;
+  disableWheel() {
+    this.props.wheel = false;
+    this.wheelDestory && this.wheelDestory();
+  }
+
+  enableWheel() {
+    this.disableWheel();
+    this.props.wheel = true;
+    if (!this.tree) return;
+
+    const {
+      config: { dom },
+    } = this.tree;
+
+    const whellHandler = (ev: WheelEvent) => {
+      const scale = 1 - ev.deltaY / 1000;
+
+      const center = new Vector3(ev.offsetX, ev.offsetY, 0).applyMatrix4(
+        this.clipMat.clone().multiply(this.cameraMat).invert()
+      );
+      this.scale([center.x, center.y], scale);
+    };
+    dom.addEventListener("wheel", whellHandler);
+    this.wheelDestory = () => dom.removeEventListener("wheel", whellHandler);
+  }
+
+  move(movePosition: number[], initMat = this.cameraMat) {
+    this.mutMat(
+      new Matrix4().setPosition(movePosition[0], movePosition[1], 0),
+      initMat
+    );
+  }
+
+  scale(center: number[], scale: number, initMat = this.cameraMat) {
+    this.mutMat(
+      new Matrix4()
+        .makeTranslation(center[0], center[1], 0)
+        .multiply(
+          new Matrix4()
+            .makeScale(scale, scale, 1)
+            .multiply(new Matrix4().makeTranslation(-center[0], -center[1], 0))
+        ),
+      initMat
+    );
+  }
+
+  rotate(center: number[], angleRad: number, initMat = this.cameraMat) {
+    this.mutMat(
+      new Matrix4()
+        .makeTranslation(center[0], center[1], 0)
+        .multiply(
+          new Matrix4()
+            .makeRotationAxis(new Vector3(0, 0, 1), angleRad)
+            .multiply(new Matrix4().makeTranslation(-center[0], -center[1], 0))
+        ),
+      initMat
+    );
+  }
+
+  mutMat(mat: number[] | Matrix4, initMat = this.cameraMat) {
+    if (mat instanceof Matrix4) {
+      initMat = initMat.clone().multiply(mat);
+    } else {
+      initMat = initMat.multiply(new Matrix4().fromArray(mat));
+    }
+    this.setCameraMat(initMat);
+  }
+
+  setCameraMat(mat: number[] | Matrix4) {
+    if (mat instanceof Matrix4) {
+      this.cameraMat = mat;
+    } else {
+      this.cameraMat.fromArray(mat);
+    }
+    this.update();
+  }
+
+  autoBound(padding?: number | number[], stageNames = [".adsord-point"]) {
+    const positions = stageNames.flatMap((item) =>
+      this.tree.stage.find(item).map((s) => {
+        return s.position();
+      })
+    );
+
+    const bound = [
+      Number.MAX_VALUE,
+      Number.MAX_VALUE,
+      -Number.MAX_VALUE,
+      -Number.MAX_VALUE,
+    ];
+    for (const position of positions) {
+      bound[0] = Math.min(bound[0], position.x);
+      bound[1] = Math.min(bound[1], position.y);
+      bound[2] = Math.max(bound[2], position.x);
+      bound[3] = Math.max(bound[3], position.y);
+    }
+    this.setBound(bound, padding);
+  }
+
+  setBound(bound: number[], padding?: number | number[]) {
+    padding = !Array.isArray(padding) ? [padding || 0, padding || 0] : padding;
+    if (padding.length === 1) {
+      padding.push(padding[0]);
+    }
+
+    const realWidth = bound[2] - bound[0];
+    const realHeight = bound[3] - bound[1];
+    const screenWidth = this.tree.stage.width();
+    const screenHeight = this.tree.stage.height();
+
+    // testPoint(this.tree.stage, [
+    //   [bound[0], bound[1]],
+    //   [bound[2], bound[3]],
+    // ]);
+
+    // 计算包含padding的新屏幕尺寸
+    const effectiveWidth = this.tree.stage.width() - padding[0] * 2;
+    const effectiveHeight = this.tree.stage.height() - padding[1] * 2;
+
+    // 计算缩放比例
+    const scaleX = effectiveWidth / realWidth;
+    const scaleY = effectiveHeight / realHeight;
+    const scale = Math.min(scaleX, scaleY); // 选择较小的比例以保持内容比例
+
+    const offsetX = (screenWidth - realWidth * scale) / 2 - bound[0] * scale;
+    const offsetY = (screenHeight - realHeight * scale) / 2 - bound[1] * scale;
+
+    // 创建矩阵并应用缩放
+    const matrix = new Matrix4()
+      .scale(new Vector3(scale, scale, 1))
+      .setPosition(offsetX, offsetY, 0);
+
+    this.clipMat = matrix;
+    this.update();
+  }
+
+  setSize(width: number, height: number) {
+    this.tree.stage.width(width);
+    this.tree.stage.height(height);
+  }
+
+  setTree(tree: Container) {
+    this.tree = tree;
+    if (this.props.move) {
+      this.enableMove();
+    }
+    if (this.props.wheel) {
+      this.enableWheel();
+    }
+  }
+
+  update() {
+    const { stage } = this.tree;
+    const translate = new Vector3();
+    const quaternion = new Quaternion();
+    const scale = new Vector3();
+    const mat = this.clipMat.clone().multiply(this.cameraMat);
+
+    mat.decompose(translate, quaternion, scale);
+    // 将四元数转换为欧拉角
+    const euler = new Euler().setFromQuaternion(quaternion, "XYZ");
+    // 提取绕Z轴的旋转,即二维旋转角度
+    const rotationZ = euler.z; // 在Three.js中,角度是以弧度表示的
+
+    // 更新Konva Stage的位置和缩放
+    stage.position({ x: translate.x, y: translate.y });
+    stage.scale({ x: scale.x, y: scale.y });
+    stage.rotation(MathUtils.radToDeg(rotationZ));
+    this.tree.redraw();
+  }
+}

+ 1 - 0
src/board/plugins/index.ts

@@ -1,2 +1,3 @@
 export * from "./bound-plugin";
 export * from "./history-plugin";
+export * from "./camera-plugin";

+ 9 - 11
src/board/register.ts

@@ -6,18 +6,15 @@ export const register = <
   T extends string,
   R extends Attrib,
   S extends ShapeType,
-  PT extends { [key in string]: Plugin }
+  PT extends { [key in string]: Plugin },
+  TYPE extends EntityType<R, S> = EntityType<R, S>,
+  TYPES extends { [key in T]: TYPE } = { [key in T]: TYPE },
+  DATA extends { [key in T]?: R | R[] } = { [key in T]?: R | R[] }
 >(
-  types: {
-    [key in T]: EntityType<R, S>;
-  },
+  types: TYPES,
   plugins?: PT
 ) => {
-  const initBoard = (
-    dom: HTMLDivElement,
-    data?: { [key in T]?: R | R[] },
-    readonly = false
-  ) => {
+  const initBoard = (dom: HTMLDivElement, data?: DATA, readonly = false) => {
     const container = new Container({
       dom,
       readonly,
@@ -36,9 +33,10 @@ export const register = <
       }
     }
     return {
+      types,
       tree: container,
-      setData(newData: { [key in T]?: R | R[] }) {
-        container.setAttrib({ data: newData });
+      setData(newData: Partial<DATA>) {
+        container.setAttrib({ data: newData as any });
       },
       getData() {
         return container.attrib.data;

+ 70 - 0
src/board/shared/act.ts

@@ -0,0 +1,70 @@
+import { Path } from "konva/lib/shapes/Path";
+import { Rect } from "konva/lib/shapes/Rect";
+import { getRealAbsoluteSize } from "./shape-helper";
+import { Group } from "konva/lib/Group";
+
+export type PathsToActShapeProps = {
+  paths: string[];
+  size: number[];
+  realWidth?: number;
+  offset?: number[];
+  strokeWidth?: number;
+  fixed?: boolean;
+  strokeColor?: string;
+};
+export const pathsToActShape = (props: PathsToActShapeProps, test = false) => {
+  const size = props.size;
+  const realSize = props.realWidth || props.size[0];
+  const scale = realSize / size[0];
+  const realBound = size.map((p) => p * scale);
+  const offset = (props.offset || [0, 0]).map((v) => v * scale);
+  const strokeWidth = props.strokeWidth ? props.strokeWidth * scale : 1;
+  const strokeColor = props.strokeColor || "#000";
+  const paths = props.paths.map(
+    (data, ndx) =>
+      new Path({
+        data,
+        id: `path-${ndx}`,
+        name: `path`,
+        strokeScaleEnabled: false,
+        stroke: strokeColor,
+        fill: strokeColor,
+        scale: { x: scale, y: scale },
+        strokeWidth,
+      })
+  );
+  const rect = new Rect({
+    x: offset[0],
+    y: offset[1],
+    name: "rect",
+    width: realBound[0],
+    height: realBound[1],
+    fill: `rgba(0, 0, 0, ${test ? 0.3 : 0})`,
+  });
+
+  const setStyle = () => {
+    let [width, height] = getRealAbsoluteSize(group, [1, 1]);
+    group.scale({ x: width, y: height });
+  };
+
+  const offsetGroup = new Group();
+  offsetGroup.add(...paths, rect);
+  offsetGroup.x(-realBound[0] / 2);
+  offsetGroup.y(-realBound[1] / 2);
+
+  const group = new Group();
+  group.add(offsetGroup);
+
+  return {
+    shape: group,
+    setData(data) {
+      group.position(data);
+      props.fixed && setStyle();
+    },
+    common() {
+      paths.forEach((path) => {
+        path.fill(strokeColor);
+      });
+    },
+  };
+};

+ 6 - 3
src/board/shared/entity-utils.ts

@@ -20,7 +20,8 @@ export const entityFactory = <
   attrib: T,
   Type: C,
   parent?: Entity<any, any>,
-  extra?: (self: InstanceType<C>) => void
+  extra?: (self: InstanceType<C>) => void,
+  created?: (self: InstanceType<C>) => void
 ) => {
   const entity = new Type({
     attrib,
@@ -36,6 +37,7 @@ export const entityFactory = <
   if (parent.isMounted) {
     entity.mounted();
   }
+  created && created(entity);
   return entity;
 };
 
@@ -57,7 +59,8 @@ export const incEntitysFactoryGenerate = <
 >(
   Type: C,
   parent?: Entity<any, any>,
-  extra?: (self: InstanceType<C>) => void
+  extra?: (self: InstanceType<C>) => void,
+  created?: (self: InstanceType<C>) => void
 ) => {
   let oldAttribs: T[] = [];
 
@@ -73,7 +76,7 @@ export const incEntitysFactoryGenerate = <
   };
 
   const add = (attrib: T) => {
-    const addEntity = entityFactory(attrib, Type, parent, extra);
+    const addEntity = entityFactory(attrib, Type, parent, extra, created);
     cache[attrib.id] = addEntity;
     return addEntity;
   };

+ 1 - 0
src/board/shared/index.ts

@@ -4,3 +4,4 @@ export * from "./public";
 export * from "./shape-mose";
 export * from "./util";
 export * from "./shape-helper";
+export * from "./act";

+ 40 - 10
src/board/shared/math.ts

@@ -23,6 +23,10 @@ export const getLineDist = (p1: number[], p2: number[]) => {
   return new Vector2(p1[0], p1[1]).distanceTo({ x: p2[0], y: p2[1] });
 };
 
+export const getDireDist = (dire: number[]) => {
+  return new Vector2(dire[0], dire[1]).length();
+};
+
 /**
  * 获取线段方向
  * @param line 线段
@@ -149,6 +153,11 @@ export const getLineCenter = (line: number[]) => {
   return [(line[0] + line[2]) / 2, (line[1] + line[3]) / 2];
 };
 
+export const round = (num: number, b: number = 2) => {
+  const scale = Math.pow(10, b);
+  return Math.round(num * scale) / scale;
+};
+
 export enum RelationshipEnum {
   Overlap = "Overlap",
   Intersect = "Intersect",
@@ -194,10 +203,14 @@ export const getLineRelationship = (line1: number[], line2: number[]) => {
   const end2 = new Vector2(line2[2], line2[3]);
 
   // 计算线段的参数方程
-  const t1 =
-    normal2.dot(start2.clone().sub(start1).normalize()) / normal2.dot(dir1);
-  const t2 =
-    normal1.dot(start1.clone().sub(start2).normalize()) / normal1.dot(dir2);
+  const t1 = round(
+    normal2.dot(start2.clone().sub(start1).normalize()) / normal2.dot(dir1),
+    6
+  );
+  const t2 = round(
+    normal1.dot(start1.clone().sub(start2).normalize()) / normal1.dot(dir2),
+    6
+  );
   const n2Negate = normal2.negate();
 
   if (t1 === 0 && t2 === 0) {
@@ -214,16 +227,21 @@ export const getLineRelationship = (line1: number[], line2: number[]) => {
     const denominator =
       (end2.y - start2.y) * (end1.x - start1.x) -
       (end2.x - start2.x) * (end1.y - start1.y);
-    const ua =
+    const ua = round(
       ((end2.x - start2.x) * (start1.y - start2.y) -
         (end2.y - start2.y) * (start1.x - start2.x)) /
-      denominator;
-    const ub =
+        denominator,
+      6
+    );
+    const ub = round(
       ((end1.x - start1.x) * (start1.y - start2.y) -
         (end1.y - start1.y) * (start1.x - start2.x)) /
-      denominator;
+        denominator,
+      6
+    );
 
     if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
+      console.log(ua, ub);
       return RelationshipEnum.Intersect; // 两线段相交
     } else {
       return RelationshipEnum.ExtendIntersect; // 延长可相交
@@ -237,9 +255,21 @@ export const getLineRelationship = (line1: number[], line2: number[]) => {
  * @param line2 线段2
  * @returns 交点坐标
  */
-export const getLineIntersection = (line1: number[], line2: number[]) => {
+export const getLineIntersection = (
+  line1: number[],
+  line2: number[],
+  needIntersects: RelationshipEnum[] = [
+    RelationshipEnum.Overlap,
+    RelationshipEnum.Intersect,
+    RelationshipEnum.ExtendIntersect,
+    RelationshipEnum.Parallel,
+    RelationshipEnum.Join,
+    RelationshipEnum.Equal,
+    RelationshipEnum.None,
+  ]
+) => {
   let relationship = getLineRelationship(line1, line2);
-  if (relationship !== RelationshipEnum.Intersect) {
+  if (!needIntersects.includes(relationship)) {
     return null;
   }
 

+ 35 - 10
src/board/shared/shape-mose.ts

@@ -5,6 +5,17 @@ import { Group } from "konva/lib/Group";
 import { getAbsoluteTransform } from "./shape-helper";
 import { KonvaEventObject } from "konva/lib/Node";
 
+let mouseDisabled = false;
+const dragShapes: (Shape | Group)[] = [];
+export const disableMouse = () => {
+  mouseDisabled = true;
+  dragShapes.forEach((shape) => shape.draggable(false));
+};
+export const enableMouse = () => {
+  mouseDisabled = false;
+  dragShapes.forEach((shape) => shape.draggable(true));
+};
+
 export const openShapeMouseStyles = <T extends Shape | Group>(
   shape: T,
   styles: ShapeStyles,
@@ -30,8 +41,9 @@ export const openShapeMouseStyles = <T extends Shape | Group>(
       : enter
       ? "hover"
       : "common";
-
-    styles[api] && styles[api](ev);
+    if (!mouseDisabled) {
+      styles[api] && styles[api](ev);
+    }
   };
 
   if (styles.hover) {
@@ -106,29 +118,42 @@ export const openShapeDrag = <T extends Shape | Group, D>(
 ) => {
   let readlyData = null as D;
 
-  shape.draggable(true);
+  if (!mouseDisabled) {
+    shape.draggable(true);
+  }
+  dragShapes.push(shape);
   shape.dragBoundFunc((pos, ev) => {
-    let move = pos;
-    if (transform) {
-      const tf = getAbsoluteTransform(shape, tfIncludeSelf).invert();
-      move = tf.point(pos);
+    if (!mouseDisabled) {
+      let move = pos;
+      if (transform) {
+        const tf = getAbsoluteTransform(shape, tfIncludeSelf).invert();
+        move = tf.point(pos);
+      }
+      handler.moveHandler([move.x, move.y], readlyData, ev);
     }
-    handler.moveHandler([move.x, move.y], readlyData, ev);
     return shape.absolutePosition();
   });
 
   if (handler.readyHandler) {
     shape.on("dragstart.drag", (ev) => {
-      readlyData = handler.readyHandler(ev);
+      if (!mouseDisabled) {
+        readlyData = handler.readyHandler(ev);
+      }
     });
   }
 
   if (handler.endHandler) {
     shape.on("dragend.drag", (ev) => {
-      handler.endHandler(readlyData, ev);
+      if (!mouseDisabled) {
+        handler.endHandler(readlyData, ev);
+      }
     });
   }
   return () => {
+    const ndx = dragShapes.indexOf(shape);
+    if (~ndx) {
+      dragShapes.splice(ndx, 1);
+    }
     shape.draggable(false);
     shape.off("dragstart.drag dragend.drag");
   };

+ 26 - 0
src/board/shared/util.ts

@@ -3,6 +3,9 @@ import { Attrib, GetSetPick } from "../type";
 import { inRevise, toRawType } from "./public";
 import { Shape } from "konva/lib/Shape";
 import { Group } from "konva/lib/Group";
+import { Stage } from "konva/lib/Stage";
+import { Circle } from "konva/lib/shapes/Circle";
+import { Entity } from "../packages";
 
 export const setShapeConfig = <T extends Group | Shape>(
   shape: T,
@@ -176,3 +179,26 @@ export const depPartialUpdate = <T>(newData: T, oldData: T): T => {
   }
   return oldData;
 };
+
+export const testPoint = (stage: Stage, points: number[][]) => {
+  points.forEach((point) => {
+    const test = new Circle({
+      fill: "red",
+      radius: 5,
+      x: point[0],
+      y: point[1],
+    });
+    stage.children[0].add(test);
+  });
+};
+
+export const getFlatChildren = (root: Entity) => {
+  const flatChildren: Entity[] = [];
+  for (const child of root.children) {
+    flatChildren.push(child, ...getFlatChildren(child));
+  }
+  return flatChildren;
+};
+
+export const generateId = (attribs: Attrib[]) =>
+  (Math.max(1, ...attribs.map(({ id }) => Number(id))) + 1).toString();

+ 1 - 1
src/board/type.d.ts

@@ -3,7 +3,7 @@ import { Shape } from "konva/lib/Shape";
 import { Group } from "konva/lib/Group";
 import { Layer } from "konva/lib/Layer";
 import { Stage } from "konva/lib/Stage";
-import { Container } from "./packages";
+import { Container, Entity, EntityType } from "./packages";
 
 type ShapeType = Group | Layer | Stage | Shape;
 

+ 39 - 17
src/components/query-board/index.vue

@@ -1,11 +1,22 @@
 <template>
-  <ElButton :disabled="!board?.history.state.hasUndo" @click="board?.history.undo()">
-    撤销
-  </ElButton>
-  <ElButton :disabled="!board?.history.state.hasRedo" @click="board?.history.redo()">
-    重做
-  </ElButton>
-  <ElButton @click="getData">获取数据</ElButton>
+  <div>
+    <ElButton :disabled="!board?.history.state.hasUndo" @click="board?.history.undo()">
+      撤销
+    </ElButton>
+    <ElButton :disabled="!board?.history.state.hasRedo" @click="board?.history.redo()">
+      重做
+    </ElButton>
+    <ElButton @click="getData">获取数据</ElButton>
+    <ElButton v-if="!drawing" @click="drawHandler"> 绘画 </ElButton>
+    <ElButton v-if="drawing" @click="drawing.cancel()"> 停止 </ElButton>
+    <ElButton v-if="drawing" @click="drawing.submit()"> 提交 </ElButton>
+    <template v-if="activeEntity">
+      <ElButton @click="board.copy({ entity: activeEntity, count: 5 })">
+        向右复制5个
+      </ElButton>
+      <ElButton @click="board.del(activeEntity)"> 删除 </ElButton>
+    </template>
+  </div>
   <div
     class="board-layout"
     :style="{ width: width + 'px', height: height + 'px' }"
@@ -15,10 +26,11 @@
 
 <script setup lang="ts">
 import { ref, watch } from "vue";
-import { BoundQueryPlugin, HistoryPlugin, register } from "../../board/";
 import { EditWholeLine } from "../../board/packages";
 import storeData from "./storeData.json";
 import { ElButton } from "element-plus";
+import { createBoard } from "../../app/liantong";
+import { Entity } from "../../board";
 
 withDefaults(defineProps<{ width?: number; height?: number; pixelRation?: number }>(), {
   width: 320,
@@ -26,22 +38,32 @@ withDefaults(defineProps<{ width?: number; height?: number; pixelRation?: number
   pixelRation: 1,
 });
 
+const drawing = ref<ReturnType<EditWholeLine["createPolygon"]>>();
+const drawHandler = () => {
+  drawing.value = board.value.room.createPolygon();
+  drawing.value.promise.then((result) => {
+    console.log(result);
+    drawing.value = null;
+  });
+};
+
+const board = ref<ReturnType<typeof createBoard>>();
 const containerRef = ref<HTMLDivElement>();
-const initBoard = register(
-  { rooms: EditWholeLine },
-  {
-    bound: new BoundQueryPlugin({ move: true, wheel: true }),
-    history: new HistoryPlugin(),
-  }
-);
+const activeEntity = ref<Entity>();
 
-const board = ref<ReturnType<typeof initBoard>>();
 watch(containerRef, (container, _, onClanup) => {
   if (container) {
-    board.value = initBoard(container, storeData, false);
+    board.value = createBoard(container, storeData);
+    board.value.bound.autoBound(60);
+    board.value.getData();
     onClanup(() => board.value.destory);
+
+    board.value.bus.on("active", (entity) => {
+      activeEntity.value = entity;
+    });
   }
 });
+
 const getData = () => {
   console.log(JSON.stringify(board.value?.getData(), null, 4));
 };

+ 8 - 0
src/components/query-board/storeData.json

@@ -122,5 +122,13 @@
         }
       ]
     }
+  ],
+  "pois": [
+    {
+      "id": "2",
+      "x": 360,
+      "y": 232,
+      "type": "bgsjg"
+    }
   ]
 }

+ 86 - 57
src/components/query-board/streData-merge.json

@@ -1,95 +1,124 @@
 {
   "rooms": [
     {
-      "id": "2",
+      "id": "1",
       "points": [
         {
-          "id": "1",
-          "x": 881,
-          "y": 492
+          "x": 360,
+          "y": 232,
+          "id": "2"
         },
         {
-          "id": "2",
-          "x": 136,
-          "y": 237
+          "x": 773,
+          "y": 206,
+          "id": "3"
         },
         {
-          "id": "8",
-          "x": 371,
-          "y": 699
+          "x": 734,
+          "y": 455,
+          "id": "4"
         },
         {
-          "id": "18",
-          "x": 480,
-          "y": 511
+          "x": 339,
+          "y": 440,
+          "id": "5"
         },
         {
-          "id": "19",
-          "x": 83,
-          "y": 647
+          "x": 688,
+          "y": 606,
+          "id": "8"
+        },
+        {
+          "x": 443,
+          "y": 594,
+          "id": "9"
+        },
+        {
+          "x": 556.8756799999999,
+          "y": 448.27376,
+          "id": "10"
+        }
+      ],
+      "lines": [
+        {
+          "id": "2",
+          "pointIds": [
+            "2",
+            "3"
+          ]
         },
         {
           "id": "3",
-          "x": 178,
-          "y": 803
+          "pointIds": [
+            "3",
+            "4"
+          ]
         },
         {
-          "x": 675.8655056700748,
-          "y": 268.3909090194661,
-          "id": "20"
+          "id": "5",
+          "pointIds": [
+            "5",
+            "2"
+          ]
         },
         {
-          "x": 163.5307763543757,
-          "y": 608.0099861089675,
-          "id": "21"
+          "id": "8",
+          "pointIds": [
+            "8",
+            "9"
+          ]
+        },
+        {
+          "id": "10",
+          "pointIds": [
+            "9",
+            "10"
+          ]
         },
         {
-          "x": 579.4292704980454,
-          "y": 468.065825925416,
-          "id": "22"
+          "id": "12",
+          "pointIds": [
+            "4",
+            "10"
+          ]
         },
         {
-          "x": 579.1720366885647,
-          "y": 467.6217710319711,
-          "id": "23"
+          "id": "13",
+          "pointIds": [
+            "10",
+            "5"
+          ]
         },
         {
-          "x": 445.2292582546364,
-          "y": 325.58255334590945,
-          "id": "24"
+          "id": "14",
+          "pointIds": [
+            "10",
+            "8"
+          ]
         }
       ],
       "polygons": [
         {
-          "id": "1",
-          "points": [
-            "1",
-            "22",
-            "23",
-            "24",
+          "id": "2",
+          "lineIds": [
             "2",
-            "20",
-            "22",
-            "8",
-            "18",
-            "23",
-            "20",
-            "24",
-            "21",
-            "19"
+            "3",
+            "12",
+            "13",
+            "5"
           ]
         },
         {
-          "id": "2",
-          "points": [
-            "3",
-            "21",
-            "2",
-            "24",
-            "23",
-            "22",
-            "1"
+          "id": "3",
+          "lineIds": [
+            "10",
+            "14",
+            "8"
           ]
+        },
+        {
+          "id": "4",
+          "lineIds": []
         }
       ]
     }

+ 4 - 2
tsconfig.node.json

@@ -7,5 +7,7 @@
     "allowSyntheticDefaultImports": true,
     "strict": true
   },
-  "include": ["vite.config.ts"]
-}
+  "include": [
+    "vite.config.ts"
+  ]
+}

+ 19 - 0
vite.config.ts

@@ -1,8 +1,27 @@
 import { defineConfig } from "vite";
 import vue from "@vitejs/plugin-vue";
+import { resolve } from "path";
 
 // https://vitejs.dev/config/
 export default defineConfig({
+  build: {
+    lib: {
+      entry: resolve(__dirname, "src/app/liantong/index.ts"),
+      name: "liantong",
+      // the proper extensions will be added
+      fileName: "liantong",
+    },
+    rollupOptions: {
+      // 确保外部化处理那些你不想打包进库的依赖
+      external: ["vue"],
+      output: {
+        // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
+        globals: {
+          vue: "Vue",
+        },
+      },
+    },
+  },
   plugins: [vue()],
   server: {
     port: 9005,