Explorar o código

Merge branch 'master' of http://192.168.0.115:3000/bill/traffic-laser

xzw %!s(int64=2) %!d(string=hai) anos
pai
achega
3669a04337

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 1
server/test/a0k4xu045_202305311600080410/attach/sceneStore


BIN=BIN
src/assets/images/empty-label.png


+ 24 - 33
src/components/base/components/input/textarea.vue

@@ -1,10 +1,10 @@
 <template>
   <div
-    class="input textarea" 
-    :class="{suffix: $slots.icon || maxlength, disabled, right}"
-    ref="textRef">
-      {{modelValue}}
-    <textarea 
+    class="input textarea"
+    :class="{ suffix: $slots.icon || maxlength, disabled, right }"
+    ref="textRef"
+  >
+    <textarea
       class="ui-text"
       :value="modelValue"
       @input="inputHandler"
@@ -18,51 +18,42 @@
       v-bind="other"
     />
     <span class="replace"></span>
-    <span v-if="$slots.icon || props.maxlength"  class="retouch">
+    <span v-if="$slots.icon || props.maxlength" class="retouch">
       <slot name="icon" />
       <span v-if="props.maxlength" class="len">
-        <span>{{modelValue.length}}</span> / {{maxlength}}
+        <span>{{ modelValue.length }}</span> / {{ maxlength }}
       </span>
     </span>
   </div>
 </template>
 
 <script setup>
-import { textareaPropsDesc } from './state'
-import { 
-  nextTick,
-  ref
-} from 'vue'
+import { textareaPropsDesc } from "./state";
+import { nextTick, ref } from "vue";
 const props = defineProps({
   type: {
     type: String,
-    default: 'text'
+    default: "text",
   },
-  ...textareaPropsDesc
-})
+  ...textareaPropsDesc,
+});
 
-console.log(props)
-const emit = defineEmits([
-  'update:modelValue',
-  'focus',
-  'blur',
-  'click'
-])
-const textRef = ref(null)
-const inputRef = ref(null)
+console.log(props);
+const emit = defineEmits(["update:modelValue", "focus", "blur", "click"]);
+const textRef = ref(null);
+const inputRef = ref(null);
 
-
-const inputHandler = ev => {
-  emit('update:modelValue', ev.target.value)
+const inputHandler = (ev) => {
+  emit("update:modelValue", ev.target.value);
   nextTick(() => {
     if (ev.target.value !== props.modelValue.toString()) {
-      ev.target.value = props.modelValue.toString()
+      ev.target.value = props.modelValue.toString();
     }
-  })
-}
+  });
+};
 
 defineExpose({
   root: textRef,
-  input: inputRef
-})
-</script>
+  input: inputRef,
+});
+</script>

+ 0 - 1
src/graphic/CanvasStyle/focus.js

@@ -93,7 +93,6 @@ const DoubleArrowLine = {
   strokeStyle: "red",
 };
 
