bill 1 год назад
Родитель
Сommit
5d55eae8cb

+ 2 - 0
package.json

@@ -12,7 +12,9 @@
   "dependencies": {
     "element-plus": "^2.7.3",
     "konva": "^9.3.6",
+    "mitt": "^3.0.1",
     "sass": "^1.77.1",
+    "stateshot": "^1.3.5",
     "three": "^0.164.1",
     "vue": "^3.4.21"
   },

+ 6 - 0
pnpm-lock.yaml

@@ -7,6 +7,7 @@ specifiers:
   konva: ^9.3.6
   mitt: ^3.0.1
   sass: ^1.77.1
+  stateshot: ^1.3.5
   three: ^0.164.1
   typescript: ^5.2.2
   vite: ^5.2.0
@@ -18,6 +19,7 @@ dependencies:
   konva: 9.3.6
   mitt: 3.0.1
   sass: 1.77.1
+  stateshot: 1.3.5
   three: 0.164.1
   vue: 3.4.27_typescript@5.4.5
 
@@ -915,6 +917,10 @@ packages:
     resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
     engines: {node: '>=0.10.0'}
 
+  /stateshot/1.3.5:
+    resolution: {integrity: sha512-A/I230vCzTBDHAc2wzCXrH3ofcNnMd9Cs/HhRrxjWJ1YI90cOklljX9XATTdU45T4W/c/+g+jBtS/oQLs+Wkdw==}
+    dev: false
+
   /three/0.164.1:
     resolution: {integrity: sha512-iC/hUBbl1vzFny7f5GtqzVXYjMJKaTPxiCxXfrvVdBi1Sf+jhd1CAkitiFwC7mIBFCo3MrDLJG97yisoaWig0w==}
     dev: false

+ 4 - 7
src/App.vue

@@ -1,13 +1,10 @@
-
 <template>
   <QueryBoard :width="width" :height="height" />
 </template>
 
 <script setup lang="ts">
-import QueryBoard from './components/query-board/index.vue'
-
-const width = window.innerWidth - 50
-const height = window.innerHeight - 50
-
+import QueryBoard from "./components/query-board/index.vue";
 
-</script>
+const width = window.innerWidth - 50;
+const height = window.innerHeight - 50;
+</script>

+ 1 - 1
src/board/env.ts

@@ -1 +1 @@
-export const DEV = false;
+export const DEV = true;

+ 15 - 3
src/board/packages/container.ts

@@ -1,4 +1,4 @@
-import { watch } from "vue";
+import { watch, watchEffect } from "vue";
 import { Attrib, ShapeType } from "../type";
 import { Layer } from "konva/lib/Layer";
 import { Entity, EntityProps, EntityType } from "./entity";
@@ -10,6 +10,7 @@ import {
 import { Shape } from "konva/lib/Shape";
 import { Group } from "konva/lib/Group";
 import { getAbsoluteTransform } from "../shared";
+import mitt, { Emitter } from "mitt";
 
 export type ContainerProps<
   T extends string = string,
@@ -30,6 +31,11 @@ export class Container<
   S extends ShapeType = ShapeType,
   C extends EntityType<R, S> = EntityType<R, S>
 > extends Entity<Attrib & { data: { [key in T]?: R | R[] } }, Layer> {
+  bus: Emitter<{
+    dataChange: void;
+    dataChangeBefore: void;
+    dataChangeAfter: void;
+  }>;
   tempLayer: Layer;
   config: ContainerProps<T, R, S, C>;
   entrys = {} as { [key in T]: InstanceType<C>[] };
@@ -62,6 +68,7 @@ export class Container<
       width: dom.offsetWidth,
       height: dom.offsetHeight,
     });
+    this.bus = mitt();
     // this.tempLayer = new Layer();
     // this.stage.add(this.tempLayer);
   }