-
 const TestPoint = {
   strokeStyle: "rgba(0,0,0,0)",
   fillStyle: "#fff",

+ 107 - 21
src/graphic/Controls/MoveRoad.js

@@ -1024,14 +1024,14 @@ export default class MoveRoad {
       rightCurveEdge.points[i].x += dx;
       rightCurveEdge.points[i].y += dy;
     }
-    leftCurveEdge.start.x += dx;
-    leftCurveEdge.start.y += dy;
-    leftCurveEdge.end.x += dx;
-    leftCurveEdge.end.y += dy;
-    rightCurveEdge.start.x += dx;
-    rightCurveEdge.start.y += dy;
-    rightCurveEdge.end.x += dx;
-    rightCurveEdge.end.y += dy;
+    // leftCurveEdge.start.x += dx;
+    // leftCurveEdge.start.y += dy;
+    // leftCurveEdge.end.x += dx;
+    // leftCurveEdge.end.y += dy;
+    // rightCurveEdge.start.x += dx;
+    // rightCurveEdge.start.y += dy;
+    // rightCurveEdge.end.x += dx;
+    // rightCurveEdge.end.y += dy;
     curveRoad.curves = mathUtil.getCurvesByPoints(curveRoad.points);
     leftCurveEdge.curves = mathUtil.getCurvesByPoints(leftCurveEdge.points);
     rightCurveEdge.curves = mathUtil.getCurvesByPoints(rightCurveEdge.points);
@@ -1169,12 +1169,36 @@ export default class MoveRoad {
     const parent = edge.getParent();
     const road = dataService.getRoad(parent);
     const line = roadService.getMidLine(road);
+    let join = mathUtil.getJoinLinePoint(position, line);
 
     let dir = "left";
     if (road.rightEdgeId == edgeId) {
       dir = "right";
     }
 
+    let leftEdge = dataService.getRoadEdge(road.leftEdgeId);
+    const line1 = mathUtil.createLine1(leftEdge.start, leftEdge.end);
+    let leftJoin = mathUtil.getJoinLinePoint(position, line1);
+    let rightEdge = dataService.getRoadEdge(road.rightEdgeId);
+    const line2 = mathUtil.createLine1(rightEdge.start, rightEdge.end);
+    let rightJoin = mathUtil.getJoinLinePoint(position, line2);
+
+    if (
+      dir == "left" &&
+      (mathUtil.isContainForSegment(position, join, rightJoin) ||
+        mathUtil.isContainForSegment(rightJoin, join, position))
+    ) {
+      return;
+    }
+
+    if (
+      dir == "right" &&
+      (mathUtil.isContainForSegment(position, join, leftJoin) ||
+        mathUtil.isContainForSegment(leftJoin, join, position))
+    ) {
+      return;
+    }
+
     const newWidth = mathUtil.getDisForPoinLine(position, line);
     if (
       newWidth > Constant.minRoadSideWidth &&
@@ -1188,25 +1212,87 @@ export default class MoveRoad {
     const curveEdge = dataService.getCurveRoadEdge(curveEdgeId);
     const parent = curveEdge.getParent();
     const curveRoad = dataService.getCurveRoad(parent);
-    let joinInfo = null;
+    //let joinInfo = null;
     let dir = "left";
     if (curveRoad.rightEdgeId == curveEdgeId) {
       dir = "right";
     }
 
-    if (dir == "left") {
-      joinInfo = mathUtil.getHitInfoForCurve(
-        position,
-        curveRoad.curves[index],
-        curveRoad.leftWidth
-      );
-    } else if (dir == "right") {
-      joinInfo = mathUtil.getHitInfoForCurve(
-        position,
-        curveRoad.curves[index],
-        curveRoad.rightWidth
-      );
+    // if (dir == "left") {
+    //   joinInfo = mathUtil.getHitInfoForCurve(
+    //     position,
+    //     curveRoad.curves[index],
+    //     curveRoad.leftWidth
+    //   );
+    // } else if (dir == "right") {
+    //   joinInfo = mathUtil.getHitInfoForCurve(
+    //     position,
+    //     curveRoad.curves[index],
+    //     curveRoad.rightWidth
+    //   );
+    // }
+
+    const leftCurveEdge = dataService.getCurveRoadEdge(curveRoad.leftEdgeId);
+    const rightCurveEdge = dataService.getCurveRoadEdge(curveRoad.rightEdgeId);
+    let joinInfo = mathUtil.getHitInfoForCurve(
+      position,
+      curveRoad.curves[index],
+      Math.max(curveRoad.leftWidth, curveRoad.rightWidth)
+    );
+
+    let leftJoinInfo = mathUtil.getHitInfoForCurve(
+      position,
+      leftCurveEdge.curves[index],
+      curveRoad.leftWidth
+    );
+    let leftLine = mathUtil.createLine1(
+      leftJoinInfo.position,
+      joinInfo.position
+    );
+    let leftJoin = mathUtil.getJoinLinePoint(position, leftLine);
+
+    let rightJoinInfo = mathUtil.getHitInfoForCurve(
+      position,
+      rightCurveEdge.curves[index],
+      curveRoad.rightWidth
+    );
+    let rightLine = mathUtil.createLine1(
+      rightJoinInfo.position,
+      joinInfo.position
+    );
+    let rightJoin = mathUtil.getJoinLinePoint(position, rightLine);
+    if (
+      dir == "left" &&
+      (mathUtil.isContainForSegment(
+        rightJoin,
+        rightJoinInfo.position,
+        joinInfo.position
+      ) ||
+        mathUtil.isContainForSegment(
+          rightJoinInfo.position,
+          rightJoin,
+          joinInfo.position
+        ))
+    ) {
+      return;
+    }
+
+    if (
+      dir == "right" &&
+      (mathUtil.isContainForSegment(
+        leftJoin,
+        leftJoinInfo.position,
+        joinInfo.position
+      ) ||
+        mathUtil.isContainForSegment(
+          leftJoinInfo.position,
+          leftJoin,
+          joinInfo.position
+        ))
+    ) {
+      return;
     }
+
     const newWidth = mathUtil.getDistance(joinInfo.position, position);
     if (
       newWidth > Constant.minRoadSideWidth &&

+ 17 - 13
src/graphic/Controls/UIControl.js

@@ -162,6 +162,10 @@ export default class UIControl {
         if (roadEdge) {
           // roadEdge.setStyle(selectUI);
           roadEdge[key](selectUI);
+          let crossPoint = dataService.getCrossPoint4(focusItem.vectorId);
+          if (crossPoint) {
+            crossPoint[key](selectUI);
+          }
         } else {
           roadEdge = dataService.getCurveRoadEdge(focusItem.vectorId);
           // roadEdge.setStyle(selectUI);
@@ -460,19 +464,19 @@ export default class UIControl {
     return new Promise((resolve) => canvas.toBlob(resolve, `${type}/image`));
   }
 
-  downloadCadImg(canvas, filename) {
-    // 图片导出为 jpg 格式
-    var type = "jpg";
-    var imgData = canvas.toDataURL(type, 3);
-    canvas.toBlob(`${type}/image`);
-
-    // 加工image data,替换mime type
-    imgData = imgData.replace(this._fixType(type), "image/octet-stream");
-    // 下载后的图片名
-    //var filename = 'cad_' + new Date().getTime() + '.' + type
-    // download
-    this.saveFile(imgData, filename);
-  }
+  // downloadCadImg(canvas, filename) {
+  //   // 图片导出为 jpg 格式
+  //   var type = "jpg";
+  //   var imgData = canvas.toDataURL(type, 3);
+  //   canvas.toBlob(`${type}/image`);
+
+  //   // 加工image data,替换mime type
+  //   imgData = imgData.replace(this._fixType(type), "image/octet-stream");
+  //   // 下载后的图片名
+  //   //var filename = 'cad_' + new Date().getTime() + '.' + type
+  //   // download
+  //   this.saveFile(imgData, filename);
+  // }
 
   saveFile(data, filename) {
     var save_link = document.createElementNS(

+ 23 - 5
src/graphic/History/HistoryUtil.js

@@ -255,7 +255,8 @@ export default class HistoryUtil {
       mathUtil.equalPoint(curveRoadEdge1.end, curveRoadEdge2.end) &&
       mathUtil.equalPoints(curveRoadEdge1.points, curveRoadEdge2.points) &&
       curveRoadEdge1.parent == curveRoadEdge2.parent &&
-      curveRoadEdge1.style == curveRoadEdge2.style
+      curveRoadEdge1.style == curveRoadEdge2.style &&
+      curveRoadEdge1.weight == curveRoadEdge2.weight
     ) {
       return false;
     } else {
@@ -310,7 +311,9 @@ export default class HistoryUtil {
       crossPoint1.edgeInfo1.id == crossPoint2.edgeInfo1.id &&
       crossPoint1.edgeInfo1.dir == crossPoint2.edgeInfo1.dir &&
       crossPoint1.edgeInfo2.id == crossPoint2.edgeInfo2.id &&
-      crossPoint1.edgeInfo2.dir == crossPoint2.edgeInfo2.dir
+      crossPoint1.edgeInfo2.dir == crossPoint2.edgeInfo2.dir &&
+      crossPoint1.style == crossPoint2.style &&
+      crossPoint1.weight == crossPoint2.weight
     ) {
       return false;
     } else {
@@ -499,6 +502,7 @@ export default class HistoryUtil {
     );
     curveRoadEdgeInfo.parent = curveRoadEdge2.parent;
     curveRoadEdgeInfo.style = curveRoadEdge2.style;
+    curveRoadEdgeInfo.weight = curveRoadEdge2.weight;
     this.setCurveRoadEdgeInfo(curveRoadEdgeInfo);
   }
 
@@ -563,6 +567,8 @@ export default class HistoryUtil {
       x: crossPoint2.extremePoint.x,
       y: crossPoint2.extremePoint.y,
     };
+    crossPointInfo.style = crossPoint2.style;
+    crossPointInfo.weight = crossPoint2.weight;
     crossPointInfo.curves = JSON.parse(JSON.stringify(crossPoint2.curves));
     this.setCrossPointInfo(crossPointInfo);
   }
@@ -749,6 +755,8 @@ export default class HistoryUtil {
     data.end = JSON.parse(JSON.stringify(curveRoadEdge.end));
     data.points = JSON.parse(JSON.stringify(curveRoadEdge.points));
     data.curves = JSON.parse(JSON.stringify(curveRoadEdge.curves));
+    data.style = curveRoadEdge.style;
+    data.weight = curveRoadEdge.weight;
     return data;
   }
 
@@ -797,6 +805,8 @@ export default class HistoryUtil {
     data.edgeInfo1 = JSON.parse(JSON.stringify(crossPoint.edgeInfo1));
     data.edgeInfo2 = JSON.parse(JSON.stringify(crossPoint.edgeInfo2));
     data.curves = JSON.parse(JSON.stringify(crossPoint.curves));
+    data.style = crossPoint.style;
+    data.weight = crossPoint.weight;
     return data;
   }
 
@@ -843,8 +853,9 @@ export default class HistoryUtil {
 
   setCurveLineInfo(curveLineInfo) {
     let curveLine = dataService.getCurveLine(curveLineInfo.vectorId);
-    curveLine.startId = curveLineInfo.start;
-    curveLine.endId = curveLineInfo.end;
+    curveLine.startId = curveLineInfo.startId;
+    curveLine.endId = curveLineInfo.endId;
+    curveLine.points = [];
     for (let i = 0; i < curveLineInfo.points.length; ++i) {
       curveLine.points[i] = dataService.getCurvePoint(
         curveLineInfo.points[i].vectorId
@@ -953,10 +964,15 @@ export default class HistoryUtil {
     curveRoadEdge.vectorId = curveRoadEdgeInfo.vectorId;
     mathUtil.clonePoint(curveRoadEdge.start, curveRoadEdgeInfo.start);
     mathUtil.clonePoint(curveRoadEdge.end, curveRoadEdgeInfo.end);
-    mathUtil.clonePoints(curveRoadEdge.points, curveRoadEdgeInfo.points);
+    for (let i = 0; i < curveRoadEdgeInfo.points.length; ++i) {
+      curveRoadEdge.points[i] = {};
+      mathUtil.clonePoint(curveRoadEdge.points[i], curveRoadEdgeInfo.points[i]);
+    }
+
     curveRoadEdge.curves = JSON.parse(JSON.stringify(curveRoadEdgeInfo.curves));
     curveRoadEdge.type = curveRoadEdgeInfo.type;
     curveRoadEdge.style = curveRoadEdgeInfo.style;
+    curveRoadEdge.weight = curveRoadEdgeInfo.weight;
   }
 
   setCurveRoadInfo(curveRoadInfo) {
@@ -1005,6 +1021,8 @@ export default class HistoryUtil {
     crossPoint.edgeInfo1 = JSON.parse(JSON.stringify(crossPointInfo.edgeInfo1));
     crossPoint.edgeInfo2 = JSON.parse(JSON.stringify(crossPointInfo.edgeInfo2));
     crossPoint.curves = JSON.parse(JSON.stringify(crossPointInfo.curves));
+    crossPoint.style = crossPointInfo.style;
+    crossPoint.weight = crossPointInfo.weight;
   }
 }
 

+ 63 - 10
src/graphic/Layer.js

@@ -36,6 +36,7 @@ import { roadService } from "./Service/RoadService";
 import { edgeService } from "./Service/EdgeService";
 import { roadPointService } from "./Service/RoadPointService";
 import { curveRoadService } from "./Service/CurveRoadService";
+import { curveRoadPointService } from "./Service/CurveRoadPointService";
 import VectorCategory from "./enum/VectorCategory";
 import Settings from "./Settings";
 import Constant from "./Constant";
@@ -356,19 +357,23 @@ export default class Layer {
       case VectorEvents.MinusCrossPoint:
         if (selectItem && selectItem.vectorId) {
           const curvePoint = dataService.getCurveRoadPoint(selectItem.vectorId);
-          const curveRoad = dataService.getCurveRoad(curvePoint.parent);
-          if (curveRoad.points.length > 3) {
-            curveRoadService.subCPoint(curveRoad, curvePoint.getIndex());
-            curveRoadService.setLanes(curveRoad.vectorId);
-            stateService.clearEventName();
+          if (curvePoint) {
+            const curveRoad = dataService.getCurveRoad(curvePoint.parent);
+            if (curveRoad.points.length > 3) {
+              curveRoadService.subCPoint(curveRoad, curvePoint.getIndex());
+              curveRoadService.setLanes(curveRoad.vectorId);
+              stateService.clearEventName();
+            }
+            this.history.save();
+            this.renderer.autoRedraw();
           }
-          this.history.save();
-          this.renderer.autoRedraw();
         }
         break;
     }
     selectItem = stateService.getSelectItem();
     stateService.setDraggingItem(selectItem);
+    stateService.clearFocusItem();
+    this.uiControl.focusVector = null;
     // 清除上一个状态
     // 设置当前事件名称
     e.preventDefault();
@@ -460,7 +465,6 @@ export default class Layer {
     this.lastX = X;
     this.lastY = Y;
     const draggingItem = stateService.getDraggingItem();
-
     switch (eventName) {
       case null:
         //监控
@@ -768,12 +772,13 @@ export default class Layer {
         }
         point = dataService.getCurveRoadPoint(draggingItem.vectorId);
         listenLayer.start(position, {
-          exceptRoadPointId: draggingItem.vectorId,
+          exceptCurveRoadPointId: draggingItem.vectorId,
           exceptCurveRoadId: point.parent, //不会融合,所以parent只有一个
         });
         if (
           listenLayer.modifyPoint &&
-          (listenLayer.modifyPoint.linkedRoadPointId ||
+          (listenLayer.modifyPoint.linkedCurveRoadPointId ||
+            listenLayer.modifyPoint.linkedRoadPointId ||
             listenLayer.modifyPoint.linkedRoadId ||
             listenLayer.modifyPoint.linkedRoadPointIdX ||
             listenLayer.modifyPoint.linkedRoadPointIdY ||
@@ -846,6 +851,9 @@ export default class Layer {
           }
           movePoint.movePoint(position, draggingItem.vectorId);
           point = dataService.getPoint(draggingItem.vectorId);
+          if (point.getCategory() == VectorCategory.Point.FixPoint) {
+            moveText.moveFullText(position, point.linkedTextId);
+          }
           needAutoRedraw = true;
           if (!point) {
             stateService.clearEventName();
@@ -992,6 +1000,12 @@ export default class Layer {
     let needAutoRedraw = false;
     switch (eventName) {
       case null:
+        if (e instanceof TouchEvent) {
+          stateService.clearSelectItem();
+          stateService.clearDraggingItem();
+          this.uiControl.focusVector = null;
+          this.renderer.autoRedraw();
+        }
         return;
       case LayerEvents.PanBackGround:
         needAutoRedraw = true;
@@ -1143,6 +1157,26 @@ export default class Layer {
         break;
       case LayerEvents.MoveCurveRoadPoint:
         needAutoRedraw = true;
+        if (
+          listenLayer.modifyPoint &&
+          listenLayer.modifyPoint.linkedCurveRoadPointId
+        ) {
+          let curveRoadPoint1 = dataService.getCurveRoadPoint(
+            listenLayer.modifyPoint.linkedCurveRoadPointId
+          );
+          let curveRoadPoint2 = dataService.getCurveRoadPoint(
+            draggingItem.vectorId
+          );
+          if (
+            listenLayer.modifyPoint.linkedCurveRoadPointId !=
+              draggingItem.vectorId &&
+            curveRoadPoint1.getParent() == curveRoadPoint2.getParent()
+          ) {
+            curveRoadPointService.deleteCurveRoadPoint(
+              listenLayer.modifyPoint.linkedCurveRoadPointId
+            );
+          }
+        }
         this.history.save();
         break;
       case LayerEvents.MoveCrossPoint:
@@ -1173,6 +1207,25 @@ export default class Layer {
         break;
       case LayerEvents.MoveCurvePoint:
         needAutoRedraw = true;
+        if (
+          listenLayer.modifyPoint &&
+          listenLayer.modifyPoint.linkedCurvePointId
+        ) {
+          let curvePoint1 = dataService.getCurvePoint(
+            listenLayer.modifyPoint.linkedCurvePointId
+          );
+          let curvePoint2 = dataService.getCurvePoint(draggingItem.vectorId);
+          if (
+            listenLayer.modifyPoint.linkedCurvePointId !=
+              draggingItem.vectorId &&
+            curvePoint1.getParent() == curvePoint2.getParent()
+          ) {
+            lineService.deleteCrossPointForCurveLine(
+              listenLayer.modifyPoint.linkedCurvePointId,
+              curvePoint1.getParent()
+            );
+          }
+        }
         this.history.save();
         break;
       case LayerEvents.MoveCurveLine:

+ 41 - 13
src/graphic/ListenLayer.js

@@ -760,6 +760,12 @@ export default class ListenLayer {
         //Constant.minAdsorbPix
         Math.max(curveRoad.leftWidth, curveRoad.rightWidth)
       );
+
+      let index = mathUtil.getIndexForCurvesPoints(position, curveRoad.points);
+      if (index == 0 || index == curveRoad.points.length) {
+        continue;
+      }
+
       console.log("isSelectCurveRoad:" + JSON.stringify(joinInfo));
       //检查edge
       const leftCurveEdge = dataService.getCurveRoadEdge(curveRoad.leftEdgeId);
@@ -778,21 +784,18 @@ export default class ListenLayer {
         Constant.minAdsorbPix
       );
 
+      let line1 = mathUtil.createLine1(
+        joinInfo.position,
+        leftJoinInfo.position
+      );
+      let position1 = mathUtil.getJoinLinePoint(position, line1);
       if (
-        mathUtil.getDistance(position, leftJoinInfo.position) >
-        mathUtil.getDistance(position, rightJoinInfo.position)
+        mathUtil.isContainForSegment(
+          position1,
+          joinInfo.position,
+          leftJoinInfo.position
+        )
       ) {
-        if (joinInfo.distance < curveRoad.rightWidth) {
-          curveRoadInfo = {
-            curveRoadId: curveRoadId,
-            type: VectorType.CurveRoad,
-            distance: joinInfo.distance,
-            x: joinInfo.position.x,
-            y: joinInfo.position.y,
-          };
-        }
-        curveRoadInfo.dir = "right";
-      } else {
         if (joinInfo.distance < curveRoad.leftWidth) {
           curveRoadInfo = {
             curveRoadId: curveRoadId,
@@ -803,6 +806,31 @@ export default class ListenLayer {
           };
         }
         curveRoadInfo.dir = "left";
+      } else {
+        let line2 = mathUtil.createLine1(
+          joinInfo.position,
+          rightJoinInfo.position
+        );
+
+        let position2 = mathUtil.getJoinLinePoint(position, line2);
+        if (
+          mathUtil.isContainForSegment(
+            position2,
+            joinInfo.position,
+            rightJoinInfo.position
+          )
+        ) {
+          if (joinInfo.distance < curveRoad.rightWidth) {
+            curveRoadInfo = {
+              curveRoadId: curveRoadId,
+              type: VectorType.CurveRoad,
+              distance: joinInfo.distance,
+              x: joinInfo.position.x,
+              y: joinInfo.position.y,
+            };
+          }
+          curveRoadInfo.dir = "right";
+        }
       }
 
       if (leftJoinInfo.distance < Constant.minAdsorbPix) {

+ 88 - 52
src/graphic/Load.js

@@ -18,7 +18,10 @@ import { historyService } from "./Service/HistoryService.js";
 import { uiService } from "./Service/UIService";
 import { crossPointService } from "./Service/CrossPointService.js";
 import Road from "./Geometry/Road.js";
+import CurveRoad from "./Geometry/CurveRoad.js";
 import { edgeService } from "./Service/EdgeService.js";
+import { curvePointService } from "./Service/CurvePointService.js";
+import { curveEdgeService } from "./Service/CurveEdgeService.js";
 
 export default class Load {
   constructor(layer) {
@@ -152,58 +155,41 @@ export default class Load {
           }
         }
       }
-      // if (dataLocal.curvelines) {
-      //   for (let key in dataLocal.curvelines) {
-      //     let startPointId = dataLocal.curvelines[key].startId
-      //     let endPointId = dataLocal.curvelines[key].endId
 
-      //     let startPosition = null;
-      //     let endPosition = null ;
-      //     if(dataLocal.curvePoints){
-      //       startPosition ={
-      //         x:dataLocal.curvePoints[startPointId].x,
-      //         y:dataLocal.curvePoints[startPointId].y,
-      //       }
-      //       endPosition ={
-      //         x:dataLocal.curvePoints[endPointId].x,
-      //         y:dataLocal.curvePoints[endPointId].y,
-      //       }
-
-      //     }
+      if (dataLocal.curvePoints) {
+        for (let key in dataLocal.curvePoints) {
+          let curvePointData = dataLocal.curvePoints[key];
+          let curvePoint = curvePointService.create(
+            curvePointData,
+            curvePointData.vectorId
+          );
+          curvePoint.setIndex(curvePointData.index);
+          curvePoint.setPointParent(curvePointData.parent);
+          dataService.addCurvePoint(curvePoint);
+        }
+      }
 
-      //     let curveline = lineService.createCurveLine(
-      //       startPosition,
-      //       endPosition,
-      //       dataLocal.curvelines[key].vectorId,
-      //     );
+      if (dataLocal.curvelines) {
+        for (let key in dataLocal.curvelines) {
+          let curveLineData = dataLocal.curvelines[key];
+          let curveLine = lineService.createCurveLineByPointIds(
+            curveLineData.points,
+            curveLineData.vectorId
+          );
+          curveLine.setStyle(curveLineData.style);
+          curveLine.setWeight(curveLineData.weight);
+          dataService.addCurveLine(curveLine);
+        }
+      }
 
-      //     if (dataLocal.curvelines[key].style) {
-      //       curveline.setStyle(dataLocal.curvelines[key].style);
-      //     }
-      //     if (dataLocal.curvelines[key].weight) {
-      //       curveline.setWeight(dataLocal.curvelines[key].weight);
-      //     }
-      //     if (dataLocal.curvelines[key].color) {
-      //       curveline.setColor(dataLocal.curvelines[key].color);
-      //     }
-      //     if (dataLocal.curvelines[key].value) {
-      //       curveline.setValue(dataLocal.curvelines[key].value);
-      //     }
-      //     if (dataLocal.curvelines[key].locationMode) {
-      //       curveline.setLocationMode(dataLocal.curvelines[key].locationMode);
-      //     }
-      //     curveline.setDisplay(dataLocal.curvelines[key].display);
-      //     if (curveline.getCategory() == VectorCategory.Line.BaseLine) {
-      //       Settings.baseLineId = key;
-      //     }
-      //   }
-      // }
       if (dataLocal.roadPoints) {
         for (let key in dataLocal.roadPoints) {
-          roadPointService.create(
+          let roadPointData = dataLocal.roadPoints[key];
+          let roadPoint = roadPointService.create(
             dataLocal.roadPoints[key],
             dataLocal.roadPoints[key].vectorId
           );
+          roadPoint.parent = JSON.parse(JSON.stringify(roadPointData.parent));
         }
       }
 
@@ -260,22 +246,69 @@ export default class Load {
 
       if (dataLocal.curveRoadPoints) {
         for (let key in dataLocal.curveRoadPoints) {
-          let curveRoadPoint = curveRoadPointService.create(
+          curveRoadPointService.create(
             dataLocal.curveRoadPoints[key],
             dataLocal.curveRoadPoints[key].vectorId
           );
         }
       }
+
+      if (dataLocal.curveRoadEdges) {
+        for (let key in dataLocal.curveRoadEdges) {
+          let curveRoadEdgeData = dataLocal.curveRoadEdges[key];
+          let curveRoadEdge = curveEdgeService.create(
+            curveRoadEdgeData.start,
+            curveRoadEdgeData.end,
+            curveRoadEdgeData.vectorId,
+            curveRoadEdgeData.parentId,
+            curveRoadEdgeData.points
+          );
+          curveRoadEdge.setStyle(curveRoadEdgeData.style);
+          curveRoadEdge.setWeight(curveRoadEdgeData.weight);
+          curveEdgeService.setCurves(curveRoadEdge);
+        }
+      }
+
       if (dataLocal.curveRoads) {
         for (let key in dataLocal.curveRoads) {
-          console.log(dataLocal.curveRoads[key].points);
-          console.log(
-            mathUtil.getCurvesByPoints(dataLocal.curveRoads[key].points, 0.2)
+          let curveRoadData = dataLocal.curveRoads[key];
+          let curveRoad = new CurveRoad(
+            curveRoadData.startId,
+            curveRoadData.endId,
+            curveRoadData.vectorId
+          );
+          dataService.addCurveRoad(curveRoad);
+
+          curveRoad.leftEdgeId = curveRoadData.leftEdgeId;
+          curveRoad.rightEdgeId = curveRoadData.rightEdgeId;
+
+          curveRoad.points = [];
+          for (let i = 0; i < curveRoadData.points.length; ++i) {
+            curveRoad.points[i] = dataService.getCurveRoadPoint(
+              curveRoadData.points[i].vectorId
+            );
+          }
+
+          curveRoad.curves = JSON.parse(JSON.stringify(curveRoadData.curves));
+          curveRoad.way = curveRoadData.way;
+          curveRoad.singleCurveRoadWidth = curveRoadData.singleCurveRoadWidth;
+          curveRoad.singleCurveRoadDrivewayCount =
+            curveRoadData.singleCurveRoadDrivewayCount;
+          curveRoad.singleLanesCurves = JSON.parse(
+            JSON.stringify(curveRoadData.singleLanesCurves)
+          );
+          curveRoad.leftWidth = curveRoadData.leftWidth;
+          curveRoad.rightWidth = curveRoadData.rightWidth;
+          curveRoad.leftDrivewayCount = curveRoadData.leftDrivewayCount;
+          curveRoad.rightDrivewayCount = curveRoadData.rightDrivewayCount;
+          curveRoad.midDivide = JSON.parse(
+            JSON.stringify(curveRoadData.midDivide)
           );
-          let curveRoad = curveRoadService.create(
-            dataLocal.curveRoads[key].startId,
-            dataLocal.curveRoads[key].endId,
-            dataLocal.curveRoads[key].vectorId
+          curveRoad.leftLanesCurves = JSON.parse(
+            JSON.stringify(curveRoadData.leftLanesCurves)
+          );
+          curveRoad.rightLanesCurves = JSON.parse(
+            JSON.stringify(curveRoadData.rightLanesCurves)
           );
         }
       }
@@ -295,6 +328,8 @@ export default class Load {
           crossPoint.extremePoint = JSON.parse(
             JSON.stringify(crossPointData.extremePoint)
           );
+          crossPoint.style = crossPointData.style;
+          crossPoint.weight = crossPointData.weight;
           dataService.addCrossPoint(crossPoint);
         }
       }
@@ -309,6 +344,7 @@ export default class Load {
           text.setAngle(dataLocal.texts[key].angle || 0);
         }
       }
+
       if (dataLocal.hasOwnProperty("currentId")) {
         dataService.setCurrentId(dataLocal.currentId);
       }

+ 95 - 54
src/graphic/Renderer/Draw.js

@@ -149,8 +149,7 @@ export const help = {
     return (
       Math.round(
         (mathUtil.getDistance(p1, p2) * coordinate.res * 100) / coordinate.ratio
-      )
-      / 100
+      ) / 100
     );
   },
   getPerpendicularPoint(p1, p2, p3, d) {
@@ -232,12 +231,11 @@ export const help = {
       VectorStyle.PointDrawLine,
       VectorStyle.SingleDashedLine,
       VectorStyle.SingleSolidLine,
-    ]
+    ];
     if (typeof line === "function" && !funStyle.includes(style)) {
-      style = VectorStyle.SingleSolidLine
+      style = VectorStyle.SingleSolidLine;
     }
 
-
     switch (style) {
       case VectorStyle.PointDrawLine:
       case VectorStyle.SingleDashedLine:
@@ -253,8 +251,8 @@ export const help = {
           ]);
         }
 
-        if (typeof line === 'function') {
-          line()
+        if (typeof line === "function") {
+          line();
         } else {
           ctx.moveTo(line[0].x, line[0].y);
           ctx.lineTo(line[1].x, line[1].y);
@@ -426,9 +424,9 @@ export default class Draw {
     const center = coordinate.getScreenXY(vector.center);
     this.context.save();
     if (vector.scale) {
-      this.context.translate(center.x, center.y)
-      this.context.scale(vector.scale, vector.scale)
-      this.context.translate(-center.x, -center.y)
+      this.context.translate(center.x, center.y);
+      this.context.scale(vector.scale, vector.scale);
+      this.context.translate(-center.x, -center.y);
     }
     this.context.drawImage(
       img,
@@ -489,13 +487,13 @@ export default class Draw {
       };
 
       ctx.save();
-      help.setStyle(ctx, styles)
+      help.setStyle(ctx, styles);
       vector.midDivide.leftMidDivide && draw(vector.midDivide.leftMidDivide);
       vector.midDivide.rightMidDivide && draw(vector.midDivide.rightMidDivide);
 
       ctx.restore();
     }
-    console.log(styles, label, vector)
+    console.log(styles, label, vector);
 
     if (import.meta.env.DEV && !isTemp) {
       const startReal = isTemp
@@ -511,9 +509,12 @@ export default class Draw {
     }
 
     this.drawRoadEdge(vector, isTemp);
-    vector.leftLanes && vector.leftLanes.forEach((g) => this.drawLan(g, !!label));
-    vector.rightLanes && vector.rightLanes.forEach((g) => this.drawLan(g, !!label));
-    vector.singleLanes && vector.singleLanes.forEach((g) => this.drawLan(g, !!label));
+    vector.leftLanes &&
+      vector.leftLanes.forEach((g) => this.drawLan(g, !!label));
+    vector.rightLanes &&
+      vector.rightLanes.forEach((g) => this.drawLan(g, !!label));
+    vector.singleLanes &&
+      vector.singleLanes.forEach((g) => this.drawLan(g, !!label));
   }
 
   drawLan(lan, focus) {
@@ -523,9 +524,9 @@ export default class Draw {
     ctx.save();
     ctx.beginPath();
     help.setVectorStyle(ctx, null, "Lane");
-    console.log(focus)
+    console.log(focus);
     if (focus) {
-      ctx.strokeStyle = 'rgba(255, 143, 40, 1)'
+      ctx.strokeStyle = "rgba(255, 143, 40, 1)";
     }
     ctx.lineWidth *= Settings.lineWidth;
     ctx.setLineDash(Style.Lane.dash);
@@ -656,14 +657,16 @@ export default class Draw {
     ctx.save();
     help.setVectorStyle(ctx, vector);
     ctx.beginPath();
-    ctx.arc(
-      extremePoint.x,
-      extremePoint.y,
-      Style.CrossPoint.radius * coordinate.ratio,
-      0,
-      Math.PI * 2,
-      true
-    );
+    if (!Settings.screenMode) {
+      ctx.arc(
+        extremePoint.x,
+        extremePoint.y,
+        Style.CrossPoint.radius * coordinate.ratio,
+        0,
+        Math.PI * 2,
+        true
+      );
+    }
     ctx.stroke();
     ctx.fill();
     ctx.restore();
@@ -675,8 +678,16 @@ export default class Draw {
     // ctx.moveTo(start.x, start.y);
     // ctx.quadraticCurveTo(pt.x, pt.y, end.x, end.y);
 
-    const [coves] = help.transformCoves([vector.curves]);
-    help.drawCoves(ctx, coves);
+    help.drawStyleLine(
+      ctx,
+      () => {
+        const [coves] = help.transformCoves([vector.curves]);
+        help.drawCoves(ctx, coves);
+      },
+      vector.style,
+      vector.weight
+    );
+
     ctx.restore();
   }
 
@@ -690,13 +701,12 @@ export default class Draw {
         vector.midDivide.leftMidDivideCurves,
         // vector.midDivide.rightMidDivideCurves,
       ]);
-      ctx.setLineDash([8 * coordinate.ratio, 8 * coordinate.ratio])
-      ctx.lineWidth *= Settings.lineWidth
+      ctx.setLineDash([8 * coordinate.ratio, 8 * coordinate.ratio]);
+      ctx.lineWidth *= Settings.lineWidth;
       for (let coves of midCovesArray) {
         help.drawCoves(ctx, coves);
       }
 
-
       // midCovesArray = help.transformCoves([
       //   vector.curves,
       // ]);
@@ -704,7 +714,6 @@ export default class Draw {
       // for (let coves of midCovesArray) {
       //   help.drawCoves(ctx, coves);
       // }
-
     }
     ctx.restore();
 
@@ -717,9 +726,9 @@ export default class Draw {
       vector
     );
     vector.leftLanesCurves &&
-      vector.leftLanesCurves.forEach(this.drawCurveLan.bind(this));
+      vector.leftLanesCurves.forEach((data) => this.drawCurveLan(data, foo));
     vector.rightLanesCurves &&
-      vector.rightLanesCurves.forEach(this.drawCurveLan.bind(this));
+      vector.rightLanesCurves.forEach((data) => this.drawCurveLan(data, foo));
 
     if (foo) {
       const leftEdge = dataService.getCurveRoadEdge(vector.leftEdgeId);
@@ -780,9 +789,14 @@ export default class Draw {
     select && help.setStyle(ctx, style);
     // ctx.lineWidth *= Settings.lineWidth;
 
-    help.drawStyleLine(this.context, () => {
-      help.drawCoves(ctx, coves);
-    }, vector.style, vector.weight);
+    help.drawStyleLine(
+      this.context,
+      () => {
+        help.drawCoves(ctx, coves);
+      },
+      vector.style,
+      vector.weight
+    );
 
     // help.drawCoves(ctx, coves);
     ctx.restore();
@@ -792,22 +806,26 @@ export default class Draw {
     }
   }
 
-  drawCurveLan(lines) {
+  drawCurveLan(lines, focus) {
     const [coves] = help.transformCoves([lines]);
     const ctx = this.context;
     ctx.save();
 
     help.setVectorStyle(ctx, null, "CurveLan");
     ctx.lineWidth *= Settings.lineWidth;
+    if (focus) {
+      ctx.strokeStyle = "rgba(255, 153, 0, 1)";
+      ctx.lineWidth *= 2;
+    }
     ctx.setLineDash(Style.Lane.dash);
     help.drawCoves(ctx, coves);
     ctx.restore();
 
     // if (import.meta.env.DEV) {
-    lines.map((line) => {
-      this.drawPoint(line.start);
-      this.drawPoint(line.end);
-    });
+    // lines.map((line) => {
+    //   this.drawPoint(line.start);
+    //   this.drawPoint(line.end);
+    // });
     // }
   }
 
@@ -1015,6 +1033,15 @@ export default class Draw {
         ...style,
         ...stylea,
       };
+    } else if (vector.category === VectorCategory.Point.FixPoint) {
+      const text = dataService.getText(vector?.linkedTextId);
+      if (text) {
+        style = {
+          ...style,
+          fillStyle: text.color,
+          strokeStyle: text.color,
+        };
+      }
     }
 
     if (vector.color) {
@@ -1040,6 +1067,7 @@ export default class Draw {
       ctx.fill();
       ctx.restore();
     };
+
     if (Settings.selectBasePointId === vector.vectorId) {
       style = {
         ...style,
@@ -1130,7 +1158,6 @@ export default class Draw {
     vector.displayPoint &&
       this.drawPoint({ ...vector.center, color: vector.color }, true);
 
-
     // vector.getBound(this.context).forEach(this.drawPoint.bind(this))
   }
 
@@ -1149,9 +1176,13 @@ export default class Draw {
     this.context.scale(width / svgWidth, height / svgHidth);
     const [style, label] = help.getVectorStyle(vector);
     this.context.lineWidth = style.lineWidth / (width / svgWidth);
-    this.context.fillStyle = 'rgba(0,0,0,0)'
-    this.context.strokeStyle = 'rgba(0,0,0,0)'
-    SVGIcons[vector.type].draw(this.context, style.fillStyle || 'rgba(0,0,0,0)', style.strokeStyle || 'rgba(0,0,0,0)');
+    this.context.fillStyle = "rgba(0,0,0,0)";
+    this.context.strokeStyle = "rgba(0,0,0,0)";
+    SVGIcons[vector.type].draw(
+      this.context,
+      style.fillStyle || "rgba(0,0,0,0)",
+      style.strokeStyle || "rgba(0,0,0,0)"
+    );
     this.context.restore();
 
     if (label) {
@@ -1162,15 +1193,20 @@ export default class Draw {
       this.context.lineTo(points[2].x, points[2].y);
       this.context.lineTo(points[3].x, points[3].y);
       this.context.strokeStyle = style.strokeStyle;
-      this.context.fillStyle = 'rgba(255, 153, 0, 0.30)';
+      this.context.fillStyle = "rgba(255, 153, 0, 0.30)";
       this.context.lineWidth = 2 * coordinate.ratio;
       this.context.setLineDash([6 * coordinate.ratio, 2 * coordinate.ratio]);
       this.context.closePath();
       this.context.stroke();
-      this.context.fill()
+      this.context.fill();
       this.context.restore();
       vector.points.forEach((point) =>
-        this.drawPoint({ ...point, fillColor: '#fff', color: style.strokeStyle, radius: 5 })
+        this.drawPoint({
+          ...point,
+          fillColor: "#fff",
+          color: style.strokeStyle,
+          radius: 5,
+        })
       );
     }
   }
@@ -1245,15 +1281,20 @@ export default class Draw {
     ctx.save();
     help.setVectorStyle(ctx, vector);
 
-    help.drawStyleLine(this.context, () => {
-      help.transformCoves([vector.curves]).forEach((coves) => {
-        help.drawCoves(ctx, coves);
-      });
-    }, vector.style, vector.weight);
+    help.drawStyleLine(
+      this.context,
+      () => {
+        help.transformCoves([vector.curves]).forEach((coves) => {
+          help.drawCoves(ctx, coves);
+        });
+      },
+      vector.style,
+      vector.weight
+    );
     ctx.restore();
 
     // if (import.meta.env.DEV) {
-      vector.points.forEach(this.drawPoint.bind(this));
+    vector.points.forEach(this.drawPoint.bind(this));
     // }
   }
 

+ 2 - 0
src/graphic/Service/CurveRoadPointService.js

@@ -104,6 +104,8 @@ export default class CurveRoadPointService {
       // road.setLeftLanes(curveRoad.leftLanes);
       // road.setRightLanes(curveRoad.rightLanes);
       // road.setSingleLanes(curveRoad.singleLanes);
+      road.setStyle(curveRoad.style);
+      road.setWeight(curveRoad.weight);
       dataService.addRoad(road);
       edgeService.computerDefaultEdge(road.vectorId);
       roadService.setLanes(road.vectorId);

+ 15 - 3
src/graphic/Service/CurveRoadService.js

@@ -3,7 +3,7 @@ import { curveRoadPointService } from "./CurveRoadPointService";
 import { curveEdgeService } from "./CurveEdgeService";
 import { mathUtil } from "../Util/MathUtil.js";
 import CurveRoad from "../Geometry/CurveRoad.js";
-import VectorType from "../enum/VectorType";
+import VectorStyle from "../enum/VectorStyle";
 import Constant from "../Constant";
 import RoadService from "./RoadService";
 import { lineService } from "./LineService";
@@ -806,6 +806,9 @@ export default class CurveRoadService extends RoadService {
       );
     }
 
+    if (!edgePoints) {
+      return;
+    }
     leftCurveEdge.points = edgePoints.leftEdgePoints;
     leftCurveEdge.start = leftCurveEdge.points[0];
     leftCurveEdge.end = leftCurveEdge.points[leftCurveEdge.points.length - 1];
@@ -1138,8 +1141,17 @@ export default class CurveRoadService extends RoadService {
       for (let i = 0; i < curveRoad.rightLanesCurves.length; ++i) {
         lineService.createCurveLineByPoints(curveRoad.rightLanesCurves[i]);
       }
-      lineService.createCurveLineByPoints(curveRoad.midDivide.leftMidDivide);
-      lineService.createCurveLineByPoints(curveRoad.midDivide.rightMidDivide);
+      // let leftMidDivide = lineService.createCurveLineByPoints(
+      //   curveRoad.midDivide.leftMidDivide
+      // );
+      // leftMidDivide.setStyle(VectorStyle.SingleDashedLine);
+      // let rightMidDivide = lineService.createCurveLineByPoints(
+      //   curveRoad.midDivide.rightMidDivide
+      // );
+      // rightMidDivide.setStyle(VectorStyle.SingleDashedLine);
+
+      let mid = lineService.createCurveLineByPoints(curveRoad.points);
+      mid.setStyle(VectorStyle.SingleDashedLine);
     }
   }
 }

+ 8 - 0
src/graphic/Service/DataService.js

@@ -436,6 +436,14 @@ export class DataService {
     }
   }
 
+  getCrossPoint4(edgeId) {
+    for (let key in this.vectorData.crossPoints) {
+      if (key.indexOf(edgeId + "-") > -1 || key.indexOf("-" + edgeId) > -1) {
+        return this.vectorData.crossPoints[key];
+      }
+    }
+  }
+
   getCrossPointForEdgeId(edgeId, dir) {
     for (let key in this.vectorData.crossPoints) {
       const crossPoint = this.vectorData.crossPoints[key];

+ 1 - 1
src/graphic/Service/LineService.js

@@ -175,7 +175,7 @@ export default class LineService {
     let newPoint = curvePointService.create(position);
     newPoint.setIndex(index);
     newPoint.setPointParent(vectorId);
-    curveLine.points.splice(index + 1, 0, newPoint);
+    curveLine.points.splice(index, 0, newPoint);
     for (let i = index + 1; i < curveLine.points.length; ++i) {
       curveLine.points[i].setIndex(i);
     }

+ 16 - 9
src/graphic/Service/PointService.js

@@ -11,7 +11,7 @@ export default class PointService {
   create(position, vectorId) {
     let point = new Point(position, vectorId);
     dataService.addPoint(point);
-    this.updateBasePointIds()
+    this.updateBasePointIds();
     return point;
   }
 
@@ -57,6 +57,10 @@ export default class PointService {
         category == VectorCategory.Point.TestBasePoint
       ) {
         this.deleteTestPoint(pointId);
+      } else if (category == VectorCategory.Point.FixPoint) {
+        let textId = point.linkedTextId;
+        dataService.deletePoint(pointId); //暂时简单粗暴
+        dataService.deleteText(textId);
       }
     }
   }
@@ -94,17 +98,16 @@ export default class PointService {
     if (Settings.selectBasePointId == basePointId) {
       Settings.selectBasePointId = null;
     }
-    this.updateBasePointIds()
+    this.updateBasePointIds();
   }
 
   deleteTestPoint(testPointId) {
     let points = dataService.getPoints();
     let needDeletePointIds = [];
     for (let key in points) {
-    
       let point = dataService.getPoint(key);
-      if(point.getCategory() == VectorCategory.Point.BasePoint){
-        continue
+      if (point.getCategory() == VectorCategory.Point.BasePoint) {
+        continue;
       }
       if (point.vectorId == testPointId) {
         needDeletePointIds.push(testPointId);
@@ -122,18 +125,22 @@ export default class PointService {
         needDeletePointIds.indexOf(line.endId) > -1
       ) {
         dataService.deleteLine(key);
-        if (needDeletePointIds.indexOf(line.startId) > -1&& endPoint.getCategory()!= VectorCategory.Point.BasePoint) {
+        if (
+          needDeletePointIds.indexOf(line.startId) > -1 &&
+          endPoint.getCategory() != VectorCategory.Point.BasePoint
+        ) {
           this.deleteTestPoint(line.endId);
         }
-        if (needDeletePointIds.indexOf(line.endId) > -1 && startPoint.getCategory()!= VectorCategory.Point.BasePoint) {
+        if (
+          needDeletePointIds.indexOf(line.endId) > -1 &&
+          startPoint.getCategory() != VectorCategory.Point.BasePoint
+        ) {
           this.deleteTestPoint(line.startId);
         }
       }
     }
     dataService.deletePoint(testPointId);
   }
-
-
 }
 
 const pointService = new PointService();

+ 14 - 2
src/graphic/Util/MathUtil.js

@@ -1623,7 +1623,7 @@ export default class MathUtil {
       if (this.isContainForSegment(join, points[i], points[i + 1])) {
         if (minDis == null || minDis > dis) {
           minDis = dis;
-          index = i;
+          index = i + 1;
         }
       }
       if (minDisToPoint == null) {
@@ -1634,7 +1634,7 @@ export default class MathUtil {
         minPointIndex = i;
       }
     }
-    if ((index = -1)) {
+    if (index == -1) {
       if (
         minDisToPoint >
         mathUtil.getDistance(position, points[points.length - 1])
@@ -1725,6 +1725,9 @@ export default class MathUtil {
     let rightEdgePoints = [];
     for (let i = 0; i < points.length - 1; ++i) {
       if (dir == "left" || !dir) {
+        if (mathUtil.equalPoint(points[i], points[i + 1])) {
+          return null;
+        }
         let leftEdgePoins1 = this.RectangleVertex(
           points[i],
           points[i + 1],
@@ -1735,6 +1738,9 @@ export default class MathUtil {
           leftEdgePoins1.leftEdgeEnd
         );
         if (i != points.length - 2) {
+          if (mathUtil.equalPoint(points[i + 2], points[i + 1])) {
+            return null;
+          }
           let leftEdgePoins2 = this.RectangleVertex(
             points[i + 1],
             points[i + 2],
@@ -1766,6 +1772,9 @@ export default class MathUtil {
       }
 
       if (dir == "right" || !dir) {
+        if (mathUtil.equalPoint(points[i], points[i + 1])) {
+          return null;
+        }
         let rightEdgePoins1 = this.RectangleVertex(
           points[i],
           points[i + 1],
@@ -1776,6 +1785,9 @@ export default class MathUtil {
           rightEdgePoins1.rightEdgeEnd
         );
         if (i != points.length - 2) {
+          if (mathUtil.equalPoint(points[i + 2], points[i + 1])) {
+            return null;
+          }
           let rightEdgePoins2 = this.RectangleVertex(
             points[i + 1],
             points[i + 2],

+ 59 - 57
src/views/graphic/geos/arrow.vue

@@ -3,50 +3,54 @@
     <template v-slot="{ data }">
       <template v-if="data.key === 'color'">
         <Color v-model:color="color">
-          <span class="color" :style="{backgroundColor: color}"></span>
+          <span class="color" :style="{ backgroundColor: color }"></span>
         </Color>
       </template>
     </template>
   </GeoTeleport>
-  <GeoTeleport :menus="typeMenus" v-if="typeMenus" class="type-geo" :active="typeMenus.find(menu => menu.key === type)" />
+  <GeoTeleport
+    :menus="typeMenus"
+    v-if="typeMenus"
+    class="type-geo"
+    :active="typeMenus.find((menu) => menu.key === type)"
+  />
 </template>
 
 <script setup lang="ts">
 import GeoTeleport from "@/views/graphic/geos/geo-teleport.vue";
-import {drawRef, FocusVector, uiType, UIType, useChange} from '@/hook/useGraphic'
-import {computed, ref, UnwrapRef, watch, watchEffect} from "vue";
-import {dataService} from "@/graphic/Service/DataService";
-import GeoActions from "@/graphic/enum/GeoActions"
-import {debounce} from "@/utils";
-import Color from '@/components/color/index.vue'
+
+import { drawRef, FocusVector, uiType, UIType, useChange } from "@/hook/useGraphic";
+import { computed, ref, UnwrapRef, watch, watchEffect } from "vue";
+import { dataService } from "@/graphic/Service/DataService";
+import GeoActions from "@/graphic/enum/GeoActions";
+import { debounce } from "@/utils";
+import Color from "@/components/color/index.vue";
 import VectorCategory from "@/graphic/enum/VectorCategory";
 
-const props = defineProps<{geo: FocusVector}>()
-const vector = computed(() => dataService.getLine(props.geo.vectorId))
-const color = ref("#000000")
-const type = ref(props.geo.category || "SingleArrowLine")
+const props = defineProps<{ geo: FocusVector }>();
+const vector = computed(() => dataService.getLine(props.geo.vectorId));
+const color = ref("#000000");
+const type = ref(props.geo.category || "SingleArrowLine");
 useChange(() => {
-  color.value = vector.value.color
-  type.value = vector.value.category
-})
-
+  color.value = vector.value.color;
+  type.value = vector.value.category;
+});
 
 const syncVector = ([color, type]) => {
-  vector.value.setColor(color)
-  vector.value.setCategory(type)
-  drawRef.value.renderer.autoRedraw()
-  drawRef.value.history.save()
-}
+  vector.value.setColor(color);
+  vector.value.setCategory(type);
+  drawRef.value.renderer.autoRedraw();
+  drawRef.value.history.save();
+};
 
-watch(() => [color.value, type.value], debounce(syncVector, 500))
+watch(() => [color.value, type.value], debounce(syncVector, 500));
 
-
-const typeMenus = ref<UnwrapRef<typeof menus>>()
+const typeMenus = ref<UnwrapRef<typeof menus>>();
 const menus = ref([
   {
-    key: 'color',
-    icon: 'del',
-    text: "颜色"
+    key: "color",
+    icon: "del",
+    text: "颜色",
   },
   {
     key: "type",
@@ -55,47 +59,46 @@ const menus = ref([
     onClick: () => {
       typeMenus.value = !typeMenus.value
         ? [
-          {
-            key: 'SingleArrowLine',
-            icon: 'arrows_s',
-            text: "单向",
-            onClick: () => type.value = "SingleArrowLine"
-          },
-          {
-            key: 'DoubleArrowLine',
-            icon: 'arrows_d',
-            text: "双向",
-            onClick: () => type.value = "DoubleArrowLine"
-          },
-        ]
-      : null
-    }
+            {
+              key: "SingleArrowLine",
+              icon: "arrows_s",
+              text: "单向",
+              onClick: () => (type.value = "SingleArrowLine"),
+            },
+            {
+              key: "DoubleArrowLine",
+              icon: "arrows_d",
+              text: "双向",
+              onClick: () => (type.value = "DoubleArrowLine"),
+            },
+          ]
+        : null;
+    },
   },
   {
-    key: 'copy',
-    icon: 'copy',
+    key: "copy",
+    icon: "copy",
     text: "复制",
     onClick: () => {
-      drawRef.value.uiControl.handleGeo(GeoActions.CopyAction)
-    }
+      drawRef.value.uiControl.handleGeo(GeoActions.CopyAction);
+    },
   },
   {
-    key: 'del',
-    icon: 'del',
+    key: "del",
+    icon: "del",
     text: "删除",
     onClick: () => {
-      drawRef.value.uiControl.handleGeo(GeoActions.DeleteAction)
-    }
-  }
-])
+      drawRef.value.uiControl.handleGeo(GeoActions.DeleteAction);
+    },
+  },
+]);
 
 watchEffect(() => {
-  menus.value[1].icon = type.value === "SingleArrowLine" ? "arrows_s" : "arrows_d"
-})
+  menus.value[1].icon = type.value === "SingleArrowLine" ? "arrows_s" : "arrows_d";
+});
 if (props.geo.category === VectorCategory.Line.NormalLine) {
-  menus.value.shift()
+  menus.value.shift();
 }
-
 </script>
 
 <style scoped lang="scss">
@@ -124,7 +127,6 @@ if (props.geo.category === VectorCategory.Line.NormalLine) {
 .type-geo {
   margin-bottom: 74px;
 }
-
 </style>
 
 <style lang="scss">

+ 13 - 13
src/views/graphic/geos/index.ts

@@ -1,17 +1,16 @@
-import { VectorType } from '@/hook/useGraphic'
-import Arrow from './arrow.vue'
-import Text from './text.vue'
-import Circle from './circle.vue'
-import magnifier from './magnifier.vue'
-import Del from './del.vue'
-import RoadEdge from './roadEdge.vue'
-import Road from './road.vue'
-import NormalLine from './normalLine.vue'
+import { VectorType } from "@/hook/useGraphic";
+import Arrow from "./arrow.vue";
+import Text from "./text.vue";
+import Circle from "./circle.vue";
+import magnifier from "./magnifier.vue";
+import Del from "./del.vue";
+import RoadEdge from "./roadEdge.vue";
+import Road from "./road.vue";
+import NormalLine from "./normalLine.vue";
 import VectorCategory from "@/graphic/enum/VectorCategory";
 import DelAndCopu from "@/views/graphic/geos/delAndCopu.vue";
 
-
-export const GlobalComp = Del
+export const GlobalComp = Del;
 
 export default {
   [VectorType.SVG]: DelAndCopu,
@@ -25,6 +24,7 @@ export default {
   [VectorCategory.Line.NormalLine]: NormalLine,
   [VectorType.CurveLine]: NormalLine,
   [VectorType.Text]: Text,
+  [VectorCategory.Point.FixPoint]: Text,
   [VectorType.Circle]: Circle,
-  [VectorType.Magnifier]: magnifier
-}
+  [VectorType.Magnifier]: magnifier,
+};

+ 49 - 46
src/views/graphic/geos/road.vue

@@ -1,87 +1,90 @@
 <template>
   <div class="layout">
     <GraphicAction class="full-action">
-      <ui-icon type="lock" ctrl @click="clickHandlerFactory(VectorEvents.UnLock)()"/>
+      <ui-icon type="lock" ctrl @click="clickHandlerFactory(VectorEvents.UnLock)()" />
     </GraphicAction>
-    <GeoTeleport :menus="menus" class="geo-teleport-use"  />
+    <GeoTeleport :menus="menus" class="geo-teleport-use" />
   </div>
 </template>
 
 <script setup lang="ts">
 import GeoTeleport from "@/views/graphic/geos/geo-teleport.vue";
 import UiIcon from "@/components/base/components/icon/index.vue";
-import {drawRef, FocusVector, VectorType} from '@/hook/useGraphic'
-import {computed, ref, watchEffect} from "vue";
-import {dataService} from "@/graphic/Service/DataService";
-import GeoActions from "@/graphic/enum/GeoActions"
+import { drawRef, FocusVector, VectorType } from "@/hook/useGraphic";
+import { computed, ref, watchEffect } from "vue";
+import { dataService } from "@/graphic/Service/DataService";
+import GeoActions from "@/graphic/enum/GeoActions";
 import GraphicAction from "@/components/button-pane/index.vue";
 import VectorEvents from "@/graphic/enum/VectorEvents";
 
-const props = defineProps<{geo: FocusVector}>()
-const vector = computed(() => dataService.getRoad(props.geo.vectorId))
+const props = defineProps<{ geo: FocusVector }>();
+const vector = computed(() => dataService.getRoad(props.geo.vectorId));
 const clickHandlerFactory = (key) => {
   return () => {
-    drawRef.value.uiControl.updateVectorForSelectUI(key)
-    console.log(vector)
-  }
-}
-
+    drawRef.value.uiControl.updateVectorForSelectUI(key);
+    console.log(vector);
+  };
+};
 
-const appendMenus = props.geo.type === VectorType.CurveRoad
-  ? [
-    {
-      key: VectorEvents.AddCrossPoint,
-      icon: "control_a",
-      text: "加控制点",
-      onClick: clickHandlerFactory(VectorEvents.AddCrossPoint)
-    },
-    {
-      key: VectorEvents.MinusCrossPoint,
-      icon: "control_d",
-      text: "减控制点" ,
-      onClick: clickHandlerFactory(VectorEvents.MinusCrossPoint)
-    },
-  ]
-  : []
+const appendMenus =
+  props.geo.type === VectorType.CurveRoad
+    ? [
+        {
+          key: VectorEvents.AddCrossPoint,
+          icon: "control_a",
+          text: "加控制点",
+          onClick: clickHandlerFactory(VectorEvents.AddCrossPoint),
+        },
+        // {
+        //   key: VectorEvents.MinusCrossPoint,
+        //   icon: "control_d",
+        //   text: "减控制点",
+        //   onClick: clickHandlerFactory(VectorEvents.MinusCrossPoint),
+        // },
+      ]
+    : [];
 const menus = ref([
   {
     key: VectorEvents.AddLane,
     icon: "lane_a",
-    text: "加车道" ,
-    onClick: clickHandlerFactory(VectorEvents.AddLane)
+    text: "加车道",
+    onClick: clickHandlerFactory(VectorEvents.AddLane),
   },
   {
     key: VectorEvents.DelLane,
     icon: "lane_d",
     text: "减车道",
-    onClick: clickHandlerFactory(VectorEvents.DelLane)
+    onClick: clickHandlerFactory(VectorEvents.DelLane),
   },
   ...appendMenus,
   {
-    key: 'copy',
-    icon: 'copy',
+    key: "copy",
+    icon: "copy",
     text: "复制",
     onClick: () => {
-      drawRef.value.uiControl.handleGeo(GeoActions.CopyAction)
-    }
+      drawRef.value.uiControl.handleGeo(GeoActions.CopyAction);
+    },
   },
   {
-    key: 'del',
-    icon: 'del',
+    key: "del",
+    icon: "del",
     text: "删除",
     onClick: () => {
-      drawRef.value.uiControl.handleGeo(GeoActions.DeleteAction)
-    }
-  }
-])
+      drawRef.value.uiControl.handleGeo(GeoActions.DeleteAction);
+    },
+  },
+]);
 
-console.log(vector.value)
+console.log(vector.value);
 watchEffect(() => {
   if (vector.value) {
-    menus.value[1].disabled = vector.value?.leftLanes.length + vector.value?.rightLanes.length + vector.value?.singleLanes.length === 0
+    menus.value[1].disabled =
+      vector.value?.leftLanes.length +
+        vector.value?.rightLanes.length +
+        vector.value?.singleLanes.length ===
+      0;
   }
-})
-
+});
 </script>
 
 <style scoped lang="scss">

+ 74 - 72
src/views/graphic/geos/roadEdge.vue

@@ -1,109 +1,109 @@
 <template>
-  <GeoTeleport :menus="menus" class="geo-teleport-use" :active="active"/>
-  <GeoTeleport :menus="childMenus" v-if="childMenus" class="type-geo"/>
+  <GeoTeleport :menus="menus" class="geo-teleport-use" :active="active" />
+  <GeoTeleport :menus="childMenus" v-if="childMenus" class="type-geo" />
 </template>
 
 <script setup lang="ts">
 import GeoTeleport from "@/views/graphic/geos/geo-teleport.vue";
-import {drawRef, FocusVector, VectorType} from '@/hook/useGraphic'
-import {computed, ref, toRaw, UnwrapRef} from "vue";
-import {dataService} from "@/graphic/Service/DataService";
-import GeoActions from "@/graphic/enum/GeoActions"
-import {UITypeExtend} from "@/views/graphic/menus";
+import { drawRef, FocusVector, VectorType } from "@/hook/useGraphic";
+import { computed, ref, toRaw, UnwrapRef } from "vue";
+import { dataService } from "@/graphic/Service/DataService";
+import GeoActions from "@/graphic/enum/GeoActions";
+import { UITypeExtend } from "@/views/graphic/menus";
 import VectorEvents from "@/graphic/enum/VectorEvents";
 import VectorStyle from "@/graphic/enum/VectorStyle";
 import VectorWeight from "@/graphic/enum/VectorWeight";
 
-
-const props = defineProps<{ geo: FocusVector }>()
-const vector = computed(() => dataService.getRoadEdge(props.geo.vectorId))
-console.error(vector.value)
+const props = defineProps<{ geo: FocusVector }>();
+const vector = computed(() => dataService.getRoadEdge(props.geo.vectorId));
+console.error(vector.value);
 
 const clickHandlerFactory = (key) => {
-  return () => drawRef.value.uiControl.updateVectorForSelectUI(key)
-}
+  return () => drawRef.value.uiControl.updateVectorForSelectUI(key);
+};
 
 const lineTypeMenu = [
   {
     key: VectorStyle.SingleSolidLine,
     icon: "line_sf",
     text: "单实线",
-    onClick: clickHandlerFactory(VectorStyle.SingleSolidLine)
+    onClick: clickHandlerFactory(VectorStyle.SingleSolidLine),
   },
   {
     key: VectorStyle.SingleDashedLine,
     icon: "line_sd",
     text: "单虚线",
-    onClick: clickHandlerFactory(VectorStyle.SingleDashedLine)
-  },
-  {
-    hide: props.geo.type === VectorType.CurveRoadEdge,
-    key: VectorStyle.DoubleSolidLine,
-    icon: "line_df",
-    text: "双实线",
-    onClick: clickHandlerFactory(VectorStyle.DoubleSolidLine)
-  },
-  {
-    hide: props.geo.type === VectorType.CurveRoadEdge,
-    key: VectorStyle.DoubleDashedLine,
-    icon: "line_dd",
-    text: "双虚线",
-    onClick: clickHandlerFactory(VectorStyle.DoubleDashedLine)
-  },
-  {
-    hide: props.geo.type === VectorType.CurveRoadEdge,
-    key: VectorStyle.BrokenLine,
-    icon: "line_broken",
-    text: "折线",
-    onClick: clickHandlerFactory(VectorStyle.BrokenLine)
+    onClick: clickHandlerFactory(VectorStyle.SingleDashedLine),
   },
+  // {
+  //   hide: props.geo.type === VectorType.CurveRoadEdge,
+  //   key: VectorStyle.DoubleSolidLine,
+  //   icon: "line_df",
+  //   text: "双实线",
+  //   onClick: clickHandlerFactory(VectorStyle.DoubleSolidLine),
+  // },
+  // {
+  //   hide: props.geo.type === VectorType.CurveRoadEdge,
+  //   key: VectorStyle.DoubleDashedLine,
+  //   icon: "line_dd",
+  //   text: "双虚线",
+  //   onClick: clickHandlerFactory(VectorStyle.DoubleDashedLine),
+  // },
+  // {
+  //   hide: props.geo.type === VectorType.CurveRoadEdge,
+  //   key: VectorStyle.BrokenLine,
+  //   icon: "line_broken",
+  //   text: "折线",
+  //   onClick: clickHandlerFactory(VectorStyle.BrokenLine),
+  // },
   {
     key: VectorStyle.PointDrawLine,
     icon: "line_dot",
     text: "点画线",
-    onClick: clickHandlerFactory(VectorStyle.PointDrawLine)
+    onClick: clickHandlerFactory(VectorStyle.PointDrawLine),
   },
   // {key: VectorStyle.Greenbelt, icon: "treelawn", text: "绿化带 ", onClick: clickHandlerFactory(VectorStyle.Greenbelt)},
-]
+];
 
 const lineWidthMenu = [
   {
     key: VectorWeight.Bold,
-    icon: 'l_thick',
+    icon: "l_thick",
     text: "宽度",
     onClick: () => {
-      clickHandlerFactory(VectorWeight.Thinning)()
-      menus.value[1] = lineWidthMenu[1]
-    }
+      clickHandlerFactory(VectorWeight.Thinning)();
+      menus.value[1] = lineWidthMenu[1];
+    },
   },
   {
     key: VectorWeight.Thinning,
-    icon: 'l_thin',
+    icon: "l_thin",
     text: "宽度",
     onClick: () => {
-      clickHandlerFactory(VectorWeight.Bold)()
-      menus.value[1] = lineWidthMenu[0]
-    }
+      clickHandlerFactory(VectorWeight.Bold)();
+      menus.value[1] = lineWidthMenu[0];
+    },
   },
-]
+];
 
-const appendMenus = props.geo.type === VectorType.CurveRoadEdge
-  ? [
-      {
-        key: VectorEvents.AddCrossPoint,
-        icon: "control_a",
-        text: "加控制点",
-        onClick: clickHandlerFactory(VectorEvents.AddCrossPoint)
-      },
-      {
-        key: VectorEvents.MinusCrossPoint,
-        icon: "control_d",
-        text: "减控制点",
-        onClick: clickHandlerFactory(VectorEvents.MinusCrossPoint)
-      },
-    ]
-  : []
-const childMenus = ref<UnwrapRef<typeof menus>>()
+const appendMenus =
+  props.geo.type === VectorType.CurveRoadEdge
+    ? [
+        // {
+        //   key: VectorEvents.AddCrossPoint,
+        //   icon: "control_a",
+        //   text: "加控制点",
+        //   onClick: clickHandlerFactory(VectorEvents.AddCrossPoint)
+        // },
+        // {
+        //   key: VectorEvents.MinusCrossPoint,
+        //   icon: "control_d",
+        //   text: "减控制点",
+        //   onClick: clickHandlerFactory(VectorEvents.MinusCrossPoint)
+        // },
+      ]
+    : [];
+const childMenus = ref<UnwrapRef<typeof menus>>();
 // console.log(vector.value)
 const menus = ref([
   {
@@ -111,18 +111,20 @@ const menus = ref([
     text: "单实线",
     icon: "line",
     onClick() {
-      childMenus.value = toRaw(childMenus.value) === lineTypeMenu ? null : lineTypeMenu
-    }
+      childMenus.value = toRaw(childMenus.value) === lineTypeMenu ? null : lineTypeMenu;
+    },
   },
   vector.value?.weight === VectorWeight.Bold ? lineWidthMenu[0] : lineWidthMenu[1],
-  ...appendMenus
-])
+  ...appendMenus,
+]);
 
 const active = computed(() =>
-  toRaw(childMenus.value) === lineTypeMenu ? menus.value[0]
-    : toRaw(childMenus.value) === lineWidthMenu ? menus.value[1] : null
-)
-
+  toRaw(childMenus.value) === lineTypeMenu
+    ? menus.value[0]
+    : toRaw(childMenus.value) === lineWidthMenu
+    ? menus.value[1]
+    : null
+);
 </script>
 
 <style scoped lang="scss">

+ 64 - 60
src/views/graphic/geos/text.vue

@@ -3,17 +3,17 @@
     <template v-slot="{ data }">
       <template v-if="data.key === 'color'">
         <Color v-model:color="color">
-          <span class="color" :style="{backgroundColor: color}"></span>
+          <span class="color" :style="{ backgroundColor: color }"></span>
         </Color>
       </template>
       <template v-if="data.key === 'fontSize'">
         <ui-input
-            type="select"
-            :options="sizeOption"
-            class="geo-input"
-            dire="top"
-            floatingClass="select-floating"
-            v-model="size"
+          type="select"
+          :options="sizeOption"
+          class="geo-input"
+          dire="top"
+          floatingClass="select-floating"
+          v-model="size"
         />
         <span class="font-size">{{ size }}</span>
       </template>
@@ -23,12 +23,12 @@
   <div class="text-model" v-if="updateText">
     <div class="text-input">
       <ui-input
-          ref="inputTextRef"
-          v-model="text"
-          width="100%"
-          :maxlength="20"
-          height="64px"
-          @blur="updateText = false"
+        ref="inputTextRef"
+        v-model="text"
+        width="100%"
+        :maxlength="20"
+        height="64px"
+        @blur="updateText = false"
       />
     </div>
   </div>
@@ -38,76 +38,80 @@
 import GeoTeleport from "@/views/graphic/geos/geo-teleport.vue";
 import UiInput from "@/components/base/components/input/index.vue";
 import UiIcon from "@/components/base/components/icon/index.vue";
-import {FocusVector, drawRef, useChange} from '@/hook/useGraphic'
-import {computed, ref, watch, watchEffect} from "vue";
-import {dataService} from "@/graphic/Service/DataService";
-import {debounce} from '@/utils'
+import { FocusVector, drawRef, useChange } from "@/hook/useGraphic";
+import { computed, ref, watch, watchEffect } from "vue";
+import { dataService } from "@/graphic/Service/DataService";
+import { debounce } from "@/utils";
 import GeoActions from "@/graphic/enum/GeoActions";
 import Color from "@/components/color/index.vue";
-
-const props = defineProps<{geo: FocusVector}>()
-const inputTextRef = ref()
-const updateText = ref(false)
-const vector = computed(() => dataService.getText(props.geo.vectorId))
-const text = ref("")
-const color = ref("#000000")
-const size = ref(18)
+import VectorCategory from "@/graphic/enum/VectorCategory";
+
+const props = defineProps<{ geo: FocusVector }>();
+const inputTextRef = ref();
+const updateText = ref(false);
+const vector = computed(() => {
+  let vectorId = props.geo.vectorId;
+  if (props.geo.category === VectorCategory.Point.FixPoint) {
+    vectorId = dataService.getPoint(props.geo.vectorId)?.linkedTextId;
+  }
+  return dataService.getText(vectorId);
+});
+const text = ref("");
+const color = ref("#000000");
+const size = ref(18);
 
 const syncVector = ([text, size, color]) => {
-  console.log(text, size, color)
-  vector.value.setValue(text)
-  vector.value.setColor(color)
-  vector.value.setFontSize(size)
-  drawRef.value.renderer.autoRedraw()
-  drawRef.value.history.save()
-}
+  console.log(text, size, color);
+  vector.value.setValue(text);
+  vector.value.setColor(color);
+  vector.value.setFontSize(size);
+  drawRef.value.renderer.autoRedraw();
+  drawRef.value.history.save();
+};
 
 watchEffect(() => {
   if (inputTextRef.value) {
-    inputTextRef.value.vmRef.input.focus()
+    inputTextRef.value.vmRef.input.focus();
   }
-})
+});
 
 useChange(() => {
-  color.value = vector.value.color
-  size.value = vector.value.fontSize
-  text.value = vector.value.value
-})
-watch(
-  () => [text.value, size.value, color.value],
-  debounce(syncVector, 500)
-)
+  console.log(vector.value);
+  color.value = vector.value.color;
+  size.value = vector.value.fontSize;
+  text.value = vector.value.value;
+});
+watch(() => [text.value, size.value, color.value], debounce(syncVector, 500));
 
 const sizeOption = [];
 for (let i = 10; i < 30; i++) {
-  sizeOption.push({label: i, value: i});
+  sizeOption.push({ label: i, value: i });
 }
 const menus = [
   {
-    key: 'color',
-    icon: 'del',
-    text: "颜色"
+    key: "color",
+    icon: "del",
+    text: "颜色",
   },
   {
-    key: 'fontSize',
-    text: "文字大小"
+    key: "fontSize",
+    text: "文字大小",
   },
   {
-    key: 'text',
-    icon: 'edit',
+    key: "text",
+    icon: "edit",
     text: "修改文字",
-    onClick: () => updateText.value = true
+    onClick: () => (updateText.value = true),
   },
   {
-    key: 'del',
-    icon: 'del',
+    key: "del",
+    icon: "del",
     text: "删除",
     onClick: () => {
-      drawRef.value.uiControl.handleGeo(GeoActions.DeleteAction)
-    }
-  }
-]
-
+      drawRef.value.uiControl.handleGeo(GeoActions.DeleteAction);
+    },
+  },
+];
 </script>
 
 <style scoped lang="scss">
@@ -145,8 +149,8 @@ const menus = [
   top: 0;
   right: 0;
   bottom: 0;
-  background-color: rgba(0,0,0,.8);
-  z-index: 4
+  background-color: rgba(0, 0, 0, 0.8);
+  z-index: 4;
 }
 
 .text-input {

+ 87 - 63
src/views/graphic/imageLabel.vue

@@ -6,99 +6,107 @@
     </div>
     <ui-input type="text" width="100%" v-model="keyword">
       <template v-slot:preIcon>
-        <ui-icon type="magnify_g"  color="rgba(255,255,255,0.6)"/>
+        <ui-icon type="magnify_g" color="rgba(255,255,255,0.6)" />
       </template>
     </ui-input>
 
-    <div v-for="typeMenu in typeMenus" :key="typeMenu.title" class="type-menu">
-      <h2 @click="showTypeMenu = showTypeMenu?.title === typeMenu.title ? null : typeMenu">
-        {{typeMenu.title}}
-        <ui-icon :type="showTypeMenu?.title === typeMenu.title ? 'fold' : 'unfold'" />
-      </h2>
-      <div class="menu-list" v-show="showTypeMenu?.title === typeMenu.title">
-        <div
+    <template v-if="typeMenus.length">
+      <div v-for="typeMenu in typeMenus" :key="typeMenu.title" class="type-menu">
+        <h2
+          @click="showTypeMenu = showTypeMenu?.title === typeMenu.title ? null : typeMenu"
+        >
+          {{ typeMenu.title }}
+          <ui-icon :type="showTypeMenu?.title === typeMenu.title ? 'fold' : 'unfold'" />
+        </h2>
+        <div class="menu-list" v-show="showTypeMenu?.title === typeMenu.title">
+          <div
             v-for="menu in typeMenu.children"
             :key="menu.key"
             class="menu"
-            :class="{active: uiType.current === menu.key}"
+            :class="{ active: uiType.current === menu.key }"
             @click="clickHandler(menu.key)"
-        >
-          <ui-icon :type="menu.icon" class="icon" />
-          <p>{{ menu.text }}</p>
+          >
+            <ui-icon :type="menu.icon" class="icon" />
+            <p>{{ menu.text }}</p>
+          </div>
         </div>
       </div>
+    </template>
+    <div v-else class="empty-images">
+      <div>
+        <img src="@/assets/images/empty-label.png" />
+        <p>无结果</p>
+      </div>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
 import UiIcon from "@/components/base/components/icon/index.vue";
-import { uiType } from '@/hook/useGraphic'
-import icons, {typeKeys} from "@/graphic/CanvasStyle/ImageLabels/SVGIcons"
-import {computed, ref, watch} from "vue";
+import { uiType } from "@/hook/useGraphic";
+import icons, { typeKeys } from "@/graphic/CanvasStyle/ImageLabels/SVGIcons";
+import { computed, ref, watch } from "vue";
 import UiInput from "@/components/base/components/input/index.vue";
-import {uses} from '@/store/SVGLabel'
+import { uses } from "@/store/SVGLabel";
 
-
-const typeMenusRaw = typeKeys.map(({type, children}) => ({
+const typeMenusRaw = typeKeys.map(({ type, children }) => ({
   title: type,
-  children: children.map(key => ({
+  children: children.map((key) => ({
     key: key,
     text: icons[key].text,
-    icon: key.substring(0,key.lastIndexOf("."))
-  }))
-}))
-const keyword = ref("")
+    icon: key.substring(0, key.lastIndexOf(".")),
+  })),
+}));
+const keyword = ref("");
 const typeMenus = computed(() => {
   const raws = typeMenusRaw.map((typeMenu) => ({
     ...typeMenu,
     children: typeMenu.children
-      .filter(item => item.text.includes(keyword.value))
-      .sort((a, b) => a.icon.localeCompare(b.icon))
+      .filter((item) => item.text.includes(keyword.value))
+      .sort((a, b) => a.icon.localeCompare(b.icon)),
   }));
 
-  const items = keyword.value ? raws : [
-    {
-      title: "常用",
-      children: uses.value
-        .sort((item1, item2) => (item2.count || 0) - (item1.count || 0)).slice(0, 10)
-        .map(item => {
-          for (let menu of typeMenusRaw) {
-            const findItem = menu.children.find(menuItem => menuItem.key === item.key);
-            if (findItem) {
-              return findItem
-            }
-          }
-        })
-        .filter(item => !!item)
-    },
-    ...raws
-  ]
-
-
-  return items.filter(item => item.children.length)
-})
-const showTypeMenu = ref()
-watch(
-  typeMenus,
-  () => showTypeMenu.value = typeMenus.value[0],
-  { immediate: true }
-)
+  const items = keyword.value
+    ? raws
+    : [
+        {
+          title: "常用",
+          children: uses.value
+            .sort((item1, item2) => (item2.count || 0) - (item1.count || 0))
+            .slice(0, 10)
+            .map((item) => {
+              for (let menu of typeMenusRaw) {
+                const findItem = menu.children.find(
+                  (menuItem) => menuItem.key === item.key
+                );
+                if (findItem) {
+                  return findItem;
+                }
+              }
+            })
+            .filter((item) => !!item),
+        },
+        ...raws,
+      ];
+
+  return items.filter((item) => item.children.length);
+});
+const showTypeMenu = ref();
+watch(typeMenus, () => (showTypeMenu.value = typeMenus.value[0]), { immediate: true });
 
 const clickHandler = (key) => {
-  const findUse = uses.value.find(use => use.key === key);
-  const lastUpdateTime = new Date().getTime()
+  const findUse = uses.value.find((use) => use.key === key);
+  const lastUpdateTime = new Date().getTime();
   if (findUse) {
-    findUse.count++
-    findUse.lastUpdateTime = lastUpdateTime
+    findUse.count++;
+    findUse.lastUpdateTime = lastUpdateTime;
   } else {
-    uses.value.push({ key, count: 1, lastUpdateTime })
+    uses.value.push({ key, count: 1, lastUpdateTime });
   }
-  uiType.change(key as any)
-}
+  uiType.change(key as any);
+};
 
 defineEmits<{ (e: "quit") }>();
-
 </script>
 
 <style lang="scss" scoped>
@@ -125,7 +133,7 @@ defineEmits<{ (e: "quit") }>();
   flex-direction: column;
   cursor: pointer;
   height: 100px;
-  transition: color .3s ease;
+  transition: color 0.3s ease;
 
   //&:hover,
   &.active {
@@ -173,7 +181,7 @@ defineEmits<{ (e: "quit") }>();
     margin: 16px 0;
     font-size: 16px;
     font-weight: bold;
-    color: rgba(255,255,255,0.6);
+    color: rgba(255, 255, 255, 0.6);
     display: flex;
     align-items: center;
     justify-content: space-between;
@@ -182,6 +190,22 @@ defineEmits<{ (e: "quit") }>();
       font-size: 16px;
     }
   }
-  border-bottom: 1px solid rgba(255,255,255,0.1);
+  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.empty-images {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+  height: calc(100% - 80px);
+  div img {
+    display: block;
+    width: 128px;
+  }
+  div p {
+    margin-top: 10px;
+    color: rgba(255, 255, 255, 1);
+  }
 }
 </style>

+ 59 - 44
src/views/graphic/index.vue

@@ -1,19 +1,17 @@
 <template>
-  <MainPanel
-      :menus="store.menus as any"
-      :active-menu-key="store.activeMenuKey.value">
+  <MainPanel :menus="store.menus as any" :active-menu-key="store.activeMenuKey.value">
     <template v-slot:header>
-      <Header  />
+      <Header />
     </template>
     <Container />
     <ChildMenus
-        v-if="store.child.value && store.activeMenuKey.value === UITypeExtend.template"
-        :menus="store.child.value as any"
-        @quit="store.child.value = null"
+      v-if="store.child.value && store.activeMenuKey.value === UITypeExtend.template"
+      :menus="store.child.value as any"
+      @quit="store.child.value = null"
     />
     <ImageLabel
-        v-if="store.activeMenuKey.value === UITypeExtend.image"
-        @quit="uiType.change(null)"
+      v-if="store.activeMenuKey.value === UITypeExtend.image"
+      @quit="uiType.change(null)"
     />
 
     <GraphicAction class="full-action" v-if="!graphicState.continuedMode">
@@ -24,59 +22,76 @@
       />
     </GraphicAction>
     <Confirm v-if="graphicState.continuedMode" />
-    <Component :is="geoComponent as any" v-else-if="geoComponent" :geo="currentVector"/>
+    <Component
+      :is="geoComponent as any"
+      v-else-if="geoComponent"
+      :geo="currentVector"
+      :key="currentVector.vectorId"
+    />
   </MainPanel>
 </template>
 
 <script lang="ts" setup>
-import MainPanel from '@/components/main-panel/index.vue'
+import MainPanel from "@/components/main-panel/index.vue";
 import ChildMenus from "@/views/graphic/childMenus.vue";
-import Header from './header.vue'
-import Container from './container.vue'
-import GraphicAction from '@/components/button-pane/index.vue'
+import Header from "./header.vue";
+import Container from "./container.vue";
+import GraphicAction from "@/components/button-pane/index.vue";
 import UiIcon from "@/components/base/components/icon/index.vue";
-import Confirm from './confirm.vue'
+import Confirm from "./confirm.vue";
 import ImageLabel from "./imageLabel.vue";
-import {router} from '@/router'
-import {computed, watchEffect} from "vue";
-import {customMap} from '@/hook'
-import {focusMenuRaw, generateMixMenus, mainMenusRaw, photoMenusRaw, Mode, UITypeExtend} from './menus'
-import {currentVector, drawRef, graphicState, uiType} from "@/hook/useGraphic";
-import geos, {GlobalComp} from "./geos/index";
+import { router } from "@/router";
+
+import { computed, watchEffect } from "vue";
+import { customMap } from "@/hook";
+import {
+  focusMenuRaw,
+  generateMixMenus,
+  mainMenusRaw,
+  photoMenusRaw,
+  Mode,
+  UITypeExtend,
+} from "./menus";
+import { currentVector, drawRef, graphicState, uiType } from "@/hook/useGraphic";
+import geos, { GlobalComp } from "./geos/index";
 
 const menusRaws = computed(() => {
-  const mode = Number(router.currentRoute.value.params.mode) as Mode
-  return mode === Mode.Photo ? photoMenusRaw : mainMenusRaw
-})
-const store = computed(() => generateMixMenus(
-  "extend",
-  (mainMenuRaw) => ({
-    ...mainMenuRaw,
-    title: mainMenuRaw.text,
-    name: mainMenuRaw.key,
-    isRoute: false,
-    // icon: 'menu',
-    bottom:  mainMenuRaw.key === UITypeExtend.setup
-  }),
-  menusRaws.value
-))
+  const mode = Number(router.currentRoute.value.params.mode) as Mode;
+  return mode === Mode.Photo ? photoMenusRaw : mainMenusRaw;
+});
+const store = computed(() =>
+  generateMixMenus(
+    "extend",
+    (mainMenuRaw) => ({
+      ...mainMenuRaw,
+      title: mainMenuRaw.text,
+      name: mainMenuRaw.key,
+      isRoute: false,
+      // icon: 'menu',
+      bottom: mainMenuRaw.key === UITypeExtend.setup,
+    }),
+    menusRaws.value
+  )
+);
 
 watchEffect(() => {
   if (graphicState.value.continuedMode) {
-    customMap.sysView = 'full'
+    customMap.sysView = "full";
   } else {
-    customMap.sysView = 'auto'
+    customMap.sysView = "auto";
   }
-})
+});
 
-const focusMenus = computed(() => focusMenuRaw[currentVector.value?.type])
+const focusMenus = computed(() => focusMenuRaw[currentVector.value?.type]);
 const geoComponent = computed(() => {
   if (currentVector.value) {
-    console.log(currentVector.value)
-    return geos[currentVector.value?.type] || geos[currentVector.value?.category] || GlobalComp
+    console.log(currentVector.value);
+    return (
+      geos[currentVector.value?.category] || geos[currentVector.value?.type] || GlobalComp
+    );
   }
-})
-const isFull = computed(() => customMap.sysView === 'full' )
+});
+const isFull = computed(() => customMap.sysView === "full");
 </script>
 
 <style lang="scss" scoped>

+ 129 - 95
src/views/graphic/menus.ts

@@ -1,11 +1,15 @@
-import { uiType, UIType, graphicState } from '@/hook/useGraphic';
-import { findMenuByAttr, generateByMenus as generateByMenusRaw, generateMixMenus as generateMixMenusRaw } from '@/utils/menus';
-import Message from '@/components/base/components/message/message.vue';
-import RoadStructure from '@/graphic/enum/RoadStructure';
-import RoadTemplate from '@/graphic/enum/RoadTemplate';
-import { computed } from 'vue';
-import { mount } from '@/components/base/utils/componentHelper';
-import Setting from '@/views/graphic/setting.vue';
+import { uiType, UIType, graphicState } from "@/hook/useGraphic";
+import {
+  findMenuByAttr,
+  generateByMenus as generateByMenusRaw,
+  generateMixMenus as generateMixMenusRaw,
+} from "@/utils/menus";
+import Message from "@/components/base/components/message/message.vue";
+import RoadStructure from "@/graphic/enum/RoadStructure";
+import RoadTemplate from "@/graphic/enum/RoadTemplate";
+import { computed } from "vue";
+import { mount } from "@/components/base/utils/componentHelper";
+import Setting from "@/views/graphic/setting.vue";
 
 export enum Mode {
   Road,
@@ -13,18 +17,18 @@ export enum Mode {
 }
 
 export const UITypeExtend = {
-  structure: 'structure',
-  template: 'template',
-  measure: 'measure',
-  road: '__road',
-  image: '__image',
-  line: '__line',
-  photo: 'photo',
-  setup: 'setup',
-  lineType: 'lineType',
-  width: 'width',
-  lock: 'lock',
-  basemap: 'Basemap',
+  structure: "structure",
+  template: "template",
+  measure: "measure",
+  road: "__road",
+  image: "__image",
+  line: "__line",
+  photo: "photo",
+  setup: "setup",
+  lineType: "lineType",
+  width: "width",
+  lock: "lock",
+  basemap: "Basemap",
 };
 
 export type MenuRaw = {
@@ -38,52 +42,60 @@ export type MenuRaw = {
 export type MenusRaw = Array<MenuRaw>;
 
 export const structureMenusRaw = [
-  { key: RoadStructure.BranchRoad, icon: 'jg_zl-1', text: '支路' },
-  { key: RoadStructure.NarrowRoad, icon: 'jg_zl', text: '窄路' },
-  { key: RoadStructure.ShoulderRoad, icon: 'jg_lj', text: '路肩' },
-  { key: RoadStructure.ZebraCrossing, icon: 'jg_bmx', text: '斑马线' },
-  { key: RoadStructure.Bridge, icon: 'jg_q', text: '桥' },
-  { key: RoadStructure.Tunnel, icon: 'jg_sd', text: '隧道' },
-  { key: RoadStructure.Sidewalk, icon: 'jg_rxd', text: '人行道' },
-  { key: RoadStructure.ConstructionSection, icon: 'jg_sgld', text: '施工路段' },
-  { key: RoadStructure.Downhill, icon: 'jg_road_u', text: '下坡' },
-  { key: RoadStructure.Uphill, icon: 'jg_sp', text: '上坡' },
-  { key: RoadStructure.RoadsideGutter, icon: 'jg_lpsg', text: '路边水沟' },
-  { key: RoadStructure.RoadsAndRails, icon: 'jg_dltlpjk', text: '道路与铁...' },
-  { key: RoadStructure.FireHydrantWell, icon: 'jg_xfsj', text: '消火栓井' },
-  { key: RoadStructure.Gullies, icon: 'jg_ysk', text: '雨水口' },
-  { key: RoadStructure.RoadPotholes, icon: 'jg_lmak', text: '路面凹坑' },
-  { key: RoadStructure.ProtrudingRoad, icon: 'jg_lmtcbf', text: '路面凸出...' },
-  { key: RoadStructure.WaterOnTheRoad, icon: 'jg_lmjs', text: '路面积水' },
+  { key: RoadStructure.BranchRoad, icon: "jg_zl-1", text: "支路" },
+  { key: RoadStructure.NarrowRoad, icon: "jg_zl", text: "窄路" },
+  { key: RoadStructure.ShoulderRoad, icon: "jg_lj", text: "路肩" },
+  { key: RoadStructure.ZebraCrossing, icon: "jg_bmx", text: "斑马线" },
+  { key: RoadStructure.Bridge, icon: "jg_q", text: "桥" },
+  { key: RoadStructure.Tunnel, icon: "jg_sd", text: "隧道" },
+  { key: RoadStructure.Sidewalk, icon: "jg_rxd", text: "人行道" },
+  { key: RoadStructure.ConstructionSection, icon: "jg_sgld", text: "施工路段" },
+  { key: RoadStructure.Downhill, icon: "jg_road_u", text: "下坡" },
+  { key: RoadStructure.Uphill, icon: "jg_sp", text: "上坡" },
+  { key: RoadStructure.RoadsideGutter, icon: "jg_lpsg", text: "路边水沟" },
+  { key: RoadStructure.RoadsAndRails, icon: "jg_dltlpjk", text: "道路与铁..." },
+  { key: RoadStructure.FireHydrantWell, icon: "jg_xfsj", text: "消火栓井" },
+  { key: RoadStructure.Gullies, icon: "jg_ysk", text: "雨水口" },
+  { key: RoadStructure.RoadPotholes, icon: "jg_lmak", text: "路面凹坑" },
+  { key: RoadStructure.ProtrudingRoad, icon: "jg_lmtcbf", text: "路面凸出..." },
+  { key: RoadStructure.WaterOnTheRoad, icon: "jg_lmjs", text: "路面积水" },
 ];
 
 export const templateMenusRaw = [
-  { key: RoadTemplate.SBend, icon: 'mb_sxwl', text: 's型弯路' },
-  { key: RoadTemplate.TJunction, icon: 'mb_dzlk', text: '丁字路口' },
-  { key: RoadTemplate.FiveForks, icon: 'mb_wclk', text: '五岔路口' },
-  { key: RoadTemplate.ExitRamp, icon: 'mb_ckzd', text: '出口匝道' },
-  { key: RoadTemplate.Crossroads, icon: 'mb_szlk', text: '十字路口' },
-  { key: RoadTemplate.NationalHighwayShoulder, icon: 'mb_gdlj', text: '国道(路肩)' },
-  { key: RoadTemplate.IndoorSection, icon: 'mb_snld', text: '室内路段' },
-  { key: RoadTemplate.Bend, icon: 'mb_wd', text: '弯道' },
-  { key: RoadTemplate.SharpCurve, icon: 'mb_jzwd', text: '急转弯道' },
-  { key: RoadTemplate.SixForkIntersection, icon: 'mb_lclk', text: '六岔路口' },
+  { key: RoadTemplate.SBend, icon: "mb_sxwl", text: "s型弯路" },
+  { key: RoadTemplate.TJunction, icon: "mb_dzlk", text: "丁字路口" },
+  { key: RoadTemplate.FiveForks, icon: "mb_wclk", text: "五岔路口" },
+  { key: RoadTemplate.ExitRamp, icon: "mb_ckzd", text: "出口匝道" },
+  { key: RoadTemplate.Crossroads, icon: "mb_szlk", text: "十字路口" },
+  {
+    key: RoadTemplate.NationalHighwayShoulder,
+    icon: "mb_gdlj",
+    text: "国道(路肩)",
+  },
+  { key: RoadTemplate.IndoorSection, icon: "mb_snld", text: "室内路段" },
+  { key: RoadTemplate.Bend, icon: "mb_wd", text: "弯道" },
+  { key: RoadTemplate.SharpCurve, icon: "mb_jzwd", text: "急转弯道" },
+  { key: RoadTemplate.SixForkIntersection, icon: "mb_lclk", text: "六岔路口" },
   // { key: RoadTemplate.WideNarrowRoad, icon: 'mb_kbzld', text: '宽变窄路段' },
-  { key: RoadTemplate.Corner, icon: 'mb_zjwd', text: '直角弯道' },
-  { key: RoadTemplate.ImportSmashedRoad, icon: 'mb_jkzd', text: '进口匝道' },
-  { key: RoadTemplate.HighSpeedTollBooth, icon: 'mb_gssfz', text: '高速收费站' },
+  { key: RoadTemplate.Corner, icon: "mb_zjwd", text: "直角弯道" },
+  { key: RoadTemplate.ImportSmashedRoad, icon: "mb_jkzd", text: "进口匝道" },
+  {
+    key: RoadTemplate.HighSpeedTollBooth,
+    icon: "mb_gssfz",
+    text: "高速收费站",
+  },
   // { key: RoadTemplate.HighSpeedHarbor, icon: 'mb_gsgw', text: '高速港湾' },
-  { key: RoadTemplate.HighwaySection, icon: 'mb_gsld', text: '高速路段' },
+  { key: RoadTemplate.HighwaySection, icon: "mb_gsld", text: "高速路段" },
 ];
 
 export const measureMenusRaw = [
   {
     key: UIType.BaseLine,
-    icon: 'line',
-    text: '基准线',
+    icon: "line",
+    text: "基准线",
     disabled: computed(() => graphicState.value.existsBaseLine),
   },
-  { key: UIType.BasePoint, text: '基准点', icon: 'point', border: true },
+  { key: UIType.BasePoint, text: "基准点", icon: "point", border: true },
   // {
   //   key: UIType.NormalLocationMode,
   //   text: '垂线定位',
@@ -99,42 +111,48 @@ export const measureMenusRaw = [
   {
     desc: "一键测量",
     key: UIType.AngleLocationMode,
-    text: '直角定位法',
-    icon: 'measure_r',
+    text: "直角定位法",
+    icon: "measure_r",
     onClick(data) {
       console.error(graphicState.value.canAllLocationMode);
       if (graphicState.value.canAllLocationMode) {
         uiType.change(data.key);
       } else {
-        Message.success({ msg: '请添加基准线及基准点后再执行此操作', time: 3000 });
+        Message.success({
+          msg: "请添加基准线及基准点后再执行此操作",
+          time: 3000,
+        });
       }
     },
   },
   {
     key: UIType.AllLocationMode,
-    text: '综合定位法',
-    icon: 'measure_c',
+    text: "综合定位法",
+    icon: "measure_c",
     onClick(data) {
       if (graphicState.value.canAllLocationMode) {
         uiType.change(data.key);
       } else {
-        Message.success({ msg: '请添加基准线及基准点后再执行此操作', time: 3000 });
+        Message.success({
+          msg: "请添加基准线及基准点后再执行此操作",
+          time: 3000,
+        });
       }
     },
-    border: true
+    border: true,
   },
   {
     key: UIType.FreeMeasureLine,
-    text: '自由测量',
-    icon: 'measure_free'
+    text: "自由测量",
+    icon: "measure_free",
   },
 ];
 
 export const mainMenusRaw: MenusRaw = [
   {
     key: UIType.Line,
-    text: '画线',
-    icon: 'line_d',
+    text: "画线",
+    icon: "line_d",
     // children: [
     //   {
     //     key: UIType.Line,
@@ -150,43 +168,48 @@ export const mainMenusRaw: MenusRaw = [
   },
   {
     key: UITypeExtend.road,
-    text: '道路',
-    icon: 'road',
+    text: "道路",
+    icon: "road",
     children: [
-      { key: UIType.OneEdgeOneLanRoad, icon: 'road_ss', text: '' },
-      { key: UIType.OneEdgeTwoLanRoad, icon: 'road_sd', text: '' },
-      { key: UIType.OneEdgeThreeLanRoad, icon: 'road_st', text: '' },
-      { key: UIType.TwoEdgeOneLanRoad, icon: 'road_ds', text: '' },
-      { key: UIType.TwoEdgeTwoLanRoad, icon: 'road_dd', text: '' },
-      { key: UIType.TwoEdgeThreeLanRoad, icon: 'road_dt', text: '' },
-      { key: UIType.CurveRoad, icon: 'road_wl', text: '' },
+      { key: UIType.OneEdgeOneLanRoad, icon: "road_ss", text: "" },
+      { key: UIType.OneEdgeTwoLanRoad, icon: "road_sd", text: "" },
+      { key: UIType.OneEdgeThreeLanRoad, icon: "road_st", text: "" },
+      { key: UIType.TwoEdgeOneLanRoad, icon: "road_ds", text: "" },
+      { key: UIType.TwoEdgeTwoLanRoad, icon: "road_dd", text: "" },
+      { key: UIType.TwoEdgeThreeLanRoad, icon: "road_dt", text: "" },
+      { key: UIType.CurveRoad, icon: "road_wl", text: "" },
       // { key: UITypeExtend.structure, icon: 'r_template', text: '道路结构', extend: structureMenusRaw },
-      { key: UITypeExtend.template, icon: 'r_structure', text: '道路模板', extend: templateMenusRaw },
+      {
+        key: UITypeExtend.template,
+        icon: "r_structure",
+        text: "道路模板",
+        extend: templateMenusRaw,
+      },
     ],
   },
   {
     key: UITypeExtend.image,
-    text: '图例',
-    icon: 'legend',
+    text: "图例",
+    icon: "legend",
     // children: [
     //   { key: UIType.BusPlane, text: "客车平面图" }
     // ]
   },
-  { key: UIType.FixPoint, text: '固定点', icon: 'text' },
+  { key: UIType.FixPoint, text: "固定点", icon: "point_a" },
   {
     key: UITypeExtend.measure,
-    text: '测量',
+    text: "测量",
     children: measureMenusRaw,
-    icon: 'measure',
+    icon: "measure",
   },
-  { key: UIType.Text, text: '文字', icon: 'text' },
-  { key: UIType.Magnifier, text: '放大镜', icon: 'magnify_g' },
+  { key: UIType.Text, text: "文字", icon: "text" },
+  { key: UIType.Magnifier, text: "放大镜", icon: "magnify_g" },
 
   // { key: UITypeExtend.photo, text: "照片库" },
   {
     key: UITypeExtend.setup,
-    icon: 'setting',
-    text: '设置',
+    icon: "setting",
+    text: "设置",
     onClick() {
       const { destroy } = mount(Setting, {
         props: {
@@ -200,26 +223,37 @@ export const mainMenusRaw: MenusRaw = [
 ];
 
 export const photoMenusRaw: MenusRaw = [
-  { key: UIType.Text, text: '文字', icon: 'text' },
-  { key: UIType.Circle, text: '圈出', icon: 'circle' },
-  { key: UIType.SingleArrow, text: '箭头', icon: 'arrows' },
-  { key: UIType.Magnifier, text: '放大镜', icon: 'magnify_g' },
+  { key: UIType.Text, text: "文字", icon: "text" },
+  { key: UIType.Circle, text: "圈出", icon: "circle" },
+  { key: UIType.SingleArrow, text: "箭头", icon: "arrows" },
+  { key: UIType.Magnifier, text: "放大镜", icon: "magnify_g" },
 ];
 
 export const headActionMenuRaw = [
-  { key: UIType.GoBack, text: '回退', icon: 'backout' },
-  { key: UIType.GoAhead, text: '前进', icon: 'redo' },
-  { key: UIType.Clear, text: '清除', icon: 'reset' },
-  { key: UIType.BackImageChange, text: '底图开关', icon: 'map' },
+  { key: UIType.GoBack, text: "回退", icon: "backout" },
+  { key: UIType.GoAhead, text: "前进", icon: "redo" },
+  { key: UIType.Clear, text: "清除", icon: "reset" },
+  { key: UIType.BackImageChange, text: "底图开关", icon: "map" },
 ];
 
 export const focusMenuRaw: { [key in string]: MenusRaw } = {};
 
-export const generateByMenus = <T extends MenuRaw>(generateFn: (men: MenuRaw) => T, mainMenus: MenusRaw = mainMenusRaw) => generateByMenusRaw(generateFn, mainMenus);
+export const generateByMenus = <T extends MenuRaw>(
+  generateFn: (men: MenuRaw) => T,
+  mainMenus: MenusRaw = mainMenusRaw
+) => generateByMenusRaw(generateFn, mainMenus);
 
-export const findMainMenuByAttr = (value: MenusRaw, attr = 'extend' as const, mainMenus = mainMenusRaw) => findMenuByAttr(value, attr, mainMenus);
+export const findMainMenuByAttr = (
+  value: MenusRaw,
+  attr = "extend" as const,
+  mainMenus = mainMenusRaw
+) => findMenuByAttr(value, attr, mainMenus);
 
-export const generateMixMenus = <T extends {}, K extends keyof MenuRaw>(childKey: K, generateFn: (men: MenuRaw) => T, mainMenus: MenusRaw = mainMenusRaw) =>
+export const generateMixMenus = <T extends {}, K extends keyof MenuRaw>(
+  childKey: K,
+  generateFn: (men: MenuRaw) => T,
+  mainMenus: MenusRaw = mainMenusRaw
+) =>
   generateMixMenusRaw(
     childKey,
     generateFn,

+ 96 - 82
src/views/photos/index.vue

@@ -1,42 +1,57 @@
 <template>
   <MainPanel>
     <template v-slot:header>
-      <Header :count="selects.length" :title="`全部照片(${photos.length})`" type="return" :on-back="() => router.back()">
-        <ui-button type="primary" @click="selectMode = !selectMode" width="96px" v-if="sortPhotos.length">
-          {{ selectMode ? '取消' : '选择' }}
+      <Header
+        :count="selects.length"
+        :title="`全部照片(${photos.length})`"
+        type="return"
+        :on-back="() => router.back()"
+      >
+        <ui-button
+          type="primary"
+          @click="selectMode = !selectMode"
+          width="96px"
+          v-if="sortPhotos.length"
+        >
+          {{ selectMode ? "取消" : "选择" }}
         </ui-button>
       </Header>
     </template>
 
     <Photos
       undata-msg="无照片。请返回场景拍照以获取照片"
-      :getURL="data => data.urlRaw || data.url"
+      :getURL="(data) => data.urlRaw || data.url"
       v-model:active="active"
       v-model:selects="selects"
       :select-mode="selectMode"
       :data="sortPhotos"
     />
-<!--    @click="router.push(writeRouteName.scene)"-->
+    <!--    @click="router.push(writeRouteName.scene)"-->
     <ButtonPane class="back fun-ctrl" v-if="!selectMode">
       <ui-icon type="photo" class="icon" />
       <ui-input
-          type="select"
-          :options="photoOptions"
-          dire="top"
-          class="select"
-          v-model="photoType"
+        type="select"
+        :options="photoOptions"
+        dire="top"
+        class="select"
+        v-model="photoType"
       />
     </ButtonPane>
 
-    <ActionMenus class="select-menus" :menus="selectMenus" dire="row" v-if="selects.length" />
+    <ActionMenus
+      class="select-menus"
+      :menus="selectMenus"
+      dire="row"
+      v-if="selects.length"
+    />
   </MainPanel>
 
   <FillSlide
-      :data="sortPhotos"
-      v-model:active="active"
-      @quit="active = null"
-      v-if="active"
-      :getURL="data => data.urlRaw || data.url"
+    :data="sortPhotos"
+    v-model:active="active"
+    @quit="active = null"
+    v-if="active"
+    :getURL="(data) => data.urlRaw || data.url"
   >
     <template v-slot:foot>
       <ActionMenus class="menus" :menus="menus" dire="row" />
@@ -45,146 +60,146 @@
 </template>
 
 <script setup lang="ts">
-import MainPanel from '@/components/main-panel/index.vue'
-import FillSlide from '@/components/fill-slide/index.vue'
-import Header from '@/components/photos/header.vue'
-import {PhotoRaw, photos} from '@/store/photos'
+import MainPanel from "@/components/main-panel/index.vue";
+import FillSlide from "@/components/fill-slide/index.vue";
+import Header from "@/components/photos/header.vue";
+import { PhotoRaw, photos } from "@/store/photos";
 import UiIcon from "@/components/base/components/icon/index.vue";
-import {router, writeRouteName} from '@/router'
+import { router, writeRouteName } from "@/router";
 import ButtonPane from "@/components/button-pane/index.vue";
-import {computed, onDeactivated, ref, watchEffect} from "vue";
-import {Mode} from '@/views/graphic/menus'
+import { computed, onDeactivated, ref, watchEffect } from "vue";
+import { Mode } from "@/views/graphic/menus";
 import UiButton from "@/components/base/components/button/index.vue";
-import Photos from '@/components/photos'
+import Photos from "@/components/photos/index.vue";
 import ActionMenus from "@/components/group-button/index.vue";
-import {useConfirm} from "@/hook";
+import { useConfirm } from "@/hook";
 import UiInput from "@/components/base/components/input/index.vue";
-import {api} from "@/store/sync";
-import {getId} from "@/utils";
+import { api } from "@/store/sync";
+import { getId } from "@/utils";
 
-const sortPhotos = computed(() => [...photos.value].reverse())
-const active = ref<PhotoRaw>()
-const selectMode = ref(false)
-const selects = ref<PhotoRaw[]>([])
+const sortPhotos = computed(() => [...photos.value].reverse());
+const active = ref<PhotoRaw>();
+const selectMode = ref(false);
+const selects = ref<PhotoRaw[]>([]);
 const menus = [
   {
     key: "road",
     text: "现场绘图",
     icon: "draw_s",
-    onClick: () => gotoDraw(Mode.Road)
+    onClick: () => gotoDraw(Mode.Road),
   },
   {
     key: "accident",
     icon: "label",
     text: "照片标注",
-    onClick: () => gotoDraw(Mode.Photo)
+    onClick: () => gotoDraw(Mode.Photo),
   },
   {
     key: "del",
     icon: "del",
     text: "删除",
-    onClick: () => delPhoto()
-  }
-]
+    onClick: () => delPhoto(),
+  },
+];
 
 const selectMenus = [
   {
     key: "del",
     icon: "del",
     text: "删除",
-    onClick: () => delSelects()
+    onClick: () => delSelects(),
   },
-]
+];
 
-const photoType = ref<string>()
+const photoType = ref<string>();
 const photoOptions = [
-  {value: "photograph", label: "相机拍照"},
-  {value: "selectPhotoAlbum", label: "相册选择"},
-  {value: "scene", label: "场景截图"},
-]
+  { value: "photograph", label: "相机拍照" },
+  { value: "selectPhotoAlbum", label: "相册选择" },
+  { value: "scene", label: "场景截图" },
+];
 
 watchEffect(() => {
   if (photoType.value) {
     if (photoType.value === "scene") {
-      router.push(writeRouteName.scene)
+      router.push(writeRouteName.scene);
     } else {
-      api[photoType.value]()
-        .then(url => {
-          photos.value.push({
-            id: getId(),
-            url,
-            urlRaw: url,
-            time: new Date().getTime(),
-            meterPerPixel: null,
-            measures: [],
-            baseLines: [],
-            fixPoints: [],
-            basePoints: []
-          })
-        })
+      api[photoType.value]().then((url) => {
+        photos.value.push({
+          id: getId(),
+          url,
+          urlRaw: url,
+          time: new Date().getTime(),
+          meterPerPixel: null,
+          measures: [],
+          baseLines: [],
+          fixPoints: [],
+          basePoints: [],
+        });
+      });
     }
-    photoType.value = null
+    photoType.value = null;
   }
-})
+});
 
 watchEffect(() => {
   if (!selectMode.value) {
-    selects.value = []
+    selects.value = [];
   }
-})
+});
 
 const delPhotoRaw = (photo = active.value) => {
-  const index = photos.value.indexOf(photo)
-  const reset = active.value ? photos.value.indexOf(active.value) : -1
+  const index = photos.value.indexOf(photo);
+  const reset = active.value ? photos.value.indexOf(active.value) : -1;
   if (~index) {
-    photos.value.splice(index, 1)
+    photos.value.splice(index, 1);
   }
   if (~reset) {
     if (reset >= photos.value.length) {
       if (photos.value.length) {
-        active.value = photos.value[photos.value.length - 1]
+        active.value = photos.value[photos.value.length - 1];
       } else {
-        active.value = null
+        active.value = null;
       }
     } else {
-      active.value = photos.value[reset]
+      active.value = photos.value[reset];
     }
   }
-}
+};
 const delPhoto = async (photo = active.value) => {
   if (await useConfirm(`确定要删除此数据?`)) {
-    delPhotoRaw(photo)
+    delPhotoRaw(photo);
   }
-}
+};
 const delSelects = async () => {
   if (await useConfirm(`确定要删除这${selects.value.length}项数据?`)) {
     while (selects.value.length) {
-      delPhotoRaw(selects.value[0])
-      selects.value.shift()
+      delPhotoRaw(selects.value[0]);
+      selects.value.shift();
     }
     if (!sortPhotos.value.length) {
-      selectMode.value = false
+      selectMode.value = false;
     }
   }
-}
+};
 
 const gotoDraw = (mode: Mode) => {
   router.push({
     name: writeRouteName.graphic,
-    params: {mode, id: active.value.id, action: 'add'}
-  })
-}
+    params: { mode, id: active.value.id, action: "add" },
+  });
+};
 
 onDeactivated(() => {
-  active.value = null
-})
+  active.value = null;
+  selectMode.value = false;
+});
 </script>
 
 <style scoped lang="scss">
 .fun-ctrl {
   color: #fff;
   font-size: 20px;
-  transition: color .3s ease;
+  transition: color 0.3s ease;
 
   .icon {
     position: absolute;
@@ -217,5 +232,4 @@ onDeactivated(() => {
   transform: translateX(-50%);
   bottom: var(--boundMargin);
 }
-
 </style>

+ 76 - 73
src/views/roads/index.vue

@@ -1,21 +1,26 @@
 <template>
   <MainPanel>
     <template v-slot:header>
-      <Header :count="selects.length" :title="`现场图管理(${sortPhotos.length})`" type="return_l" :on-back="() => api.closePage()">
+      <Header
+        :count="selects.length"
+        :title="`现场图管理(${sortPhotos.length})`"
+        type="return_l"
+        :on-back="() => api.closePage()"
+      >
         <ui-button
-            :type="selectMode ? 'primary' : 'normal'"
-            @click="selectMode = !selectMode"
-            width="96px"
-            style="margin-right: 16px"
-            v-if="sortPhotos.length"
+          :type="selectMode ? 'primary' : 'normal'"
+          @click="selectMode = !selectMode"
+          width="96px"
+          style="margin-right: 16px"
+          v-if="sortPhotos.length"
         >
-          {{ selectMode ? '取消' : '选择' }}
+          {{ selectMode ? "取消" : "选择" }}
         </ui-button>
         <ui-button
-            v-if="!selectMode"
-            type="primary"
-            @click="router.push({name: writeRouteName.photos})"
-            width="96px"
+          v-if="!selectMode"
+          type="primary"
+          @click="router.push({ name: writeRouteName.photos })"
+          width="96px"
         >
           新增
         </ui-button>
@@ -23,28 +28,32 @@
     </template>
 
     <Photos
-        undata-msg="无现场图。请点击右上角按钮绘制现场图。"
-        v-model:active="active"
-        v-model:selects="selects"
-        :select-mode="selectMode"
-        :data="sortPhotos"
-        :getURL="data => data?.table?.url || data.url"
+      undata-msg="无现场图。请点击右上角按钮绘制现场图。"
+      v-model:active="active"
+      v-model:selects="selects"
+      :select-mode="selectMode"
+      :data="sortPhotos"
+      :getURL="(data) => data?.table?.url || data.url"
     >
-      <template v-slot="{data}">
-        <p>{{ data.title || '默认标题' }}</p>
+      <template v-slot="{ data }">
+        <p>{{ data.title || "默认标题" }}</p>
       </template>
     </Photos>
 
-
-    <ActionMenus class="select-menus" :menus="selectMenus" dire="row" v-if="selects.length" />
+    <ActionMenus
+      class="select-menus"
+      :menus="selectMenus"
+      dire="row"
+      v-if="selects.length"
+    />
   </MainPanel>
 
   <FillSlide
-      :getURL="data => data?.table?.url || data.url"
-      :data="sortPhotos"
-      v-model:active="active"
-      @quit="active = null"
-      v-if="active"
+    :getURL="(data) => data?.table?.url || data.url"
+    :data="sortPhotos"
+    v-model:active="active"
+    @quit="active = null"
+    v-if="active"
   >
     <template v-slot:foot>
       <ActionMenus class="menus" :menus="menus" dire="row" />
@@ -53,106 +62,102 @@
 </template>
 
 <script setup lang="ts">
-import MainPanel from '@/components/main-panel/index.vue'
-import FillSlide from '@/components/fill-slide/index.vue'
-import {roadPhotos, RoadPhoto} from '@/store/roadPhotos'
+import MainPanel from "@/components/main-panel/index.vue";
+import FillSlide from "@/components/fill-slide/index.vue";
+import { roadPhotos, RoadPhoto } from "@/store/roadPhotos";
 import ActionMenus from "@/components/group-button/index.vue";
-import {router, writeRouteName} from '@/router'
-import {computed, onDeactivated, ref, watchEffect} from "vue";
-import {Mode} from '@/views/graphic/menus'
+import { router, writeRouteName } from "@/router";
+import { computed, onDeactivated, ref, watchEffect } from "vue";
+import { Mode } from "@/views/graphic/menus";
 import UiButton from "@/components/base/components/button/index.vue";
 import Header from "@/components/photos/header.vue";
 import Photos from "@/components/photos/index.vue";
 import ButtonPane from "@/components/button-pane/index.vue";
 import UiIcon from "@/components/base/components/icon/index.vue";
-import {useConfirm} from "@/hook";
-import {api} from "@/store/sync";
-import {photos} from "@/store/photos";
-
-
-const sortPhotos = computed(() => [...roadPhotos.value].reverse())
-const active = ref<RoadPhoto>()
-const selectMode = ref(false)
-const selects = ref<RoadPhoto[]>([])
+import { useConfirm } from "@/hook";
+import { api } from "@/store/sync";
+import { photos } from "@/store/photos";
+
+const sortPhotos = computed(() => [...roadPhotos.value].reverse());
+const active = ref<RoadPhoto>();
+const selectMode = ref(false);
+const selects = ref<RoadPhoto[]>([]);
 const menus = [
   {
     key: "road",
     icon: "edit",
     text: "修改",
-    onClick: () => gotoDraw()
+    onClick: () => gotoDraw(),
   },
   {
     key: "del",
     icon: "del",
     text: "删除",
-    onClick: () => delPhoto()
-  }
-]
+    onClick: () => delPhoto(),
+  },
+];
 
 const selectMenus = [
   {
     key: "del",
     icon: "del",
     text: "删除",
-    onClick: () => delSelects()
+    onClick: () => delSelects(),
   },
-]
+];
 
 watchEffect(() => {
   if (!selectMode.value) {
-    selects.value = []
+    selects.value = [];
   }
-})
+});
 
 const delPhotoRaw = (roadPhoto = active.value) => {
-  const index = roadPhotos.value.indexOf(roadPhoto)
-  const reset = active.value ? roadPhotos.value.indexOf(active.value) : -1
+  const index = roadPhotos.value.indexOf(roadPhoto);
+  const reset = active.value ? roadPhotos.value.indexOf(active.value) : -1;
   if (~index) {
-    roadPhotos.value.splice(index, 1)
+    roadPhotos.value.splice(index, 1);
   }
   if (~reset) {
     if (reset >= roadPhotos.value.length) {
       if (roadPhotos.value.length) {
-        active.value = roadPhotos.value[roadPhotos.value.length - 1]
+        active.value = roadPhotos.value[roadPhotos.value.length - 1];
       } else {
-        active.value = null
+        active.value = null;
       }
     } else {
-      active.value = roadPhotos.value[reset]
+      active.value = roadPhotos.value[reset];
     }
   }
-}
+};
 
 const delPhoto = async (photo = active.value) => {
   if (await useConfirm(`确定要删除此数据?`)) {
-    delPhotoRaw(photo)
+    delPhotoRaw(photo);
   }
-
-}
+};
 const delSelects = async () => {
   if (await useConfirm(`确定要删除这${selects.value.length}项数据?`)) {
     while (selects.value.length) {
-      delPhotoRaw(selects.value[0])
-      selects.value.shift()
+      delPhotoRaw(selects.value[0]);
+      selects.value.shift();
     }
     if (!sortPhotos.value.length) {
-      selectMode.value = false
+      selectMode.value = false;
     }
   }
-}
-
+};
 
 const gotoDraw = () => {
   router.push({
     name: writeRouteName.graphic,
-    params: {mode: Mode.Road, id: active.value.id, action: 'update'}
-  })
-}
+    params: { mode: Mode.Road, id: active.value.id, action: "update" },
+  });
+};
 
 onDeactivated(() => {
-  active.value = null
-})
-
+  active.value = null;
+});
 </script>
 
 <style scoped lang="scss">
@@ -170,7 +175,7 @@ onDeactivated(() => {
 .fun-ctrl {
   color: #fff;
   font-size: 20px;
-  transition: color .3s ease;
+  transition: color 0.3s ease;
 
   .icon {
     position: absolute;
@@ -184,11 +189,9 @@ onDeactivated(() => {
   bottom: var(--boundMargin);
 }
 
-
 .select-menus {
   left: 50%;
   transform: translateX(-50%);
   bottom: var(--boundMargin);
 }
-
 </style>

+ 170 - 128
src/views/roads/tabulation.vue

@@ -2,113 +2,109 @@
   <MainPanel>
     <template v-slot:header>
       <Header title="现场绘图 | 制表" :on-back="onBack" type="return">
-        <ui-button
-            type="primary"
-            @click="saveHandler"
-            width="96px"
-        >
-          保存
-        </ui-button>
+        <ui-button type="primary" @click="saveHandler" width="96px"> 保存 </ui-button>
       </Header>
     </template>
 
-    <div class="tab-layout" v-if="roadPhoto" :class="{downMode}">
+    <div class="tab-layout" v-if="roadPhoto" :class="{ downMode }">
       <div class="content" ref="layoutRef">
         <table>
           <tr>
-            <td class="value title" colspan="6" height="64">
-              <span v-if="downMode">{{roadPhoto.title}}</span>
+            <td class="value title" colspan="6" height="62">
+              <span v-if="downMode">{{ roadPhoto.title }}</span>
               <ui-input
-                  v-else
-                  type="text"
-                  @input="input"
-                  v-model="roadPhoto.title"
-                  @blur="history.push"
+                v-else
+                type="text"
+                @input="input"
+                v-model="roadPhoto.title"
+                @blur="history.push"
               />
             </td>
           </tr>
           <tr>
-            <td class="label" width="150" height="64">到达事故现场时间</td>
+            <td class="label" width="150" height="50">到达事故现场时间</td>
             <td class="value">
-              <span v-if="downMode">{{history.value.arrivalTime}}</span>
+              <span v-if="downMode">{{ history.value.arrivalTime }}</span>
               <ui-input
-                  v-else
-                  type="text"
-                  @input="input"
-                  v-model="history.value.arrivalTime"
-                  @blur="history.push"
+                v-else
+                type="text"
+                @input="input"
+                v-model="history.value.arrivalTime"
+                @blur="history.push"
               />
             </td>
             <td class="label" width="100">天气</td>
             <td class="value" width="80">
-              <span v-if="downMode">{{history.value.weather}}</span>
+              <span v-if="downMode">{{ history.value.weather }}</span>
               <ui-input
-                  v-else
-                  type="text"
-                  @input="input"
-                  v-model="history.value.weather"
-                  @blur="history.push"
+                v-else
+                type="text"
+                @input="input"
+                v-model="history.value.weather"
+                @blur="history.push"
               />
             </td>
             <td class="label" width="100">路面性质</td>
             <td class="value" width="150">
-              <span v-if="downMode">{{history.value.conditions}}</span>
+              <span v-if="downMode">{{ history.value.conditions }}</span>
               <ui-input
-                  v-else
-                  type="text"
-                  @input="input"
-                  v-model="history.value.conditions"
-                  @blur="history.push"
+                v-else
+                type="text"
+                @input="input"
+                v-model="history.value.conditions"
+                @blur="history.push"
               />
             </td>
           </tr>
           <tr>
-            <td class="label"  height="64">事故发生地点</td>
+            <td class="label" height="40">事故发生地点</td>
             <td class="value" colspan="5">
-              <span v-if="downMode">{{history.value.location}}</span>
+              <span v-if="downMode">{{ history.value.location }}</span>
               <ui-input
-                  v-else
-                  type="text"
-                  @input="input"
-                  v-model="history.value.location"
-                  @blur="history.push"
+                v-else
+                type="text"
+                @input="input"
+                v-model="history.value.location"
+                @blur="history.push"
               />
             </td>
           </tr>
           <tr>
-            <td class="image" colspan="6" height="360">
+            <td class="image" colspan="6" height="676">
               <div class="photo-layout">
                 <img
-                    :src="useStaticUrl(roadPhoto.url).value"
-                    @blur="history.push"
-                    class="photo"
-                    :style="{transform: photoCSSMatrix}"
-                    ref="photoRef"
+                  :src="useStaticUrl(roadPhoto.url).value"
+                  @blur="history.push"
+                  class="photo"
+                  :style="{ transform: photoCSSMatrix }"
+                  ref="photoRef"
                 />
                 <img
-                    src="/static/compass.png"
-                    class="compass"
-                    :style="{transform: compassCSSMatrix}"
-                    ref="compassRef"
+                  src="/static/compass.png"
+                  class="compass"
+                  :style="{ transform: compassCSSMatrix }"
+                  ref="compassRef"
                 />
                 <p class="compass-info">比例1 : {{ proportion }}</p>
               </div>
             </td>
           </tr>
           <tr>
-            <td class="value" colspan="6" height="64">
-              <span v-if="downMode">{{history.value.illustrate}}</span>
+            <td class="value textarea-layout" colspan="6" height="73">
+              <span v-if="downMode">{{ history.value.illustrate }}</span>
               <ui-input
-                  v-else
-                  type="text"
-                  @input="input"
-                  v-model="history.value.illustrate"
-                  @blur="history.push"
+                class="textarea"
+                v-else
+                type="textarea"
+                @input="input"
+                v-model="history.value.illustrate"
+                @blur="history.push"
+                :maxlength="120"
               />
             </td>
           </tr>
-          <tr >
-            <td class="value date" colspan="6" height="48">
+          <tr>
+            <td class="value date" colspan="6" height="50">
               {{ formatDate(new Date(), "yyyy年MM月dd日hh时mm分") }}
             </td>
           </tr>
@@ -125,103 +121,107 @@
 </template>
 
 <script setup lang="ts">
-import { router, writeRouteName } from '@/router'
+import { router, writeRouteName } from "@/router";
 import { formatDate } from "@/utils";
-import {computed, nextTick, ref, watchEffect} from "vue";
-import { useHistory } from '@/hook/useHistory'
-import {roadPhotos, RoadPhoto, getDefaultTable} from "@/store/roadPhotos";
-import {useStaticUrl} from "@/hook/useStaticUrl";
-import html2canvas from 'html2canvas'
+import { computed, nextTick, onDeactivated, ref, watchEffect } from "vue";
+import { useHistory } from "@/hook/useHistory";
+import { roadPhotos, RoadPhoto, getDefaultTable } from "@/store/roadPhotos";
+import { useStaticUrl } from "@/hook/useStaticUrl";
+import html2canvas from "html2canvas";
 import UiButton from "@/components/base/components/button/index.vue";
 import UiInput from "@/components/base/components/input/index.vue";
-import {HandMode, useHand} from '@/hook/useHand'
+import { HandMode, useHand } from "@/hook/useHand";
 import Header from "@/components/photos/header.vue";
 import MainPanel from "@/components/main-panel/index.vue";
-import {downloadImage, uploadImage} from "@/store/sync";
-import {Mode} from "@/views/graphic/menus";
+import { downloadImage, uploadImage } from "@/store/sync";
+import { Mode } from "@/views/graphic/menus";
 import Message from "@/components/base/components/message/message.vue";
 
 const roadPhoto = computed<RoadPhoto>(() => {
-  let route, params, data
-  if (((route = router.currentRoute.value).name === writeRouteName.tabulation)
-    && (params = route.params).id
-    && (data = roadPhotos.value.find(data => data.id === params.id))) {
-    return data
+  let route, params, data;
+  if (
+    (route = router.currentRoute.value).name === writeRouteName.tabulation &&
+    (params = route.params).id &&
+    (data = roadPhotos.value.find((data) => data.id === params.id))
+  ) {
+    return data;
   } else {
     // router.back();
   }
-})
+});
 const history = computed(
   () => roadPhoto.value && useHistory(getDefaultTable(roadPhoto.value))
-)
+);
 
-const input = () => history.value.state.hasRedo = false
+const input = () => (history.value.state.hasRedo = false);
 
-const compassRef = ref<HTMLImageElement>()
+const compassRef = ref<HTMLImageElement>();
 const { cssMatrix: compassCSSMatrix, matrix: compassMatrix } = useHand(
   compassRef,
   HandMode.Angle,
   () => {
-    history.value.value.compassAngle = compassMatrix.value
-    history.value.push()
+    history.value.value.compassAngle = compassMatrix.value;
+    history.value.push();
   },
   history.value.value.compassAngle
-)
-const photoRef = ref<HTMLImageElement>()
+);
+const photoRef = ref<HTMLImageElement>();
 const { cssMatrix: photoCSSMatrix, matrix: photoMatrix } = useHand(
   photoRef,
   HandMode.MoveAndScale,
   () => {
-    history.value.value.imageTransform = photoMatrix.value
-    history.value.push()
+    history.value.value.imageTransform = photoMatrix.value;
+    history.value.push();
   },
   history.value.value.imageTransform
-)
-
-const proportion = ref(1)
-const photoLoaded = ref(false)
+);
+onDeactivated(() => (photoLoaded.value = false));
+const proportion = ref(1);
+const photoLoaded = ref(false);
 watchEffect(() => {
   if (!roadPhoto.value || !photoRef.value) {
     return;
   }
   if (!photoLoaded.value) {
-    photoRef.value.onload = () => photoLoaded.value = true
+    photoRef.value.onload = () => (photoLoaded.value = true);
+    return;
   }
-  const scale = roadPhoto.value.data.scale || 1
-  const martrixScale = photoMatrix.value[0]
-  const photoWidth = photoRef.value.naturalWidth
-  const prop = ((photoWidth / photoRef.value.offsetWidth) * scale) / martrixScale
-  proportion.value = Math.ceil(prop * 100) / 100
-})
-
+  const scale = roadPhoto.value.data.scale / window.devicePixelRatio || 1;
+  const martrixScale = photoMatrix.value[0];
+  const photoWidth = photoRef.value.naturalWidth;
+  const prop = ((photoWidth / photoRef.value.offsetWidth) * scale) / martrixScale;
+  proportion.value = Math.ceil(1 / prop);
+});
 
 const onBack = () => {
   router.replace({
     name: writeRouteName.graphic,
-    params: {mode: Mode.Road, id: roadPhoto.value.id, action: 'update'}
-  })
-}
+    params: { mode: Mode.Road, id: roadPhoto.value.id, action: "update" },
+  });
+};
 