@@ -82,7 +89,12 @@ export class Container<
   initIncFactory() {
     const types = this.config.types;
     for (const key in types) {
-      this.incFactorys[key] = incEntitysFactoryGenerate(types[key], this);
+      this.incFactorys[key] = incEntitysFactoryGenerate(
+        types[key],
+        this,
+        () => {},
+        true
+      );
     }
   }
 
@@ -113,7 +125,7 @@ export class Container<
         return result;
       },
       this.diffRedraw.bind(this),
-      { immediate: true, flush: "sync" }
+      { immediate: true, flush: "pre" }
     );
   }
 

+ 19 - 6
src/board/packages/entity.ts

@@ -5,7 +5,7 @@ import { Group } from "konva/lib/Group";
 import { Layer } from "konva/lib/Layer";
 import { Stage } from "konva/lib/Stage";
 import { depPartialUpdate } from "../shared/util";
-import { reactive, watchEffect } from "vue";
+import { reactive, watch, watchEffect } from "vue";
 import {
   DragHandlers,
   openShapeDrag,
@@ -97,14 +97,19 @@ export abstract class Entity<
   }
 
   private destoryReactive: () => void;
-  protected initReactive() {
+  protected initReactive(flush: "pre" | "post" | "sync" = "pre") {
     this.destoryReactive && this.destoryReactive();
-    return watchEffect(
+    let isStop = false;
+    const stop = watchEffect(
       () => {
-        this.diffRedraw();
+        isStop || this.diffRedraw();
       },
-      { flush: "post" }
+      { flush }
     );
+    return () => {
+      isStop = true;
+      stop();
+    };
   }
 
   init() {
@@ -131,10 +136,18 @@ export abstract class Entity<
     this.teleport = parentShape as unknown as ParentShapeType<S>;
     this.setZIndex(this.zIndex);
 
-    this.setAttrib(this.props.attrib);
     if (this.props.reactive) {
       this.destoryReactive = this.initReactive();
     }
+    watch(
+      () => this.attrib.id,
+      (newId, oldId) => {
+        if (newId !== oldId) {
+          console.error("changeId", newId, oldId);
+        }
+      },
+      { flush: "sync" }
+    );
   }
 
   isMounted = false;

+ 33 - 3
src/board/packages/whole-line/editable/edit-whole-line.ts

@@ -9,6 +9,7 @@ import {
 } from "../view/";
 import {
   getWholeLineLine,
+  getWholeLineLinesRawByPoint,
   getWholeLinePolygonPoints,
   getWholeLinePolygonRaw,
   mergeWholeLinePointsByPoint,
@@ -17,6 +18,8 @@ import {
 } from "../service";
 import { EntityDragHandlers, openEntityDrag } from "../../../shared/shape-mose";
 import { ShapeType } from "../../../type";
+import { getAdsorbPosition } from "../../../shared/adsorb";
+import { getRealAbsoluteSize } from "../../../shared";
 
 export class EditWholeLine<
   W extends WholeLineAttrib = WholeLineAttrib,
@@ -73,6 +76,7 @@ export class EditWholeLine<
   createPolygon() {
     let prePolygonId: string;
     this.endCreatePolygon && this.endCreatePolygon();
+    this.container.bus.emit("dataChangeBefore");
     this.endCreatePolygon = penWholeLinePoygonsEditWithHelperShapesMouse(
       {
         quotePoint: true,
@@ -90,6 +94,9 @@ export class EditWholeLine<
           }
           prePolygonId = pid;
         },
+        quitHandler: () => {
+          this.container.bus.emit("dataChangeAfter");
+        },
       },
       this.shape
     );
@@ -148,13 +155,36 @@ export class EditWholeLine<
     this.pointDragHandler = {
       readyHandler: (attrib) => {
         // this.container.tempDraw(...this.getChildByPointsAttrib([attrib]));
+        this.container.bus.emit("dataChangeBefore");
         return [attrib.x, attrib.y];
       },
       moveHandler: (pointAttrib, move) => {
-        pointAttrib.x = move[0];
-        pointAttrib.y = move[1];
+        const exclusionIds = getWholeLineLinesRawByPoint(
+          this.attrib,
+          pointAttrib
+        ).map((line) => WholeLineLine.namespace + line.id);
+        exclusionIds.push(WholeLinePoint.namespace + pointAttrib.id);
+
+        const [radius] = getRealAbsoluteSize(this.container.stage, [10, 10]);
+        const [radiusInner] = getRealAbsoluteSize(
+          this.container.stage,
+          [500, 500]
+        );
+
+        const position = getAdsorbPosition({
+          tree: this.container,
+          position: move,
+          radius,
+          radiusInner,
+          exclusionIds: exclusionIds,
+        });
+        pointAttrib.x = position[0];
+        pointAttrib.y = position[1];
+      },
+      endHandler: (attrib) => {
+        this.pointAfterHandler(attrib);
+        this.container.bus.emit("dataChangeAfter");
       },
-      endHandler: this.pointAfterHandler.bind(this),
     };
     super.init();
   }

+ 7 - 0
src/board/packages/whole-line/service/whole-line-base.ts

@@ -25,6 +25,13 @@ export const getWholeLinePoints = (config: WholeLineAttrib, ids?: string[]) => {
   }
 };
 
+export const getWholeLineLinesRawByPoint = (
+  config: WholeLineAttrib,
+  point: WholeLinePointAttrib
+) => {
+  return config.lines.filter(({ pointIds }) => pointIds.includes(point.id));
+};
+
 export const getWholeLineLineRaw = (config: WholeLineAttrib, lineId: string) =>
   config.lines.find(({ id }) => id === lineId);
 

+ 18 - 5
src/board/packages/whole-line/service/whole-line-edit.ts

@@ -17,6 +17,7 @@ import { Attrib, ShapeType } from "../../../type";
 import {
   WholeLineChange,
   generateWholeLinePoygonId,
+  getWholeLinePoint,
   getWholeLinePolygonPoints,
   mergeChange,
   wholeLineAddLineByPointIds,
@@ -159,14 +160,26 @@ export const penWholeLinePoygonsEdit = <
       if (polyginAttrib.id === prevId) {
         return { change, isClose: false };
       }
+    }
+    const polygonPoints = getWholeLinePolygonPoints(config, polyginAttrib.id);
+    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 {
-      const position = adsorbRadius
-        ? getAdsorbPosition({ tree, pixel, radius: adsorbRadius })
-        : tree.getRealFromStage(pixel);
-      pointAttrib = pointAttribFactory(position) as P & Attrib;
+      position = tree.getRealFromStage(pixel);
     }
+    pointAttrib = pointAttribFactory(position) as P & Attrib;
 
-    const polygonPoints = getWholeLinePolygonPoints(config, polyginAttrib.id);
     const curNdx = polygonPoints.findIndex(({ id }) => id === pointAttrib.id);
     const isClose = curNdx === 0 && (!autoClose || polygonPoints.length >= 3);
     // 存在的情况下删除

+ 3 - 2
src/board/packages/whole-line/service/whole-line-helper.ts

@@ -13,7 +13,7 @@ import {
 } from "./whole-line-edit";
 import addMouseIco from "../../../shared/cursor/pic_pen_a.ico";
 import delMouseIco from "../../../shared/cursor/pic_pen_r.ico";
-import { getRealAbsoluteSize } from "../../../shared";
+import { getAbsoluteTransform, getRealAbsoluteSize } from "../../../shared";
 import { lineShapeFactory, pointShapeFactory } from "../style";
 import { Layer } from "konva/lib/Layer";
 import { Group } from "konva/lib/Group";
@@ -103,10 +103,10 @@ 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);
-  stage.on("mousedown.editPolygonsMode", clickHandler);
   document.addEventListener("keydown", quitHandler);
 
   return {
@@ -228,6 +228,7 @@ export const penWholeLinePoygonsEditWithHelperShapesMouse = <
       ...props,
       quitHandler: () => {
         stopWatchHelper();
+        console.log("结束了");
         props.quitHandler && props.quitHandler();
       },
     });