-const downMode = ref(false)
-const layoutRef = ref<HTMLDivElement>()
+const downMode = ref(false);
+// const downMode = ref(true);
+const layoutRef = ref<HTMLDivElement>();
 const getLayoutImage = async () => {
-  downMode.value = true
-  await nextTick()
-  const canvas = await html2canvas(layoutRef.value)
-  Message.success({ msg: "已保存至相册", time: 2000 } )
-  downMode.value = false
-  const blob = await new Promise<Blob>(resolve => canvas.toBlob(resolve, "image/jpeg", 0.95))
-  await downloadImage(blob)
-  return await uploadImage(blob)
-}
+  downMode.value = true;
+  await nextTick();
+  const canvas = await html2canvas(layoutRef.value);
+  Message.success({ msg: "已保存至相册", time: 2000 });
+  downMode.value = false;
+  const blob = await new Promise<Blob>((resolve) =>
+    canvas.toBlob(resolve, "image/jpeg", 0.95)
+  );
+  await downloadImage(blob);
+  return await uploadImage(blob);
+};
 const saveHandler = async () => {
   roadPhoto.value.table = {
     ...history.value.value,
-    url: await getLayoutImage()
-  }
-  router.replace({name: writeRouteName.roads})
-}
-
+    url: await getLayoutImage(),
+  };
+  router.replace({ name: writeRouteName.roads });
+};
 </script>
 
 <style lang="scss" scoped>