+ 4 - 0
src/board/packages/whole-line/view/whole-line-line.ts

@@ -76,6 +76,10 @@ export class WholeLineLine<
     return result;
   }
 
+  protected initReactive() {
+    return super.initReactive("post");
+  }
+
   mounted(): void {
     super.mounted();
     this.actShape.common(null);

+ 1 - 1
src/board/packages/whole-line/view/whole-line.ts

@@ -171,7 +171,7 @@ export class WholeLine<
         lineIds: this.attrib.lines.map(({ id }) => id),
       }),
       this.diffRedraw.bind(this),
-      { immediate: true, flush: "sync" }
+      { immediate: true, flush: "pre" }
     );
   }
 }

+ 6 - 0
src/board/plugins/bound-plugin.ts

@@ -141,6 +141,12 @@ export class BoundQueryPlugin {
 
   setTree(tree: Container) {
     this.tree = tree;
+    if (!this.bound) {
+      const size = tree.stage.getSize();
+      const position = tree.stage.position();
+      this.bound = [position.x, position.y, size.width, size.height];
+    }
+
     if (this.props.move) {
       this.enableMove();
     }

+ 88 - 0
src/board/plugins/history-plugin.ts

@@ -0,0 +1,88 @@
+import { reactive, watchEffect } from "vue";
+import { Container } from "../packages";
+import { History } from "stateshot";
+import { inRevise } from "../shared";
+
+export class HistoryPlugin<T = any> {
+  tree: Container;
+  history: History;
+  state = reactive({
+    hasUndo: false,
+    hasRedo: false,
+  });
+
+  disabled: boolean = true;
+  setDisable(disabled: boolean) {
+    this.disabled = disabled;
+    if (disabled) {
+      this.state.hasRedo = this.state.hasUndo = false;
+    } else {
+      this.syncState();
+    }
+  }
+
+  private syncState() {
+    if (!this.history || this.disabled) return;
+    this.state.hasRedo = this.history.hasRedo;
+    this.state.hasUndo = this.history.hasUndo;
+  }
+
+  setTree(tree: Container) {
+    this.tree = tree;
+    this.history = new History();
+    this.setDisable(false);
+    this.push(this.tree.attrib as any);
+    this.timelyPush();
+  }
+
+  undo() {
+    if (!this.disabled && this.history.hasUndo) {
+      this.history.undo();
+      this.tree.setAttrib(this.history.get());
+      this.syncState();
+    }
+  }
+
+  redo() {
+    if (!this.disabled && this.history.hasRedo) {
+      this.history.redo();
+      this.tree.setAttrib(this.history.get());
+      this.syncState();
+    }
+  }
+
+  push(data: T) {
+    const eData = this.history.get();
+    eData && delete eData.children;
+    if (inRevise(data, eData)) {
+      data = JSON.parse(JSON.stringify(data));
+      this.history.pushSync(data);
+      this.syncState();
+      console.log("push", data);
+    }
+  }
+
+  private timelyPushDestory: () => void;
+  private timelyPush() {
+    this.timelyPushDestory && this.timelyPushDestory();
+    const handler = () => {
+      this.push(this.tree.attrib as any);
+    };
+    const beforeHandler = () => {
+      this.setDisable(true);
+    };
+    const afterHandler = () => {
+      this.setDisable(false);
+      handler();
+    };
+    this.tree.bus.on("dataChange", handler);
+    this.tree.bus.on("dataChangeBefore", beforeHandler);
+    this.tree.bus.on("dataChangeAfter", afterHandler);
+    this.timelyPushDestory = () => {
+      this.tree.bus.off("dataChange", handler);
+      this.tree.bus.off("dataChangeBefore", beforeHandler);
+      this.tree.bus.off("dataChangeAfter", afterHandler);
+      this.timelyPushDestory = null;
+    };
+  }
+}

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

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

+ 3 - 4
src/board/register.ts

@@ -6,13 +6,12 @@ export const register = <
   T extends string,
   R extends Attrib,
   S extends ShapeType,
-  PT extends string,
-  P extends Plugin
+  PT extends { [key in string]: Plugin }
 >(
   types: {
     [key in T]: EntityType<R, S>;
   },
-  plugins?: { [key in PT]: P }
+  plugins?: PT
 ) => {
   const initBoard = (
     dom: HTMLDivElement,
@@ -28,7 +27,7 @@ export const register = <
     });
     container.init();
 
-    const pluginApis = {} as { [key in PT]: P };
+    const pluginApis = {} as PT;
     if (plugins) {
       for (const key in plugins) {
         const plugin = plugins[key];

+ 148 - 27
src/board/shared/adsorb.ts

@@ -1,13 +1,23 @@
 import { Line } from "konva/lib/shapes/Line";
 import { Container } from "../packages";
-import { getLine2Angle, getLineDist, getLineProjection } from "./math";
+import {
+  createLineByDire,
+  getLine2Angle,
+  getLineDire,
+  getLineDist,
+  getLineProjection,
+  getRotateDire,
+  getVerticaLineDire,
+} from "./math";
 import { MathUtils } from "three";
+import { getRealAbsoluteSize } from "./shape-helper";
 
 type AdsorbBaseProps = { tree: Container; position: number[] };
 
 export type AdsorbPointProps = AdsorbBaseProps & {
   refPointName?: string;
   radius?: number;
+  exclusionIds?: string[];
 };
 /**
  * 参考点吸附
@@ -16,13 +26,28 @@ export const getAdsorbPointPosition = ({
   tree,
   position,
   refPointName = "adsord-point",
+  exclusionIds = [],
   radius = 10,
 }: AdsorbPointProps) => {
-  const refPositions = tree.stage.find(`.${refPointName}`).map((ref) => {
-    const refPos = ref.position();
-    return [refPos.x, refPos.y];
-  });
+  const refPositions = tree.stage
+    .find(`.${refPointName}`)
+    .filter((point) => !exclusionIds.includes(point.id()))
+    .map((ref) => {
+      const refPos = ref.position();
+      return [refPos.x, refPos.y];
+    });
+  return getAdsorbPointPositionRaw({ refPositions, radius, position });
+};
 
+export const getAdsorbPointPositionRaw = ({
+  position,
+  radius = 10,
+  refPositions,
+}: {
+  refPositions: number[][];
+  position: number[];
+  radius: number;
+}) => {
   let adsorbPosition: number[] | null = null;
   let minDis = Number.MAX_VALUE;
   for (const refPosition of refPositions) {
@@ -38,6 +63,8 @@ export const getAdsorbPointPosition = ({
 
 type AdsorbLineProps = AdsorbBaseProps & {
   refLineName?: "adsord-line";
+  exclusionIds?: string[];
+  radiusInner?: number;
   angle?: number;
 };
 
@@ -48,47 +75,141 @@ export const getAdsorbLinePosition = ({
   tree,
   position,
   refLineName = "adsord-line",
+  exclusionIds = [],
   angle = 3,
+  radiusInner = 50,
 }: AdsorbLineProps) => {
-  const refLines = tree.stage.find<Line>(`.${refLineName}`).flatMap((ref) => {
-    const line = ref.points();
-
-    return [line];
-  });
-
-  angle = MathUtils.degToRad(angle);
+  const refLines = tree.stage
+    .find<Line>(`.${refLineName}`)
+    .filter((line) => {
+      if (exclusionIds.includes(line.id())) {
+        return false;
+      }
+      const points = line.points();
+      return (
+        getLineDist([points[0], points[1]], position) < radiusInner ||
+        getLineDist([points[2], points[3]], position) < radiusInner
+      );
+    })
+    .map((ref) => {
+      return ref.points();
+    });
+  return getAdsorbLinePositionRaw({ position, refLines, angle });
+};
 
-  let minAngle = Number.MAX_VALUE;
+const adsorbLineAngles = [0, 90, 180, 270, 360];
+export const getAdsorbLinePositionRaw = ({
+  position,
+  refLines,
+  angle = 3,
+}: {
+  position: number[];
+  refLineName?: "adsord-line";
+  refLines: number[][];
+  angle?: number;
+}) => {
+  let adsorbAngle: number | null = null;
   let adsorbLine: number[] | null = null;
 
+  let isAdsorb = false;
   for (const refLine of refLines) {
-    let line = refLine;
-    let cAngle = Math.abs(getLine2Angle(line, [line[0], line[1], ...position]));
-    if (cAngle > angle) {
-      line = [refLine[2], refLine[3], refLine[0], refLine[1]];
-      cAngle = Math.abs(getLine2Angle(line, [line[0], line[1], ...position]));
+    const points = [
+      [refLine[0], refLine[1]],
+      [refLine[2], refLine[3]],
+    ];
+    const cAngles = points.map((start) => {
+      let angle = MathUtils.radToDeg(
+        getLine2Angle(refLine, [...start, ...position])
+      );
+      return (angle + 360) % 360;
+    });
+
+    let i = 0,
+      j = 0;
+    for (i = 0; i < cAngles.length; i++) {
+      for (j = 0; j < adsorbLineAngles.length; j++) {
+        const adAngle = adsorbLineAngles[j];
+        if (cAngles[i] >= adAngle - angle && cAngles[i] <= adAngle + angle) {
+          break;
+        }
+      }
+      if (j !== adsorbLineAngles.length) {
+        break;
+      }
     }
 
-    if (cAngle < angle && cAngle < minAngle) {
-      minAngle = cAngle;
-      adsorbLine = line;
+    if (j !== adsorbLineAngles.length || i !== cAngles.length) {
+      adsorbLine = refLine;
+      adsorbAngle = adsorbLineAngles[j];
+
+      const rdire = getRotateDire(
+        getLineDire(adsorbLine),
+        MathUtils.degToRad(adsorbAngle)
+      );
+      const start =
+        getLineDist([adsorbLine[0], adsorbLine[1]], position) >
+        getLineDist([adsorbLine[2], adsorbLine[3]], position)
+          ? [adsorbLine[2], adsorbLine[3]]
+          : [adsorbLine[0], adsorbLine[1]];
+      adsorbLine = createLineByDire(rdire, start, 10);
+      position = getLineProjection(adsorbLine, position).point;
+      isAdsorb = true;
     }
   }
+  if (isAdsorb) {
+    return position;
+  }
+};
 
-  return adsorbLine && getLineProjection(adsorbLine, position).point;
+export type AdsorbSelfLinesProps = {
+  points?: number[][];
+  angle?: number;
+  position: number[];
 };
+export const getAdsorbSelfLinesPosition = ({
+  position,
+  points,
+  angle,
+}: AdsorbSelfLinesProps) => {
+  if (!points?.length) return;
 
-export const getAdsorbSelfLinesPosition = () => {};
+  if (points.length === 1) {
+    const point = points[0];
+    const refLines = [
+      [point[0] - 50, point[1], point[0] + 100, point[1]],
+      [point[0], point[1] - 50, point[0], point[1] + 50],
+    ];
+    return getAdsorbLinePositionRaw({ position, refLines, angle });
+  } else {
+    const last = points.slice(points.length - 2).flatMap((a) => a);
+    const first = points.slice(0, 2).flatMap((a) => a);
+    const vlines = [
+      createLineByDire(getVerticaLineDire(last), [last[2], last[3]], 10),
+      createLineByDire(getVerticaLineDire(first), [first[0], last[1]], 10),
+    ];
+
+    // 垂直最后一段
+    return (
+      getAdsorbLinePositionRaw({
+        position,
+        refLines: vlines,
+        angle,
+      }) || position
+    );
+  }
+};
 
 export type AdsorbProps = Omit<
-  AdsorbPointProps & AdsorbLineProps,
+  AdsorbPointProps & AdsorbLineProps & AdsorbSelfLinesProps,
   "position"
-> & { pixel: number[] };
+> & { pixel?: number[]; position?: number[]; radiusInner?: number };
 
 export const getAdsorbPosition = (props: AdsorbProps) => {
-  const position = props.tree.getPixelFromStage(props.pixel);
+  const position = props.position || props.tree.getRealFromStage(props.pixel);
   let refPosition: number[];
-  if ((refPosition = getAdsorbPointPosition({ ...props, position }))) {
+  if ((refPosition = getAdsorbSelfLinesPosition({ ...props, position }))) {
+    return refPosition;
+  } else if ((refPosition = getAdsorbPointPosition({ ...props, position }))) {
     return refPosition;
   } else if ((refPosition = getAdsorbLinePosition({ ...props, position }))) {
     return refPosition;

+ 8 - 2
src/board/shared/entity-utils.ts

@@ -1,5 +1,6 @@
 import { EntityType, Entity } from "../packages/entity";
 import { Attrib, ShapeType } from "../type";
+import { inRevise } from "./public";
 import { getChangeAllPoart } from "./util";
 
 const getExtendsProps = (parent: Entity<any, any>) => {
@@ -56,7 +57,8 @@ export const incEntitysFactoryGenerate = <
 >(
   Type: C,
   parent?: Entity<any, any>,
-  extra?: (self: InstanceType<C>) => void
+  extra?: (self: InstanceType<C>) => void,
+  dev = false
 ) => {
   let oldAttribs: T[] = [];
 
@@ -88,7 +90,11 @@ export const incEntitysFactoryGenerate = <
     const adds = addPort.map((id) => add(findAttrib(attribs, id)));
 
     const upds = changePort.map((id) => {
-      cache[id].setAttrib(findAttrib(attribs, id));
+      const newAttrib = findAttrib(attribs, id);
+      if (inRevise(newAttrib, cache[id].attrib)) {
+        console.log("setAttrib");
+        cache[id].setAttrib(findAttrib(attribs, id));
+      }
       return cache[id];
     });
 

+ 47 - 1
src/board/shared/math.ts

@@ -1,4 +1,4 @@
-import { Vector2, Line3, Vector3, ShapeUtils } from "three";
+import { Vector2, Line3, Vector3, ShapeUtils, Matrix3 } from "three";
 
 const getLineDireVector2 = (line: number[]) =>
   new Vector2(line[2] - line[0], line[3] - line[1]).normalize();
@@ -42,6 +42,52 @@ export const getVerticalDire = (dire: number[]) => {
 };
 
 /**
+ * 获取旋转指定度数后的向量
+ * @param dire 远向量
+ * @param angleRad 旋转角度
+ * @returns 旋转后向量
+ */
+export const getRotateDire = (dire: number[], angleRad: number) => {
+  // 创建一个二维旋转矩阵
+  const rotationMatrix = new Matrix3().set(
+    Math.cos(angleRad),
+    -Math.sin(angleRad),
+    0,
+    Math.sin(angleRad),
+    Math.cos(angleRad),
+    0,
+    0,
+    0,
+    1
+  );
+  // 将原向量复制到新向量中
+  const rotatedVector = new Vector2(dire[0], dire[1]);
+  // 使用旋转矩阵对向量进行旋转
+  rotatedVector.applyMatrix3(rotationMatrix);
+  // 返回旋转后的向量
+  return rotatedVector.toArray();
+};
+
+/**
+ * 创建线段
+ * @param dire 向量
+ * @param start 起始点
+ * @param dis 长度
+ * @returns 线段
+ */
+export const createLineByDire = (
+  dire: number[],
+  start: number[],
+  dis: number
+) => {
+  const temp = new Vector2(dire[0], dire[1]);
+  return [
+    ...start,
+    ...temp.multiplyScalar(dis).add({ x: start[0], y: start[1] }).toArray(),
+  ];
+};
+
+/**
  * 获取线段的垂直方向向量
  * @param line 原线段
  * @returns 垂直向量

+ 25 - 6
src/board/shared/util.ts

@@ -131,17 +131,36 @@ export const depPartialUpdate = <T>(newData: T, oldData: T): T => {
 
   switch (type) {
     case "Array":
-      for (let i = 0; i < nData.length; i++) {
-        oData[i] = depPartialUpdate(nData[i], oData[i]);
-      }
-      while (oData.length !== nData.length) {
-        oData.pop();
+      if (nData[0]?.id || oData[0]?.id) {
+        var { changePort, addPort, delPort } = getChangeAllPoart(nData, oData);
+
+        addPort.forEach((qid) => {
+          oData.push(nData.find(({ id }) => id === qid));
+        });
+        delPort.forEach((dId) => {
+          const ndx = oData.findIndex(({ id }) => id === dId);
+          ~ndx && oData.splice(ndx, 1);
+        });
+        changePort.forEach((cId) => {
+          const nItem = nData.find(({ id }) => id === cId);
+          const ndx = oData.findIndex(({ id }) => id === cId);
+
+          oData[ndx] = depPartialUpdate(nItem, oData[ndx]);
+        });
+      } else {
+        for (let i = 0; i < nData.length; i++) {
+          oData[i] = depPartialUpdate(nData[i], oData[i]);
+        }
+        while (oData.length !== nData.length) {
+          oData.pop();
+        }
       }
+
       break;
     case "Object":
       const oKeys = Object.keys(oData).sort();
       const nKeys = Object.keys(nData).sort();
-      const { addPort, delPort, holdPort } = getChangePart(nKeys, oKeys);
+      var { addPort, delPort, holdPort } = getChangePart(nKeys, oKeys);
 
       for (let i = 0; i < holdPort.length; i++) {
         oData[oKeys[i]] = depPartialUpdate(

+ 20 - 8
src/components/query-board/index.vue

@@ -1,4 +1,10 @@
 <template>
+  <ElButton :disabled="!board?.history.state.hasUndo" @click="board?.history.undo()">
+    撤销
+  </ElButton>
+  <ElButton :disabled="!board?.history.state.hasRedo" @click="board?.history.redo()">
+    重做
+  </ElButton>
   <div
     class="board-layout"
     :style="{ width: width + 'px', height: height + 'px' }"
@@ -8,9 +14,10 @@
 
 <script setup lang="ts">
 import { ref, watch } from "vue";
-import { register } from "../../board/";
+import { BoundQueryPlugin, HistoryPlugin, register } from "../../board/";
 import { EditWholeLine } from "../../board/packages";
 import storeData from "./storeData.json";
+import { ElButton } from "element-plus";
 
 withDefaults(defineProps<{ width?: number; height?: number; pixelRation?: number }>(), {
   width: 320,
@@ -19,21 +26,26 @@ withDefaults(defineProps<{ width?: number; height?: number; pixelRation?: number
 });
 
 const containerRef = ref<HTMLDivElement>();
-const initBoard = register({ rooms: EditWholeLine });
+const initBoard = register(
+  { rooms: EditWholeLine },
+  {
+    bound: new BoundQueryPlugin({ move: true, wheel: true }),
+    history: new HistoryPlugin(),
+  }
+);
+
+const board = ref<ReturnType<typeof initBoard>>();
 watch(containerRef, (container, _, onClanup) => {
   if (container) {
-    const board = initBoard(container, storeData, false);
-    console.log(board.tree);
-    window.board = board;
-    onClanup(() => board.destory);
+    board.value = initBoard(container, storeData, false);
+    onClanup(() => board.value.destory);
   }
 });
 </script>
 
 <style lang="scss" scoped>
 .board-layout {
-  position: relative;
-  display: inline-block;
+  position: absolute;
 
   canvas {
     display: block;