@@ -232,6 +232,8 @@ const saveHandler = async () => {
   overflow-y: auto;
   left: 0;
   right: 0;
+
+  font-family: SimSun-Regular, SimSun;
   color: #000;
   font-size: 16px;
 }
@@ -283,10 +285,9 @@ const saveHandler = async () => {
 
 .content table {
   width: 100%;
-  height: 800px;
+  // height: 800px;
   border-collapse: collapse;
 
-
   tr:not(:first-child) {
     &:nth-child(2) td {
       border-top: 2px solid #000;
@@ -307,11 +308,11 @@ const saveHandler = async () => {
 
   .value {
     height: 43px;
-    background-color: #D4E8FF;
+    background-color: #d4e8ff;
   }
 
   .title {
-    padding-bottom: 16px;
+    padding-bottom: 7px;
     position: relative;
 
     &:after {
@@ -331,12 +332,33 @@ const saveHandler = async () => {
 }
 
 .downMode {
+  .title {
+    span {
+      height: 62px !important;
+      font-size: 54px !important;
+      font-family: SimSun-Regular, SimSun;
+      font-weight: 400;
+      color: #000000;
+      line-height: 63px;
+      letter-spacing: 13px;
+    }
+  }
+
   .content {
     width: 1485px;
     height: 1050px;
-    padding: 125px 100px 75px 100px;
+    // padding: 125px 100px 75px 100px;
+    padding: 30px 28px 26px;
     overflow: hidden;
   }
+  .content table .textarea-layout {
+    height: 73px;
+    span {
+      padding: 10px 0px;
+      display: block;
+      height: 100%;
+    }
+  }
   .content table {
     .value {
       background: none;
@@ -344,23 +366,23 @@ const saveHandler = async () => {
 
     tr:not(:first-child) {
       td {
-        border-right: 5px solid #000;
-        border-bottom: 5px solid #000;
+        border-right: 3px solid #000;
+        border-bottom: 3px solid #000;
       }
 
       &:nth-child(2) td {
-        border-top: 7.5px solid #000;
+        border-top: 3px solid #000;
       }
 
       td:first-child {
-        border-left: 7.5px solid #000;
+        border-left: 3px solid #000;
       }
       td:last-child {
-        border-right: 7.5px solid #000;
+        border-right: 3px solid #000;
       }
 
       &:last-child td {
-        border-bottom: 7.5px solid #000;
+        border-bottom: 3px solid #000;
       }
     }
   }
@@ -379,7 +401,8 @@ const saveHandler = async () => {
 <style lang="scss">
 .value {
   box-sizing: border-box;
-  padding: 8px 10px;
+  // padding: 8px 10px;
+  padding: 0 10px;
   input,
   .ui-input {
     width: 100%;
@@ -398,7 +421,7 @@ const saveHandler = async () => {
     display: block;
     font-size: 32px !important;
     height: 48px !important;
-    font-weight: bold;
+    font-weight: 400;
     text-align: center;
     line-height: 48px !important;
   }
@@ -411,4 +434,23 @@ const saveHandler = async () => {
     line-height: 48px !important;
   }
 }
+.textarea {
+  textarea {
+    color: #000 !important;
+    font-size: 16px !important;
+    line-height: 1.2em;
+  }
+}
+
+.textarea-layout {
+  .textarea {
+    height: 100% !important;
+  }
+  .retouch {
+    justify-content: flex-end !important;
+    .len {
+      color: #000 !important;
+    }
+  }
+}
 </style>

+ 27 - 29
src/views/scene/TrackMeasure.vue

@@ -1,46 +1,45 @@
 <template>
   <div class="photo-btn">
-    <ButtonPane
-        class="item fun-ctrl"
-        :size="80"
-        @click="callback"
-    >
-      <ui-icon type="affirm" class="icon" />
+    <ButtonPane class="item fun-ctrl" :size="80" @click="callback">
+      <ui-icon type="return" class="icon" />
     </ButtonPane>
     <ButtonPane class="item fun-ctrl" :size="80" @click="active = !active">
-      <ui-icon type="line_h" class="icon" :class="{active}" />
+      <ui-icon type="line_h" class="icon" :class="{ active }" />
     </ButtonPane>
   </div>
 </template>
 <script setup lang="ts">
 import UiIcon from "@/components/base/components/icon/index.vue";
 import ButtonPane from "@/components/button-pane/index.vue";
-import {startMeasure, SuccessMeasureAtom} from "@/views/scene/linkage/measure";
-import {onActivated, onMounted, ref, watchEffect} from "vue";
-import {tempMeasures} from "@/store/measure";
-import {measureDisabledStack} from '@/hook/custom'
+import { startMeasure, SuccessMeasureAtom } from "@/views/scene/linkage/measure";
+import { onActivated, onMounted, ref, watchEffect, nextTick } from "vue";
+import { tempMeasures } from "@/store/measure";
+import { measureDisabledStack } from "@/hook/custom";
+import { useSDK } from "@/hook";
 
-const props = defineProps<{ onConfirm: (data: SuccessMeasureAtom) => void }>()
-const active = ref(true)
+const props = defineProps<{ onConfirm: (data: SuccessMeasureAtom) => void }>();
+const active = ref(true);
 const callback = () => {
-  props.onConfirm(tempMeasures.value[0] as any)
-  tempMeasures.value = []
-}
+  props.onConfirm(tempMeasures.value[0] as any);
+  tempMeasures.value = [];
+};
 watchEffect(() => {
   if (active.value) {
-    tempMeasures.value = []
-    startMeasure('L_LINE', 'red')
-      .then((measure) => {
-        if (measure) {
-          active.value = null
-          tempMeasures.value = [measure]
-
-          console.log(tempMeasures.value)
-        }
-      })
+    tempMeasures.value = [];
+    startMeasure("L_LINE", "red").then((measure) => {
+      if (measure) {
+        active.value = null;
+        tempMeasures.value = [measure];
+        nextTick(() => {
+          const canvas = useSDK().carry.measureMap.get(measure);
+          canvas.bus.on("update", (pos) => {
+            measure.length = canvas.getDistance().value;
+          });
+        });
+      }
+    });
   }
-})
-
+});
 </script>
 
 <style lang="scss" scoped>
@@ -66,5 +65,4 @@ watchEffect(() => {
     font-size: 28px;
   }
 }
-
 </style>

+ 108 - 94
src/views/scene/menus/actions.ts

@@ -1,41 +1,54 @@
-import {findMenuByKey, menuEnum, MenuRaw} from "@/views/scene/menus/menus";
-import {continuedMeasure, startMeasure, stopMeasure as stopMeasureRaw} from "@/views/scene/linkage/measure";
-import {list, MeasureAtom, MeasureType} from "@/store/measure";
-import {baseLines} from '@/store/baseLine'
-import {basePoints} from "@/store/basePoint";
-import {Ref, watch} from "vue";
-import {activeBasePointStack, activeFixPointStack, customMap} from "@/hook";
-import {getCoverPos} from '../linkage/cover'
-import {Pos3D} from "@/sdk";
-import {fixPoints} from "@/store/fixPoint";
+import { findMenuByKey, menuEnum, MenuRaw } from "@/views/scene/menus/menus";
+import {
+  continuedMeasure,
+  startMeasure,
+  stopMeasure as stopMeasureRaw,
+} from "@/views/scene/linkage/measure";
+import { list, MeasureAtom, MeasureType } from "@/store/measure";
+import { baseLines } from "@/store/baseLine";
+import { basePoints } from "@/store/basePoint";
+import { Ref, watch } from "vue";
+import {
+  activeBasePointStack,
+  activeFixPointStack,
+  customMap,
+  useConfirm,
+} from "@/hook";
+import { getCoverPos } from "../linkage/cover";
+import { Pos3D } from "@/sdk";
+import { fixPoints } from "@/store/fixPoint";
 import Message from "@/components/base/components/message/message.vue";
-import { getId } from '@/utils'
+import { getId } from "@/utils";
 
-const trackPosMenuAction = (onComplete: () => void, onAddStore: (pos: Pos3D) => void, continued = false) => {
-  let onCleanup
-  let stop = false
+const trackPosMenuAction = (
+  onComplete: () => void,
+  onAddStore: (pos: Pos3D) => void,
+  continued = false
+) => {
+  let onCleanup;
+  let stop = false;
   const trackCoverPos = () => {
-    onCleanup && onCleanup()
-    onCleanup = getCoverPos(pos => {
-      onAddStore(pos)
+    onCleanup && onCleanup();
+    onCleanup = getCoverPos((pos) => {
+      onAddStore(pos);
       if (!continued) {
-        onComplete()
+        onComplete();
       } else if (!stop) {
-        trackCoverPos()
+        trackCoverPos();
       }
-    })
-  }
-  trackCoverPos()
+    });
+  };
+  trackCoverPos();
   return () => {
-    stop = true
-    onCleanup()
-  }
-}
+    stop = true;
+    onCleanup();
+  };
+};
 
 const stopMeasure = () => {
-  stopMeasureRaw()
+  stopMeasureRaw();
   // customMap.magnifier = false
-}
+};
 const trackMeasureMenuAction = (
   measureType: MeasureType,
   menu: MenuRaw,
@@ -44,156 +57,157 @@ const trackMeasureMenuAction = (
   name: string,
   color = "#3290ff"
 ) => {
-  let hide
-  let startTipEd = false
-  let endTipEd = false
+  let hide;
+  let startTipEd = false;
+  let endTipEd = false;
   const startTip = () => {
-    hide && hide()
+    hide && hide();
     if (!startTipEd) {
-      hide = Message.success({msg: `请绘制${name}`})
+      hide = Message.success({ msg: `请绘制${name}` });
       // 请选择一个位置单击,确定${name}的起点
-      startTipEd = true
+      startTipEd = true;
     }
-  }
+  };
   const firstTip = () => {
-    hide && hide()
+    hide && hide();
     if (!endTipEd) {
-      endTipEd = true
+      endTipEd = true;
       // hide = Message.success({msg: `再选择一个位置单击,确定${name}的终点`})
     }
-  }
-  startTip()
+  };
+  startTip();
   // customMap.magnifier = true
   const onAddMeasure = (data: MeasureAtom) => {
     if (data) {
       data.id = getId();
-      onAddStore(data)
-      onComplete()
+      onAddStore(data);
+      onComplete();
     }
     if (menu.continued) {
-      startTip()
+      startTip();
     }
-  }
-
+  };
 
   if (menu.continued) {
-    continuedMeasure(measureType as any, color, onAddMeasure, firstTip)
+    continuedMeasure(measureType as any, color, onAddMeasure, firstTip);
   } else {
-    startMeasure(measureType as any, color,  firstTip).then(onAddMeasure)
+    startMeasure(measureType as any, color, firstTip).then(onAddMeasure);
   }
 
   return () => {
-    stopMeasure()
-    hide()
-  }
-}
+    stopMeasure();
+    hide();
+  };
+};
 
 const menuActions = {
   [menuEnum.BASE_POINT]: (_, onComplete) => {
-    let hide = Message.success({ msg: "请单击选择基准点位置" })
+    let hide = Message.success({ msg: "请单击选择基准点位置" });
     const onDestroy = trackPosMenuAction(
       () => {
-        hide && hide()
-        onComplete()
+        hide && hide();
+        onComplete();
       },
-      pos => {
-        const len = basePoints.value.push({ id: getId(), pos })
-        activeBasePointStack.current.value.value = basePoints.value[len - 1]
+      (pos) => {
+        const len = basePoints.value.push({ id: getId(), pos });
+        activeBasePointStack.current.value.value = basePoints.value[len - 1];
         if (hide) {
-          hide()
-          hide = null
+          hide();
+          hide = null;
         }
       },
       true
-    )
+    );
     return () => {
-      onDestroy()
-      hide && hide()
-    }
+      onDestroy();
+      hide && hide();
+    };
   },
-  [menuEnum.FIX_POINT]:  (_, onComplete) => {
-    let hide = Message.success({ msg: "请单击选择固定点位置" })
+  [menuEnum.FIX_POINT]: (_, onComplete) => {
+    let hide = Message.success({ msg: "请单击选择固定点位置" });
     const onDestroy = trackPosMenuAction(
       () => {
-        hide && hide()
-        onComplete()
+        hide && hide();
+        onComplete();
       },
-      pos => {
-        const len = fixPoints.value.push({ id: getId(), pos, text: "固定点" })
-        activeFixPointStack.current.value.value = fixPoints.value[len - 1]
+      (pos) => {
+        const len = fixPoints.value.push({ id: getId(), pos, text: "固定点" });
+        activeFixPointStack.current.value.value = fixPoints.value[len - 1];
         if (hide) {
-          hide()
-          hide = null
+          hide();
+          hide = null;
         }
       },
       true
-    )
+    );
     return () => {
-      onDestroy()
-      hide && hide()
-    }
+      onDestroy();
+      hide && hide();
+    };
   },
   [menuEnum.MEASURE_ROW]: (menu, onComplete) => {
     return trackMeasureMenuAction(
-      'L_LINE',
+      "L_LINE",
       menu,
       (data) => list.value.push(data),
       onComplete,
       "测量线"
-    )
+    );
   },
   [menuEnum.MEASURE_COLUMN]: (menu, onComplete) => {
     return trackMeasureMenuAction(
-      'V_LINE',
+      "V_LINE",
       menu,
       (data) => list.value.push(data),
       onComplete,
       "测量线"
-    )
+    );
   },
   [menuEnum.MEASURE_FREE]: (menu, onComplete) => {
     return trackMeasureMenuAction(
-      'LINE',
+      "LINE",
       menu,
       (data) => list.value.push(data),
       onComplete,
       "测量线"
-    )
+    );
   },
   [menuEnum.BASE_LINE]: (menu, onComplete) => {
     return trackMeasureMenuAction(
-      'BASE_LINE',
+      "BASE_LINE",
       menu,
       (data) => baseLines.value.push(data),
       onComplete,
       "基准线",
       "#ED1C24"
-    )
+    );
   },
-  [menuEnum.CLEAR]: (menu, onComplete) => {
-    list.value = []
-    baseLines.value = []
-    basePoints.value = []
-    fixPoints.value = []
-    onComplete()
+  [menuEnum.CLEAR]: async (menu, onComplete) => {
+    if (await useConfirm("确认情况?该操作无法撤销")) {
+      list.value = [];
+      baseLines.value = [];
+      basePoints.value = [];
+      fixPoints.value = [];
+    }
+    onComplete();
   },
-}
+};
 
 export const joinActions = (activeKey: Ref<string>) => {
   return watch(
     () => activeKey.value,
     (key, oldKey, onCleanup) => {
       if (key && menuActions[key]) {
-        const menu = findMenuByKey(key as any)
+        const menu = findMenuByKey(key as any);
         const cleanup = menuActions[key](menu, () => {
           if (!menu.continued) {
             activeKey.value = null;
           }
         });
-        if (typeof cleanup === 'function') {
+        if (typeof cleanup === "function") {
           onCleanup(cleanup);
         }
       }
     }
-  )
-}
+  );
+};

+ 20 - 11
src/views/scene/mode.vue

@@ -8,10 +8,11 @@
 </template>
 
 <script lang="ts" setup>
-import GroupButton from '@/components/group-button/index.vue'
-import {Mode} from "@/sdk";
-import {computed, ref, watch, watchEffect} from "vue";
-import {customMap, disabledMap} from "@/hook/custom/index";
+import GroupButton from "@/components/group-button/index.vue";
+import { Mode } from "@/sdk";
+import { computed, ref, watch, watchEffect } from "vue";
+import { customMap, disabledMap } from "@/hook/custom/index";
+import { params } from "@/hook";
 
 const tabs = [
   {
@@ -26,16 +27,25 @@ const tabs = [
   },
 ];
 
-const activeKey = ref(tabs[0].mode);
+const key = params.m + "model";
+
+const activeKey = ref(Number(localStorage.getItem(key) || 0));
 const menus = computed(() =>
-  tabs.map(tab => ({
+  tabs.map((tab) => ({
     icon: tab.mode === activeKey.value ? tab.activeIcon : tab.icon,
     key: tab.mode,
-    onClick: () => activeKey.value = tab.mode
+    onClick: () => (activeKey.value = tab.mode),
   }))
-)
+);
 
-watch(activeKey, () => customMap.mode = activeKey.value)
+watch(
+  activeKey,
+  () => {
+    customMap.mode = activeKey.value;
+    localStorage.setItem(key, activeKey.value.toString());
+  },
+  { immediate: true }
+);
 </script>
 
 <style lang="scss" scoped>
@@ -55,5 +65,4 @@ watch(activeKey, () => customMap.mode = activeKey.value)
   }
 }
 </style>
-<script setup lang="ts">
-</script>
+<script setup lang="ts"></script>