cuiqian2004
2025-09-12 d87c256a957a6a5c3b40eaf9c52ec68f2fc22c97
pages/map/js/ctx.js
@@ -5,8 +5,25 @@
import {
   Base64
} from '../../../comm/base64.js';
import {
   hasSelfIntersection
} from '../../../comm/line.js';
import {
   hexToRGBA,
} from '../../../comm/utils.js';
import {
   throttle
} from 'lodash-es';
import {
   debounce
} from "lodash-es";
import {
   Result
} from "ant-design-vue";
// import okIcon from '../../../static/images/confirm.svg';
// import cancelIcon from '../../../static/images/remove.svg';
export default {
   data() {
      return {
@@ -30,7 +47,19 @@
         agvObj: null,
         initFlag: false,
         editMode: false,
         objEditing: null,
         onOjectMoving: debounce(this.objectMoving, 200),
         mapInfo: {
            proportion: 1,
            img_proportion: 1,
            max_x: 1,
            max_y: 1,
            min_x: 0,
            min_y: 0,
            img_x: 1,
            img_y: 1
         },
         pressObjTimer: 0
      }
   },
@@ -65,7 +94,7 @@
               transparentCorners: false,
               cornerStyle: 'circle',
               borderScaleFactor: 2,
               padding: 10,
               padding: 5,
            });
            this.canvasId = `canvas_${uuidv4()}`
@@ -82,8 +111,11 @@
               stopContextMenu: true, // 禁止长按菜单
               fireRightClick: true,
               fireMiddleClick: true,
               targetFindTolerance: 15, // 增大触摸容差
               isTouchSupported: true
               targetFindTolerance: 10, // 增大触摸容差
               isTouchSupported: true,
               enableRetinaScaling: true,
               renderOnAddRemove: false,
               imageSmoothingEnabled: true
            })
            this.canvas.clear()
            this.eleWidth = cantainerEl.clientWidth
@@ -99,7 +131,7 @@
               height: this.eleHeight,
               selectable: false,
               hasControls: false,
               fill: "#FFFFFF"
               fill: "#FFFFFF20"
            })
            this.canvas.add(this.workSpace)
            this.patchFabricForUniApp(this.canvas)
@@ -135,61 +167,17 @@
            return this;
         };
      },
      createDeleteControl(obj) {
         // 创建删除按钮的图片元素
         const _this = this
         const deleteIcon =
            "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Ebene_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='595.275px' height='595.275px' viewBox='200 215 230 470' xml:space='preserve'%3E%3Ccircle style='fill:%23F44336;' cx='299.76' cy='439.067' r='218.516'/%3E%3Cg%3E%3Crect x='267.162' y='307.978' transform='matrix(0.7071 -0.7071 0.7071 0.7071 -222.6202 340.6915)' style='fill:white;' width='65.545' height='262.18'/%3E%3Crect x='266.988' y='308.153' transform='matrix(0.7071 0.7071 -0.7071 0.7071 398.3889 -83.3116)' style='fill:white;' width='65.544' height='262.179'/%3E%3C/g%3E%3C/svg%3E";
         const deleteImg = document.createElement('img');
         deleteImg.src = deleteIcon;
         // 添加自定义删除控件到所有对象的原型上
         //   console.log("controls", JSON.stringify(fabric.Object.prototype))
         obj.controls.deleteControl = new fabric.Control({
            x: 0.5,
            y: 0,
            offsetY: 0,
            offsetX: 36,
            mouseDownHandler: _this.deleteObject, // 触摸事件处理器
            render: (ctx, left, top, styleOverride, fabricObject) => {
               if (fabricObject.lockRemove) {
                  return;
               }
               if (fabricObject.canvas?.skipTargetFind) {
                  return;
               }
               const size = this.cornerSize || 36;
               // drawImg(ctx, left, top, imgDelete, 24, 24, fabricObject.angle);
               ctx.save();
               ctx.translate(left, top);
               ctx.drawImage(deleteImg, -size / 2, -size / 2, size, size);
               ctx.restore();
            },
            cornerSize: 36,
            withConnection: false,
            actionName: 'delete', // 动作名称
            pointerStyle: 'pointer' // 指针样式
         });
      },
      deleteObject(eventData, transform, x, y) {
         console.log("")
         const target = transform.target;
         const canvas = target.canvas;
         canvas.remove(target);
         canvas.requestRenderAll();
      },
      editCancelObject(eventData, transform, x, y) {
         const target = transform.target;
         const canvas = target.canvas;
         this.$ownerInstance.callMethod('receiveRenderData', {
            method: "edit_finish",
            cmd: "cancel",
            type: target.eleType,
            data: target.data,
            station: target.data,
         });
         target.set({
            lockEdit: true
         })
         this.canvas.requestRenderAll()
      },
      editOkObject(eventData, transform, x, y) {
@@ -198,83 +186,176 @@
         this.$ownerInstance.callMethod('receiveRenderData', {
            method: "edit_finish",
            cmd: "ok",
            type: target.eleType,
            data: target.data,
            station: target.data,
         });
         target.set({
            lockEdit: true
         })
         this.canvas.requestRenderAll()
      },
      closeOkCancelControl() {
         let list = this.canvas.getObjects() || []
         list = list.filter((a) => a.eleType == "cmd")
         list.forEach((obj) => {
            this.canvas.remove(obj)
         })
         this.objEditing = null
      },
      createOkCancelControl(obj) {
         // 创建删除按钮的图片元素
         const _this = this
         const cancelImg = document.createElement('img');
         cancelImg.src = "static/images/remove.svg"
         const okImg = document.createElement('img');
         okImg.src = "static/images/confirm.svg"
         // 添加自定义删除控件到所有对象的原型上
         //   console.log("controls", JSON.stringify(fabric.Object.prototype))
         obj.controls.cancelControl = new fabric.Control({
            x: -0.5,
            y: 0,
            offsetY: 0,
            offsetX: -32,
            mouseDownHandler: _this.editCancelObject, // 触摸事件处理器
            render: (ctx, left, top, styleOverride, fabricObject) => {
         var scale = this.canvas.getZoom()
         if (scale < 1) {
            scale = 1
         }
         var scale2 = 1
         var offset = 50
               if (fabricObject.lockEdit) {
                  return;
               }
               if (fabricObject.canvas?.skipTargetFind) {
                  return;
               }
               const size = this.cornerSize || 32;
               // drawImg(ctx, left, top, imgDelete, 24, 24, fabricObject.angle);
               ctx.save();
               ctx.translate(left, top);
               ctx.drawImage(cancelImg, -size / 2, -size / 2, size, size);
               ctx.restore();
            },
            cornerSize: 32,
            withConnection: false,
            actionName: 'delete', // 动作名称
            pointerStyle: 'pointer' // 指针样式
         });
         obj.controls.okControl = new fabric.Control({
            x: 0.5,
            y: 0,
            offsetY: 0,
            offsetX: 32,
            mouseDownHandler: _this.editOkObject, // 触摸事件处理器
            render: (ctx, left, top, styleOverride, fabricObject) => {
         //if (obj.eleType == "station" || obj.eleType == "edit_teaching") {
         scale2 = 1 / scale
               if (fabricObject.lockEdit) {
                  return;
               }
               if (fabricObject.canvas?.skipTargetFind) {
                  return;
               }
               const size = this.cornerSize || 32;
               // drawImg(ctx, left, top, imgDelete, 24, 24, fabricObject.angle);
               ctx.save();
               ctx.translate(left, top);
               ctx.drawImage(okImg, -size / 2, -size / 2, size, size);
               ctx.restore();
            },
            cornerSize: 32,
            withConnection: false,
            actionName: 'delete', // 动作名称
            pointerStyle: 'pointer' // 指针样式
         //}
         offset = 50 / scale
         this.objEditing = obj
         let list = this.canvas.getObjects() || []
         list = list.filter((a) => a.eleType == "cmd")
         if (list.length > 0) {
            let left = obj.left - offset
            let top = obj.top + obj.height / 2
            if (obj.originX == "center") {
               left = obj.left - obj.width / 2 - offset
            }
            if (obj.originX == "center") {
               top = obj.top
            }
            let curIndex = list.findIndex((a) => a.id == "cancel")
            if (curIndex > -1) {
               list[curIndex].set({
                  left,
                  top,
                  mainObj: obj,
                  scaleX: scale2,
                  scaleY: scale2
               })
               list[curIndex].setCoords()
               this.canvas.bringObjectToFront(list[curIndex]);
            }
            left = obj.left + obj.width + offset
            if (obj.originX == "center") {
               left = obj.left + obj.width / 2 + offset
            }
            curIndex = list.findIndex((a) => a.id == "ok")
            if (curIndex > -1) {
               list[curIndex].set({
                  left,
                  top,
                  mainObj: obj,
                  scaleX: scale2,
                  scaleY: scale2
               })
               list[curIndex].setCoords()
               this.canvas.bringObjectToFront(list[curIndex]);
            }
            return
         }
         let objList = []
         let ellipse = new fabric.Ellipse({
            left: -15,
            top: -15,
            rx: 15,
            ry: 15,
            stroke: "#F5222D",
            strokeWidth: 1,
            fill: "#F5222D",
         });
         objList.push(ellipse)
         let line = new fabric.Line([-6, -6, 6, 6], {
            stroke: "white",
            strokeWidth: 3,
         });
         objList.push(line)
         line = new fabric.Line([6, -6, -6, 6], {
            stroke: "white",
            strokeWidth: 3,
         });
         objList.push(line)
         let left = obj.left - offset
         let top = obj.top + obj.height / 2
         if (obj.originX == "center") {
            left = obj.left - obj.width / 2 - offset
         }
         if (obj.originY == "center") {
            top = obj.top
         }
         let objGroup = new fabric.Group(objList, {
            id: `cancel`,
            eleType: "cmd",
            left,
            top,
            width: 30,
            height: 30,
            originX: "center",
            originY: "center",
            lockRotation: true,
            lockScalingX: true,
            lockScalingY: true,
            lockMovementX: true,
            lockMovementY: true,
            mainObj: obj,
            scaleX: scale2,
            scaleY: scale2
         });
         this.canvas.add(objGroup)
         objList = []
         ellipse = new fabric.Ellipse({
            left: -15,
            top: -15,
            rx: 15,
            ry: 15,
            stroke: "#52C41A",
            strokeWidth: 1,
            fill: "#52C41A",
         });
         objList.push(ellipse)
         line = new fabric.Line([-8, -2, -2, 5], {
            stroke: "white",
            strokeWidth: 3,
         });
         objList.push(line)
         line = new fabric.Line([-3, 6, 9, -5], {
            stroke: "white",
            strokeWidth: 3,
         });
         objList.push(line)
         left = obj.left + obj.width + offset
         if (obj.originX == "center") {
            left = obj.left + obj.width / 2 + offset
         }
         objGroup = new fabric.Group(objList, {
            id: `ok`,
            eleType: "cmd",
            left,
            top,
            width: 30,
            height: 30,
            originX: "center",
            originY: "center",
            lockRotation: true,
            lockScalingX: true,
            lockScalingY: true,
            lockMovementX: true,
            lockMovementY: true,
            scaleX: scale2,
            scaleY: scale2,
            mainObj: obj
         });
         this.canvas.add(objGroup)
      },
      addObject(obj) {
         obj.set({
            lockEdit: true
         })
         this.canvas.add(obj)
         this.createOkCancelControl(obj)
      },
      canvasEventListener() {
         const _this = this
@@ -299,10 +380,11 @@
         _this.canvas.on("selection:updated", function(e) {
            console.log("selection:updated", e);
            const activeObj = _this.canvas.getActiveObject();
            activeObj.set({
               borderColor: '#1890ff',
               borderScaleFactor: 3,
               padding: 5,
               padding: 3,
               borderDashArray: [5, 5], //5px 实线和5px 间隔
            });
            _this.canvas.requestRenderAll();
@@ -311,7 +393,7 @@
         });
         _this.canvas.on("selection:cleared", function(e) {
            console.log("selection:cleared", e);
            //   console.log("selection:cleared", e);
            //_this.selectionChangeCanvas();
         });
@@ -321,88 +403,191 @@
               e.target.isRemoved = true;
            }
         });
         _this.canvas.on("transform", function(e) {
            _this.onScaleChange()
         });
         _this.canvas.on("object:modified", function(e) {
            // console.log("object:modified", e.target);
            if (e?.target.eleType == "station") {
               const obj = e.target
               obj.data.x = obj.left
               obj.data.y = obj.top
               _this.$ownerInstance.callMethod('receiveRenderData', {
                  method: "update_station",
                  station: obj.data,
               });
            }
            // _this.resizetCanvas();
         });
         _this.canvas.on("object:moving", function(e) {
            // console.log("object:modified", e.target);
            console.log("object:moving", e.target);
            _this.onOjectMoving(e.target)
         });
         var pressObjTimer
         cantainerEl.addEventListener('touchstart', function(e) {
            console.log('touchstart:', e);
            //      console.log('touchstart:', e.touches.length);
            e.preventDefault(); // 阻止默认行为
            _this.canvas.fire('touch:start', {
               e: e
            });
            //   _this.canvas._onMouseDown(e);
            _this.pointerSelectObject(e)
            if (!_this.canvas.getActiveObject()) {
               // 根据触摸点数量判断交互类型
               if (e.touches.length === 1) {
                  _this.handleSingleTouch(e.touches[0]);
               } else if (e.touches.length >= 2) {
                  _this.handleMultiTouch(e.touches);
               }
            } else {
               if (e.touches.length === 1) {
                  const touch = e.touches[0]
                  this.lastPosX = touch.clientX;
                  this.lastPosY = touch.clientY;
                  _this.lastPosX = touch.clientX;
                  _this.lastPosY = touch.clientY;
                  const list = _this.canvas.getActiveObjects()
                  if (list.length === 1) {
                     pressObjTimer = setTimeout(function() {
                        console.log("edit_station", list[0].eleType)
                        if (list[0].eleType == "station") {
                           _this.setAllObjectSelectable(false)
                     if (!_this.objEditing) {
                        this.pressObjTimer = setTimeout(function() {
                           list[0].set({
                              selectable: true
                           })
                           const zoom = _this.canvas.getZoom();
                           let deltaX = list[0].left * zoom
                           let deltaY = list[0].top * zoom
                           const vpt = _this.canvas.viewportTransform;
                           if (list[0].eleType == "station") {
                              _this.setAllObjectSelectable(false)
                           _this.$ownerInstance.callMethod('receiveRenderData', {
                              method: "edit_station",
                              station: list[0].data,
                              view: {
                                 x: vpt[4] + deltaX,
                                 y: vpt[5] + deltaY,
                                 // x: e.touches[0].clientX,
                                 // y: e.touches[0].clientY,
                                 width: list[0].width * zoom,
                                 height: list[0].height * zoom
                              }
                           });
                              list[0].set({
                                 selectable: true
                              })
                              let deltaX = list[0].left * zoom
                              let deltaY = list[0].top * zoom
                              const scale = zoom < 1 ? zoom : 1
                              _this.$ownerInstance.callMethod('receiveRenderData', {
                                 method: "edit_station",
                                 station: list[0].data,
                                 view: {
                                    x: vpt[4] + deltaX,
                                    y: vpt[5] + deltaY,
                                    // x: e.touches[0].clientX,
                                    // y: e.touches[0].clientY,
                                    width: list[0].width * scale,
                                    height: list[0].height * scale
                                 }
                              });
                           } else if (list[0].eleType == "public_teaching") {
                              const pt = _this.canvas.getPointer(touch); // ← 关键
                              // 2. pointer 就是画布坐标
                              _this.$ownerInstance.callMethod('receiveRenderData', {
                                 method: "select_teaching_path",
                                 data: list[0].data,
                                 type: "public",
                                 point: pt
                              });
                           } else if (list[0].eleType == "station_teaching") {
                              _this.$ownerInstance.callMethod('receiveRenderData', {
                                 method: "select_teaching_path",
                                 data: list[0].data,
                                 type: "station",
                                 point: pt
                              });
                           }
                        }, 1000); //
                     }
                     let activeObj = list[0]
                     if (activeObj.eleType == "region_pt_add") {
                        const data = activeObj.mainObj?.data
                        data.path.push({
                           x: _this.getActualXFromImg(activeObj.left),
                           y: _this.getActualYFromImg(activeObj.top)
                        })
                        _this.updateRegion(activeObj.mainObj, data)
                     } else if (activeObj.eleType == "cmd") {
                        let data = activeObj.mainObj.data
                        if (activeObj.mainObj.eleType == "edit_teaching") {
                           let left = _this.getActualXFromImg(activeObj.left)
                           let top = _this.getActualYFromImg(activeObj.top)
                           let right = _this.getActualXFromImg(activeObj.left + activeObj.width)
                           let bottom = _this.getActualYFromImg(activeObj.top + activeObj.height)
                           data = [
                              [left, top],
                              [left, bottom],
                              [right, bottom],
                              [right, top]
                           ]
                           console.log(data)
                        }
                     }, 3000); //
                        _this.$ownerInstance.callMethod('receiveRenderData', {
                           method: "edit_finish",
                           cmd: activeObj.id,
                           type: activeObj.mainObj.eleType,
                           data: data,
                        });
                        if (activeObj.id == "ok") {
                           if (activeObj.mainObj.eleType == "region") {
                              _this.addRegionFinish(activeObj.mainObj)
                           } else if (activeObj.mainObj.eleType == "virtual_wall") {
                              _this.addVirtualWallFinish(activeObj.mainObj)
                           }
                        }
                        if (activeObj.mainObj.eleType == "edit_teaching") {
                           let list = _this.canvas.getObjects() || []
                           for (let i2 in list) {
                              const obj = list[i2]
                              obj.set({
                                 opacity: 1
                              })
                           }
                           if (activeObj.mainObj?.mainObj) {
                              activeObj.mainObj.mainObj.set({
                                 selectable: true
                              })
                           }
                           const ptObjs = activeObj.mainObj.ptObjs || []
                           _this.canvas.remove(activeObj.mainObj)
                           for (let i2 in ptObjs) {
                              const obj = ptObjs[i2]
                              _this.canvas.remove(obj)
                           }
                        }
                        if (activeObj.mainObj.eleType == "station") {
                           _this.setAllObjectSelectable(true)
                           activeObj.mainObj.tipObj.set({
                              left: activeObj.mainObj.left,
                              top: activeObj.mainObj.top - activeObj.mainObj.height / 2 -
                                 activeObj.mainObj
                                 .tipObj.height / 2,
                              visible: true
                           })
                           activeObj.mainObj.tipObj.setCoords()
                        }
                        _this.closeOkCancelControl()
                        _this.canvas.requestRenderAll()
                     }
                     // else if (activeObj.eleType == "station") {
                     //    _this.$ownerInstance.callMethod('receiveRenderData', {
                     //       method: "select_station",
                     //       data: activeObj.data,
                     //       select: activeObj.mark ? false : true
                     //    });
                     // }
                  }
               } else if (e.touches.length >= 2) {
                  _this.handleMultiTouch(e.touches);
               }
            }
         });
         cantainerEl.addEventListener('touchmove', function(e) {
            //   _this.canvas._onMouseMove(e);
            //   console.log('touchmove:', e.touches.length);
            e.preventDefault(); // 阻止默认行为
            // 处理移动
            if (!_this.canvas.getActiveObject()) {
            const list = _this.canvas.getActiveObjects()
            if (list.length == 0) {
               if (e.touches.length === 1) {
                  _this.handleSingleTouchMove(e.touches[0]);
               } else if (e.touches.length >= 2) {
@@ -410,83 +595,526 @@
               }
            } else {
               if (e.touches.length === 1) {
                  const touch = e.touches[0]
                  const deltaX = touch.clientX - this.lastPosX;
                  const deltaY = touch.clientY - this.lastPosY;
                  if (Math.abs(deltaX) > 20)
                     clearTimeout(pressObjTimer);
                  if (list.length > 1 || list[0].lockMovementX) {
                     if (list[0].lockMovementX) {
                        _this.canvas.discardActiveObject();
                     }
                     _this.handleSingleTouchMove(e.touches[0]);
                  } else {
                     const touch = e.touches[0]
                     const deltaX = touch.clientX - this.lastPosX;
                     const deltaY = touch.clientY - this.lastPosY;
                     if (Math.abs(deltaX) > 20)
                        clearTimeout(this.pressObjTimer);
                  }
               } else if (e.touches.length >= 2) {
                  _this.canvas.discardActiveObject();
                  _this.handleMultiTouchMove(e.touches);
               }
            }
         });
         cantainerEl.addEventListener('touchend', function(e) {
            //   _this.canvas._onMouseUp(e);
            //   console.log('touchend:');
            e.preventDefault(); // 阻止默认行为
            _this.touchPoint = {
               x: 0,
               y: 0
            };
            if (!_this.canvas.getActiveObject()) {
            const activeObj = _this.canvas.getActiveObject()
            if (!activeObj) {
               // 处理结束事件
               if (e.touches.length === 0) {
                  _this.handleTouchEnd();
               }
               if (_this.editObject) {
                  _this.canvas.setActiveObject(_this.editObject)
               }
               // if (_this.editObject) {
               //    _this.canvas.setActiveObject(_this.editObject)
               // }
            } else {
               clearTimeout(pressObjTimer);
               // if (activeObj.lockMovementX) {
               //    _this.canvas.discardActiveObject();
               // }
            }
            if (this.pressObjTimer) {
               clearTimeout(this.pressObjTimer);
               this.pressObjTimer = null
            }
         });
         cantainerEl.addEventListener('touchcancel', function(e) {
            if (_this.canvas.getActiveObject()) {
               // 处理结束事件
               clearTimeout(pressObjTimer);
            } else {
               if (_this.editObject) {
                  _this.canvas.setActiveObject(_this.editObject)
               }
            //   console.log('touchcancel:');
            if (this.pressObjTimer) {
               clearTimeout(this.pressObjTimer);
               this.pressObjTimer = null
            }
            // const activeObj = _this.canvas.getActiveObject()
            // if (activeObj) {
            //    if (activeObj.lockMovementX) {
            //       _this.canvas.discardActiveObject();
            //    }
            // }
         })
      },
      onSelectionChanage() {
         const _this = this
         const list = _this.canvas.getActiveObjects()
         if (list.length === 1) {
            if (list[0].eleType == "station") {
               _this.$ownerInstance.callMethod('receiveRenderData', {
                  method: "selected_change",
                  type: list[0].eleType,
                  param: list[0].data
               });
            } else if (list[0].eleType == "agv") {
               _this.$ownerInstance.callMethod('receiveRenderData', {
                  method: "selected_change",
                  type: list[0].eleType,
                  param: list[0].data
               });
            } else if (list[0].eleType == "agv_line") {
               _this.$ownerInstance.callMethod('receiveRenderData', {
                  method: "selected_change",
                  type: list[0].eleType,
                  param: list[0].data
               });
            } else {
               _this.$ownerInstance.callMethod('receiveRenderData', {
                  method: "selected_change",
                  type: ""
               });
      // 计算点到线段的距离
      pointToSegmentDistance(px, py, x1, y1, x2, y2) {
         const A = px - x1;
         const B = py - y1;
         const C = x2 - x1;
         const D = y2 - y1;
         const dot = A * C + B * D;
         const lenSq = C * C + D * D;
         let param = -1;
         if (lenSq !== 0) param = dot / lenSq;
         let xx, yy;
         if (param < 0) {
            xx = x1;
            yy = y1;
         } else if (param > 1) {
            xx = x2;
            yy = y2;
         } else {
            xx = x1 + param * C;
            yy = y1 + param * D;
         }
         const dx = px - xx;
         const dy = py - yy;
         return Math.sqrt(dx * dx + dy * dy);
      },
      // 获取 Path 的所有线段
      getPathSegments(path) {
         const segs = [];
         const pts = path.path;
         let lastX = 0,
            lastY = 0;
         for (let i = 0; i < pts.length; i++) {
            const cmd = pts[i];
            if (cmd[0] === 'M') {
               lastX = cmd[1];
               lastY = cmd[2];
            } else if (cmd[0] === 'L') {
               const x = cmd[1];
               const y = cmd[2];
               segs.push([lastX, lastY, x, y]);
               lastX = x;
               lastY = y;
            }
         }
         return segs;
      },
      // 判断点是否在 Path 或 Line 的边框上
      isPointOnStroke(obj, pointer, tolerance = 5) {
         if (obj instanceof fabric.Line) {
            const dist = this.pointToSegmentDistance(
               pointer.x, pointer.y,
               obj.x1, obj.y1, obj.x2, obj.y2
            );
            return dist <= tolerance;
         }
         if (obj instanceof fabric.Path) {
            const segs = this.getPathSegments(obj);
            for (const [x1, y1, x2, y2] of segs) {
               const dist = this.pointToSegmentDistance(pointer.x, pointer.y, x1, y1, x2, y2);
               if (dist <= tolerance) return true;
            }
         }
         return false;
      },
      pointerSelectObject(e) {
         const pointer = this.canvas.getPointer(e);
         const objects = this.canvas.getObjects();
         objects.splice(0, 1);
         this.canvas.discardActiveObject()
         let pointerList = []
         let pointerList2 = []
         for (let i = objects.length - 1; i >= 0; i--) {
            const obj = objects[i];
            if (obj.selectable && obj.opacity > 0) {
               if (obj instanceof fabric.Path || obj instanceof fabric.Line) {
                  if (this.isPointOnStroke(obj, pointer)) {
                     console.log(i, obj.eleType)
                     pointerList.unshift(obj)
                  }
               } else {
                  const isHit = obj.containsPoint(pointer);
                  if (isHit) {
                     console.log(i, obj.eleType)
                     pointerList.unshift(obj)
                  }
               }
            }
         } else {
            _this.$ownerInstance.callMethod('receiveRenderData', {
               method: "selected_change",
               type: ""
            });
         }
         for (let i = pointerList.length - 1; i >= 0; i--) {
            const obj = pointerList[i];
            if (obj instanceof fabric.Path || obj instanceof fabric.Line) {
               if (this.isPointOnStroke(obj, pointer, 1)) {
                  this.canvas.discardActiveObject()
                  this.canvas.setActiveObject(obj);
                  this.canvas.requestRenderAll();
                  return
               }
               pointerList2.unshift(obj)
            } else {
               this.canvas.discardActiveObject()
               this.canvas.setActiveObject(obj);
               this.canvas.requestRenderAll();
               return
            }
         }
         if (pointerList2.length > 0) {
            if (pointerList2.length == 1) {
               const obj = pointerList2[i];
               this.canvas.discardActiveObject()
               this.canvas.setActiveObject(obj);
               this.canvas.requestRenderAll();
               return
            }
            pointerList = pointerList2
            pointerList2 = []
            for (let i = pointerList.length - 1; i >= 0; i--) {
               const obj = pointerList[i];
               if (this.isPointOnStroke(obj, pointer, 2)) {
                  this.canvas.discardActiveObject()
                  this.canvas.setActiveObject(obj);
                  this.canvas.requestRenderAll();
                  return
               }
               pointerList2.unshift(obj)
            }
         }
         if (pointerList2.length > 0) {
            if (pointerList2.length == 1) {
               const obj = pointerList2[i];
               this.canvas.discardActiveObject()
               this.canvas.setActiveObject(obj);
               this.canvas.requestRenderAll();
               return
            }
            pointerList = pointerList2
            pointerList2 = []
            for (let i = pointerList.length - 1; i >= 0; i--) {
               const obj = pointerList[i];
               if (this.isPointOnStroke(obj, pointer, 3)) {
                  this.canvas.discardActiveObject()
                  this.canvas.setActiveObject(obj);
                  this.canvas.requestRenderAll();
                  return
               }
               pointerList2.unshift(obj)
            }
         }
         if (pointerList2.length > 0) {
            if (pointerList2.length == 1) {
               const obj = pointerList2[i];
               this.canvas.discardActiveObject()
               this.canvas.setActiveObject(obj);
               this.canvas.requestRenderAll();
               return
            }
            pointerList = pointerList2
            pointerList2 = []
            for (let i = pointerList.length - 1; i >= 0; i--) {
               const obj = pointerList[i];
               if (this.isPointOnStroke(obj, pointer, 4)) {
                  this.canvas.discardActiveObject()
                  this.canvas.setActiveObject(obj);
                  this.canvas.requestRenderAll();
                  return
               }
               pointerList2.unshift(obj)
            }
         }
         if (pointerList2.length > 0) {
            const obj = pointerList2[pointerList2.length - 1];
            this.canvas.discardActiveObject()
            this.canvas.setActiveObject(obj);
            this.canvas.requestRenderAll();
            return
         }
         // objects.forEach(obj => {
         //    if (obj instanceof fabric.Path || obj instanceof fabric.Line) {
         //       // 设置一个“点击容忍度”,比如 5 像素
         //       const tolerance = 5;
         //       // 临时扩大路径的点击区域
         //       const originalStrokeWidth = obj.strokeWidth;
         //       obj.strokeWidth = originalStrokeWidth + tolerance * 2;
         //       const isHit = obj.intersectsWithPointer(pointer);
         //       // 恢复原始宽度
         //       obj.strokeWidth = originalStrokeWidth;
         //       if (isHit) {
         //          pointerList.push(obj)
         //       }
         //    } else if (obj instanceof fabric.Rect || obj instanceof fabric.Ellipse) {
         //       const isHit = obj.containsPoint(pointer);
         //       if (isHit) {
         //          pointerList.push(obj)
         //       }
         //    }
         //    // else if (obj instanceof fabric.Group )
         //    // {
         //    //    const objects2 = obj.getObjects();
         //    //    const isHit = obj.containsPoint(pointer);
         //    //    if (isHit) {
         //    //       pointerList.push(obj)
         //    //    }
         //    // }
         // });
         // if (pointerList.length === 0) {
         //    return
         // }
         // const obj = pointerList.pop()
         // this.canvas.discardActiveObject()
         // this.canvas.setActiveObject(obj)
      },
      objectMoving(target) {
         const _this = this
         if (!target)
            return
         if (target?.eleType == "station") {
            const obj = target
            obj.data.x = _this.getActualXFromImg(obj.left)
            obj.data.y = _this.getActualYFromImg(obj.top)
            const vpt = _this.canvas.viewportTransform;
            const zoom = this.canvas.getZoom();
            let deltaX = obj.left * zoom
            let deltaY = obj.top * zoom
            _this.$ownerInstance.callMethod('receiveRenderData', {
               method: "update_station",
               station: obj.data,
               view: {
                  x: vpt[4] + deltaX,
                  y: vpt[5] + deltaY,
               }
            });
            _this.createOkCancelControl(obj)
            _this.canvas.renderAll()
         } else if (target?.eleType == "edit_teaching") {
            _this.updateEditTeachingPath(target)
         } else if (target.eleType == "virtual_wall" || target.eleType == "region") {
            const data = target.data
            const offX = target.left - target.oldLeft
            const offY = target.top - target.oldTop
            console.log("path", offX, offY, data.path)
            data.path.forEach((pt) => {
               pt.x += _this.getActualSizeFromImg(offX)
               pt.y -= _this.getActualSizeFromImg(offY)
            })
            console.log("path2", data.path)
            if (target.eleType == "virtual_wall")
               _this.updateVirtualWall(target, data)
            else
               _this.updateRegion(target, data)
            _this.canvas.renderAll()
         } else if (target?.eleType == "wall_pt") {
            const data = target.mainObj?.data
            if (!data)
               return
            let pt = data.path[0]
            let id = `${data.id}_${ pt.x}_${pt.y}`
            console.log("wall_pt", target.id, id, pt, data.path)
            if (target.id == id) {
               data.path[0] = {
                  x: _this.getActualXFromImg(target.left),
                  y: _this.getActualYFromImg(target.top)
               }
            } else {
               pt = data.path[1]
               id = `${data.id}_${ pt.x}_${pt.y}`
               console.log("wall_pt", id, pt)
               if (target.id == id) {
                  data.path[1] = {
                     x: _this.getActualXFromImg(target.left),
                     y: _this.getActualYFromImg(target.top)
                  }
               }
            }
            _this.updateVirtualWall(target.mainObj, data)
            _this.canvas.renderAll()
         } else if (target?.eleType == "region_pt") {
            const data = target.mainObj?.data
            if (!data)
               return
            const curIndex = data.path.findIndex((pt) => `${data.id}_${ pt.x}_${pt.y}` == target.id)
            if (curIndex > -1) {
               let polygon = []
               data.path.forEach((pt, index) => {
                  if (curIndex == index) {
                     polygon.push([_this.getActualXFromImg(target.left), _this.getActualYFromImg(target
                        .top)])
                  } else {
                     polygon.push([pt.x, pt.y])
                  }
               })
               polygon.push(polygon[0])
               if (hasSelfIntersection(polygon)) {
                  _this.showToast("进行区和可行区必须是闭合图形")
                  target.set({
                     left: _this.getXOnImg(data.path[curIndex].x),
                     top: _this.getYOnImg(data.path[curIndex].y)
                  })
                  target.setCoords()
               } else {
                  data.path[curIndex] = {
                     x: _this.getActualXFromImg(target.left),
                     y: _this.getActualYFromImg(target.top)
                  }
                  _this.updateRegion(target.mainObj, data)
               }
               _this.canvas.renderAll()
            }
         } else if (target?.eleType == "edit_teaching_pt") {
            let left = target.mainObj.left
            let top = target.mainObj.top
            let width = target.mainObj.width
            let height = target.mainObj.height
            if (target.id == `edit_teaching_pt_0`) {
               if (target.left > target.mainObj.left + target.mainObj.width - 10) {
                  target.set({
                     left: target.mainObj.left + target.mainObj.width - 10
                  })
               }
               width += left - target.left
               left = target.left
               if (target.top > target.mainObj.top + target.mainObj.height - 10) {
                  target.set({
                     top: target.mainObj.top + target.mainObj.height - 10
                  })
               }
               height += top - target.top
               top = target.top
            } else if (target.id == `edit_teaching_pt_1`) {
               if (target.left > target.mainObj.left + target.mainObj.width - 10) {
                  target.set({
                     left: target.mainObj.left + target.mainObj.width - 10
                  })
               }
               width += left - target.left
               left = target.left
               if (target.top < target.mainObj.top + 10) {
                  target.set({
                     top: target.mainObj.top + 10
                  })
               }
               height = target.top - top
            } else if (target.id == `edit_teaching_pt_2`) {
               if (target.left < target.mainObj.left + 10) {
                  target.set({
                     left: target.mainObj.left + 10
                  })
               }
               width = target.left - left
               if (target.top < target.mainObj.top + 10) {
                  target.set({
                     top: target.mainObj.top + 10
                  })
               }
               height = target.top - top
            } else if (target.id == `edit_teaching_pt_3`) {
               if (target.left < target.mainObj.left + 10) {
                  target.set({
                     left: target.mainObj.left + 10
                  })
               }
               width = target.left - left
               if (target.top > target.mainObj.top + target.mainObj.height - 10) {
                  target.set({
                     top: target.mainObj.top + target.mainObj.height - 10
                  })
               }
               height += top - target.top
               top = target.top
            }
            target.mainObj.set({
               left,
               top,
               height,
               width
            })
            target.mainObj.setCoords()
            this.updateEditTeachingPath(target.mainObj)
         }
      },
      onSelectionChanage() {
         const _this = this
         // const list = _this.canvas.getActiveObjects()
         // if (list.length === 1) {
         //    if (list[0].eleType == "station") {
         //       _this.$ownerInstance.callMethod('receiveRenderData', {
         //          method: "selected_change",
         //          type: list[0].eleType,
         //          param: list[0].data
         //       });
         //    } else if (list[0].eleType == "agv") {
         //       _this.$ownerInstance.callMethod('receiveRenderData', {
         //          method: "selected_change",
         //          type: list[0].eleType,
         //          param: list[0].data
         //       });
         //    } else if (list[0].eleType == "agv_line") {
         //       _this.$ownerInstance.callMethod('receiveRenderData', {
         //          method: "selected_change",
         //          type: list[0].eleType,
         //          param: list[0].data
         //       });
         //    } else {
         //       _this.$ownerInstance.callMethod('receiveRenderData', {
         //          method: "selected_change",
         //          type: ""
         //       });
         //    }
         // } else {
         //    _this.$ownerInstance.callMethod('receiveRenderData', {
         //       method: "selected_change",
         //       type: ""
         //    });
         // }
      },
      safeLoadImage(url, maxSize = 2048) {
         console.log(url)
@@ -553,6 +1181,18 @@
         });
      },
      // 将 Base64 转为 Blob,再生成 URL
      base64ToBlob(base64, mime) {
         const byteString = atob(base64.split(',')[1]);
         const ab = new ArrayBuffer(byteString.length);
         const ia = new Uint8Array(ab);
         for (let i = 0; i < byteString.length; i++) {
            ia[i] = byteString.charCodeAt(i);
         }
         return new Blob([ab], {
            type: mime
         });
      },
      async loadBase64ImageWithProgress(data, maxSize = 2048) {
         const _this = this
         return new Promise((resolve, reject) => {
@@ -561,6 +1201,10 @@
            if (base64Image.indexOf("data:image/png;base64,") < 0) {
               base64Image = "data:image/png;base64," + data
            }
            const blob = this.base64ToBlob(base64Image, 'image/png');
            const url = URL.createObjectURL(blob);
            _this.$ownerInstance.callMethod('receiveRenderData', {
               method: "set_backgroud_progress",
               type: "start",
@@ -592,6 +1236,8 @@
               }
            };
            img.onload = () => {
               // 用完释放内存
               URL.revokeObjectURL(url);
               const percent = Math.min(this.progressPercent + 10, 95);
               _this.$ownerInstance.callMethod('receiveRenderData', {
                  method: "set_backgroud_progress",
@@ -625,7 +1271,8 @@
               reject(new Error('图片加载失败'));
            };
            // 开始加载
            img.src = base64Image;
            //img.src = base64Image;
            img.src = url;
         });
      },
@@ -638,16 +1285,30 @@
         }
         return null;
      },
      clearObjects() {
         const list = this.canvas.getObjects()
         list.splice(0, 1)
         for (let i in list) {
            const obj = list[i]
            this.canvas.remove(obj)
         }
      },
      setBackground(info) {
         const _this = this
         if (!this.canvas)
         if (!this.canvas) {
            _this.$ownerInstance.callMethod('receiveRenderData', {
               method: "set_backgroud_progress",
               type: "error",
            });
            return
         this.canvas.clear()
         this.canvas.selectionColor = 'rgba(100, 200, 255, 0.3)'; // 选中背景色
         this.canvas.selectionBorderColor = '#1890ff'; // 边框颜色
         this.canvas.selectionLineWidth = 3; // 边框宽度
         }
         // this.canvas.clear()
         // this.canvas.selectionColor = 'rgba(100, 200, 255, 0.3)'; // 选中背景色
         // this.canvas.selectionBorderColor = '#1890ff'; // 边框颜色
         // this.canvas.selectionLineWidth = 3; // 边框宽度
         this.agvObj = null
         const cantainerEl = document.getElementById("canvasMap")
         this.eleWidth = cantainerEl.clientWidth
@@ -655,92 +1316,115 @@
         console.log("client", this.eleWidth, this.eleHeight)
         this.canvas.setWidth(this.eleWidth);
         this.canvas.setHeight(this.eleHeight);
         //console.log("setBackground", JSON.stringify(info))
         this.mapInfo = {
            proportion: info.proportion || 1,
            img_proportion: info.img_proportion || 1,
            max_x: info.max_x || 1,
            max_y: info.max_y || 1,
            min_x: info.min_x || 0,
            min_y: info.min_y || 0,
            img_x: info.img_x || 1,
            img_y: info.img_y || 1,
         }
         return new Promise((resolve, reject) => {
            // if (svgUrl) {
            //    fabric.loadSVGFromURL(svgUrl).then(({
            //       objects,
            //       options
            //    }) => {
            //       const workSpace = fabric.util.groupSVGElements(objects, options);
            //       workSpace.set({
            //          id: "workspace",
            //          eleType: "workspace",
            //          left: 0,
            //          top: 0,
            //          selectable: false,
            //          hasControls: false,
            //       });
            //       _this.canvas.add(workSpace)
            //       if (_this.workSpace) {
            //          _this.canvas.remove(_this.workSpace)
            //       }
            //       _this.workSpace = workSpace
            //       //   _this.auto()
            //       resolve()
            //    });
            // } else if (imgUrl) {
            if (info.filedata) {
               _this.loadBase64ImageWithProgress(info.filedata).then((img) => {
                  //console.warn('setBackground', JSON.stringify(img));
                  if (img) {
                     if (_this.mapInfo.img_x == 1) {
                        _this.mapInfo.img_x = img.width
                        _this.mapInfo.max_x = _this.mapInfo.img_proportion * _this.mapInfo
                           .img_x + _this.mapInfo.min_x
                     }
                     if (_this.mapInfo.img_y == 1) {
                        _this.mapInfo.img_y = img.height
                        _this.mapInfo.max_y = _this.mapInfo.img_proportion * _this.mapInfo
                           .img_y + _this.mapInfo.min_y
                     }
                     img.set({
                        id: "workspace",
                        eleType: "workspace",
                        left: 0,
                        top: 0,
                     });
                     // if (_this.workSpace instanceof fabric.Group) {
                     //    const objs = _this.workSpace.getObjects()
                     //    const rect = objs[1]
                     //    _this.workSpace.remove(objs[0])
                     //    _this.workSpace.insertAt(0,img)
                     //    rect.set({
                     //       width: _this.mapInfo.img_x,
                     //       height: _this.mapInfo.img_y,
                     //    })
                     //    _this.workSpace.set({
                     //       width: _this.mapInfo.img_x,
                     //       height: _this.mapInfo.img_y,
                     //    })
                     //    resolve()
                     //    return
                     // }
                     _this.clearObjects()
                     const rect = new fabric.Rect({
                        left: 0,
                        top: 0,
                        width: _this.mapInfo.img_x,
                        height: _this.mapInfo.img_y,
                        stroke: "#333",
                        strokeWidth: 1,
                        strokeDashArray: [5, 5],
                        strokeLineCap: 'butt',
                        fill: "rgba(255,255,255,0)",
                     })
                     console.log(rect.width, _this.mapInfo.img_x, img.width)
                     let wsGroup = new fabric.Group([img, rect], {
                        id: "workspace",
                        eleType: "workspace",
                        selectable: false,
                        hasControls: false,
                        left: 0,
                        top: 0,
                        width: _this.mapInfo.img_x,
                        height: _this.mapInfo.img_y,
                     });
                     _this.canvas.add(img)
                     _this.canvas.add(wsGroup)
                     if (_this.workSpace) {
                        _this.canvas.remove(_this.workSpace)
                     }
                     _this.workSpace = img
                     _this.workSpace = wsGroup
                  }
                  //_this.checkMemoryUsage()
                  resolve()
               }).catch((err) => {
                  _this.$ownerInstance.callMethod('receiveRenderData', {
                     method: "set_backgroud_progress",
                     type: "error",
                  });
                  resolve()
               })
            } else {
               _this.$ownerInstance.callMethod('receiveRenderData', {
                  method: "set_backgroud_progress",
                  type: "end",
               });
               resolve()
            }
            // fabric.Image.fromURL(imgUrl).then((img) => {
            //    // 设置背景图像的属性
            //    img.set({
            //       id: "workspace",
            //       eleType: "workspace",
            //       left: 0,
            //       top: 0,
            //       selectable: false,
            //       hasControls: false,
            //    });
            //    _this.canvas.add(img)
            //    if (_this.workSpace) {
            //       _this.canvas.remove(_this.workSpace)
            //    }
            //    _this.workSpace = img
            //    //_this.auto()
            //    resolve()
            // })
            //}
         })
      },
      getAutoScale() {
         // 按照宽度
         const eleWidth = this.eleWidth
         const eleHeight = this.eleHeight
         const eleWidth = this.eleWidth - 20
         const eleHeight = this.eleHeight - 200
         if (!this.workSpace)
            return 1
         const width = this.workSpace.width
@@ -771,12 +1455,12 @@
            scale: scale
         });
         if (!this.workSpace) return;
         this.setCenterFromObject(this.workSpace);
         // this.setCenterFromObject(this.workSpace);
         // 超出画布不展示
         _this.workSpace.clone().then((cloned) => {
            _this.canvas.clipPath = cloned;
            _this.canvas.requestRenderAll();
         });
         // _this.workSpace.clone().then((cloned) => {
         //    _this.canvas.clipPath = cloned;
         //    _this.canvas.requestRenderAll();
         // });
      },
      setDrawingType(type, svg) {
         if (svg) {
@@ -819,7 +1503,7 @@
            x: touch.clientX,
            y: touch.clientY
         };
         console.log('单点触摸开始');
         //   console.log('单点触摸开始');
         let activeObj = this.canvas.getActiveObject();
         if (!activeObj) {
@@ -828,6 +1512,7 @@
               this.isDragging = true;
               this.lastPosX = touch.clientX;
               this.lastPosY = touch.clientY;
            } else {
               this.isDragging = false;
               this.isDrawing = true;
@@ -849,19 +1534,45 @@
         // 多点触摸初始逻辑
      },
      handleSingleTouchMove(touch) {
         console.log('单点移动', touch.clientX, touch.clientY);
         //console.log('单点移动', touch.clientX, touch.clientY,this.lastPosX,this.lastPosY);
         if (this.isDragging) {
            const deltaX = touch.clientX - this.lastPosX;
            const deltaY = touch.clientY - this.lastPosY;
            // 移动视口
            console.log('relativePan', deltaX, deltaY);
            if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
               if (this.pressObjTimer) {
                  clearTimeout(this.pressObjTimer);
                  this.pressObjTimer = null
               }
            }
            // 移动视口
            //   console.log('relativePan', deltaX, deltaY);
            const vpt = this.canvas.viewportTransform;
            this.canvas.relativePan(new fabric.Point(deltaX, deltaY));
            if (this.objEditing) {
               if (this.objEditing.eleType == "station") {
                  const zoom = this.canvas.getZoom();
                  let deltaX2 = this.objEditing.left * zoom
                  let deltaY2 = this.objEditing.top * zoom
                  this.$ownerInstance.callMethod('receiveRenderData', {
                     method: "update_station",
                     station: this.objEditing.data,
                     view: {
                        x: vpt[4] - deltaX + deltaX2,
                        y: vpt[5] - deltaY + deltaY2,
                     }
                  });
               }
            }
            this.lastPosX = touch.clientX;
            this.lastPosY = touch.clientY;
            this.canvas.renderAll()
         } else if (this.isDrawing) {
            const vpt = this.canvas.viewportTransform;
            console.log("viewportTransform", vpt[4], vpt[5])
            //   console.log("viewportTransform", vpt[4], vpt[5])
            let startX = this.touchStartPoint.x - vpt[4]
            let startY = this.touchStartPoint.y - vpt[5]
            let endX = touch.clientX - vpt[4]
@@ -872,7 +1583,7 @@
            startY /= scale
            endX /= scale
            endY /= scale
            console.log("viewportTransform", startX, startY, endX, endY)
            //   console.log("viewportTransform", startX, startY, endX, endY)
            const left =
               endX > startX ?
               startX :
@@ -1017,7 +1728,7 @@
                  this.drawingObj = line
               }
               this.canvas.requestRenderAll();
               // this.canvas.requestRenderAll();
            }
@@ -1033,7 +1744,7 @@
         const _this = this
         const touch1 = touches[0];
         const touch2 = touches[1];
         console.log('多点移动', touch1.identifier, touch2.identifier);
         //console.log('多点移动', touch1.identifier, touch2.identifier);
         const distance = Math.sqrt(
            Math.pow(touch2.clientX - touch1.clientX, 2) +
@@ -1062,59 +1773,132 @@
         } else if (scale == scaleAuto) {
            return
         }
         console.log(scale, scaleAuto)
         //console.log(scale, scaleAuto)
         this.setZoomAuto(scale, center)
         console.log('多点移动 - 距离:', distance, '角度:', angle);
         //   console.log('多点移动 - 距离:', distance, '角度:', angle);
         // 多点移动逻辑
      },
      handleTouchEnd() {
         console.log('所有触摸结束');
         //   console.log('所有触摸结束');
         this.isDrawing = false
         this.drawingObj = undefined
         this.initialDistance = null;
         // 触摸结束逻辑
      },
      getXOnImg(x) {
         const mapX = x * this.mapInfo.proportion
         const imgX = (mapX - this.mapInfo.min_x) / this.mapInfo.img_proportion
         return imgX
      },
      getYOnImg(y) {
         const mapY = y * this.mapInfo.proportion
         const imgY = (this.mapInfo.max_y - mapY) / this.mapInfo.img_proportion
         return imgY
      },
      getSizeOnImg(size) {
         const imgSize = size * this.mapInfo.proportion / this.mapInfo.img_proportion
         return imgSize
      },
      getActualXFromImg(x) {
         const mapX = x * this.mapInfo.img_proportion + this.mapInfo.min_x
         const actualX = mapX / this.mapInfo.proportion
         return actualX
      },
      getActualYFromImg(y) {
         const mapY = this.mapInfo.max_y - y * this.mapInfo.img_proportion
         const actualY = mapY / this.mapInfo.proportion
         return actualY
      },
      getActualSizeFromImg(size) {
         const actualSize = size * this.mapInfo.img_proportion / this.mapInfo.proportion
         return actualSize
      },
      onScaleChange() {
         var scale = this.canvas.getZoom()
         if (scale < 1) {
            scale = 1
         }
         const scale2 = 1 / scale
         let list = this.canvas.getObjects()
         const filter = ["agv", "current_teaching", "edit_teaching", "edit_teaching_pt", "cmd", "station",
            "station_mark", "station_tip"
         ]
         list = list.filter((a) => filter.includes(a.eleType))
         list.forEach((obj) => {
            if (obj.eleType == "edit_teaching") {
               obj.set({
                  strokeWidth: 2 * scale2
               })
            } else {
               obj.set({
                  scaleX: scale2,
                  scaleY: scale2
               })
            }
            if (obj.eleType == "station" || obj.eleType == "edit_teaching") {
               const tipObj = obj.tipObj
               if (tipObj) {
                  tipObj.set({
                     left: obj.left,
                     top: obj.top - (obj.height / 2 + tipObj
                        .height / 2) * scale2
                  })
                  tipObj.setCoords()
               }
            }
            if (obj.eleType == "cmd") {
               const obj2 = obj.mainObj
               if (obj2?.eleType == "station") {
                  if (obj.id == `cancel`) {
                     obj.set({
                        left: obj2.left - (obj2.width / 2 + 50) * scale2,
                     })
                     obj.setCoords()
                  } else if (obj.id == `ok`) {
                     obj.set({
                        left: obj2.left + (obj2.width / 2 + 50) * scale2,
                     })
                     obj.setCoords()
                  }
               }
            }
         })
         if (this.objEditing) {
            this.createOkCancelControl(this.objEditing)
         }
         this.canvas.renderAll()
      },
      addStation(info) {
         const _this = this
         return new Promise((resolve) => {
            const zoom = _this.canvas.getZoom();
            let svg = "static/images/station.svg"
            // if (info.angle > 0) {
            //    if (info.angle / 3.14 <= 0.25) {
            //       svg = "static/images/station2.svg"
            //    } else if (info.angle / 3.14 <= 0.50) {
            //       svg = "static/images/station3.svg"
            //    } else if (info.angle / 3.14 <= 0.75) {
            //       svg = "static/images/station4.svg"
            //    } else if (info.angle / 3.14 <= 1) {
            //       svg = "static/images/station5.svg"
            //    }
            // } else if (info.angle < 0) {
            //    if (info.angle / 3.14 < -0.75) {
            //       svg = "static/images/station5.svg"
            //    } else if (info.angle / 3.14 < -0.5) {
            //       svg = "static/images/station6.svg"
            //    } else if (info.angle / 3.14 < -0.25) {
            //       svg = "static/images/station7.svg"
            //    } else if (info.angle / 3.14 < 0) {
            //       svg = "static/images/station8.svg"
            //    }
            // }
            //   const scale = this.getAutoScale()
            const left = info.x // * scale
            const top = info.y //* scale
            const angle = info.angle * 180 / 3.14
            console.log("addStation", svg, info)
            const left = _this.getXOnImg(info.x) // * scale
            const top = _this.getYOnImg(info.y) //* scale
            const angle = info.angle * 180 / Math.PI
            fabric.loadSVGFromURL(svg).then(
               ({
                  objects,
                  options
               }) => {
                  const obj = fabric.util.groupSVGElements(objects, options);
                  obj.set({
                  var scale = this.canvas.getZoom()
                  if (scale < 1) {
                     scale = 1
                  }
                  const scale2 = 1 / scale
                  const objGroup = fabric.util.groupSVGElements(objects, options);
                  objGroup.set({
                     id: `station_${new Date().getTime()}`,
                     eleType: "station",
                     left,
@@ -1123,6 +1907,10 @@
                     data: info,
                     originX: "center",
                     originY: "center",
                     // width: options.width,
                     // height: options.height,
                     // viewBoxWidth: options.viewBoxWidth || options.width,
                     // viewBoxHeight: options.viewBoxHeight || options.height,
                     hasControls: this.editMode,
                     lockRotation: true,
                     lockScalingX: true,
@@ -1130,21 +1918,589 @@
                     lockMovementX: true,
                     lockMovementY: true,
                     canSelect: true,
                     lockEdit: true
                     lockEdit: true,
                     scaleX: scale2,
                     scaleY: scale2,
                  })
                  _this.canvas.add(objGroup)
                  const objectText = new fabric.Text(`${info.name}`, {
                     id: `station_tip${new Date().getTime()}`,
                     eleType: "station_tip",
                     left: objGroup.left,
                     top: objGroup.top - 20,
                     fontSize: 14,
                     fontFamily: "Microsoft YaHei", //
                     fill: "#000", //
                     stroke: "#000", //
                     textBaseline: "alphabetic", // Correct value
                     originX: "center",
                     originY: "center",
                     hasControls: false,
                     lockRotation: true,
                     lockScalingX: true,
                     lockScalingY: true,
                     lockMovementX: true,
                     lockMovementY: true,
                     canSelect: false,
                     selectable: false,
                     scaleX: scale2,
                     scaleY: scale2,
                  });
                  _this.addObject(obj)
                  objGroup.set({
                     tipObj: objectText
                  })
                  objectText.set({
                     left: objGroup.left,
                     top: objGroup.top - (objGroup.height / 2 + objectText
                        .height / 2) * scale2,
                  })
                  _this.canvas.add(objectText)
                  _this.canvas.bringObjectToFront(objGroup);
                  resolve()
               }
            )
         })
      },
      setMarkStation(obj, mark) {
         console.log("setMarkStation", obj.id, obj.mark, mark)
         if (mark) {
            if (obj.mark) {
               return
            }
            var scale = this.canvas.getZoom()
            if (scale < 1) {
               scale = 1
            }
            const scale2 = 1 / scale
            let ellipse = new fabric.Ellipse({
               id: `station_mark${new Date().getTime()}`,
               eleType: "station_mark",
               left: obj.left,
               top: obj.top,
               rx: 20,
               ry: 20,
               stroke: "#ff7f23",
               strokeWidth: 2,
               fill: "#ffffff00",
               originX: "center",
               originY: "center",
               lockRotation: true,
               lockScalingX: true,
               lockScalingY: true,
               scaleX: scale2,
               scaleY: scale2,
            });
            obj.set({
               mark: true,
               markObj: ellipse,
            })
            obj.add(ellipse)
         } else {
            if (obj.mark) {
               obj.remove(obj.markObj)
               delete obj.markObj
               obj.set({
                  mark: false,
               })
            }
            return
         }
      },
      updateCurrentTeaching(teachingData) {
         var posArr = teachingData.pos_list || []
         const pos_list = []
         if (this.curTeachingObj) {
            this.canvas.remove(this.curTeachingObj)
            this.curTeachingObj = null
         }
         posArr.forEach((item) => {
            const curIndex = pos_list.findIndex((item2) => item2.x === item.x && item2.y === item.y)
            if (curIndex < 0) {
               pos_list.push(item)
            }
         })
         let path2 = ""
         const theta = 20;
         let headlen = 10;
         var main_road = teachingData.main_road || 0
         const len = pos_list.length
         let fromX = 0,
            fromY = 0,
            toX = 0,
            toY = 0;
         for (let index = 0; index < len; index++) {
            const pt = pos_list[index]
            let pt2 = {
               x: this.getXOnImg(pt.x),
               y: this.getYOnImg(pt.y)
            }
            if (index > 0) {
               if ((index % 50 == 0 || index == len - 1 || index % 50 == 1)) {
                  if (index % 50 == 0 || index == len - 1) {
                     toX = pt2.x
                     toY = pt2.y
                     path2 += ` L${pt2.x} ${pt2.y}`
                     // 计算各角度和对应的P2,P3坐标
                     let angle = (Math.atan2(fromY - toY, fromX - toX) * 180) / Math.PI,
                        angle1 = ((angle + theta) * Math.PI) / 180,
                        angle2 = ((angle - theta) * Math.PI) / 180,
                        topX = headlen * Math.cos(angle1),
                        topY = headlen * Math.sin(angle1),
                        botX = headlen * Math.cos(angle2),
                        botY = headlen * Math.sin(angle2);
                     let arrowX = fromX - topX,
                        arrowY = fromY - topY;
                     arrowX = toX + topX;
                     arrowY = toY + topY;
                     path2 += " L " + arrowX + " " + arrowY;
                     arrowX = toX + botX;
                     arrowY = toY + botY;
                     path2 += " M " + arrowX + " " + arrowY;
                     path2 += " L " + toX + " " + toY;
                  }
               } else {
                  path2 += ` L${pt2.x} ${pt2.y}`
               }
            } else {
               path2 = `M${pt2.x} ${pt2.y}`
            }
            fromX = pt2.x
            fromY = pt2.y
         }
         let strokeWidth = 1
         let stroke = "#95DE64"
         if (main_road == 1) {
            stroke = "#69C0FF"
         }
         const objPath = new fabric.Path(
            path2, {
               id: "current_teaching",
               eleType: "current_teaching",
               stroke: stroke,
               strokeWidth,
               // strokeDashArray: [5, 3],
               // strokeLineCap: 'butt',
               fill: "#ffffff00",
               hasControls: false,
               lockRotation: true,
               lockScalingX: true,
               lockScalingY: true,
               lockMovementX: true,
               lockMovementY: true,
               selectable: false,
               opacity: 1,
            })
         this.canvas.add(objPath)
         this.curTeachingObj = objPath
      },
      addTeachingPath(teachingData, id, type) {
         var posArr = teachingData.pos_list || []
         const pos_list = []
         posArr.forEach((item) => {
            const curIndex = pos_list.findIndex((item2) => item2.x === item.x && item2.y === item.y)
            if (curIndex < 0) {
               pos_list.push(item)
            }
         })
         console.log(posArr.length, pos_list.length)
         let path2 = ""
         const theta = 20;
         let headlen = 10;
         var main_road = 1
         const len = pos_list.length
         let fromX = 0,
            fromY = 0,
            toX = 0,
            toY = 0;
         for (let index = 0; index < len; index++) {
            const pt = pos_list[index]
            let pt2 = {
               x: this.getXOnImg(pt.x),
               y: this.getYOnImg(pt.y)
            }
            if (index > 0) {
               if ((type == "public_teaching") && (index % 50 == 0 || index == len - 1 || index % 50 == 1)) {
                  if (index % 50 == 1 || index == len - 1) {
                     if (teachingData.bidirection == 1 && index < len - 1) { //- 1
                        toY = fromY
                        toX = fromX
                        fromX = pt2.x
                        fromY = pt2.y
                        let angle = (Math.atan2(fromY - toY, fromX - toX) * 180) / Math.PI,
                           angle1 = ((angle + theta) * Math.PI) / 180,
                           angle2 = ((angle - theta) * Math.PI) / 180,
                           topX = headlen * Math.cos(angle1),
                           topY = headlen * Math.sin(angle1),
                           botX = headlen * Math.cos(angle2),
                           botY = headlen * Math.sin(angle2);
                        let arrowX = fromX - topX,
                           arrowY = fromY - topY;
                        arrowX = toX + topX;
                        arrowY = toY + topY;
                        let path3 = " L " + arrowX + " " + arrowY;
                        arrowX = toX + botX;
                        arrowY = toY + botY;
                        path3 += " M " + arrowX + " " + arrowY;
                        path3 += " L " + toX + " " + toY;
                        path2 += path3
                        fromY = toY
                        fromX = toX
                     }
                  }
                  if (index % 50 == 0 || index == len - 1) {
                     toX = pt2.x
                     toY = pt2.y
                     // if (fromX == toX && fromY == toY) {
                     //    if (index - 2 >= 0) {
                     //       const pt3 = pos_list[index - 2]
                     //       fromX = this.getXOnImg(pt3.x),
                     //          fromY = this.getYOnImg(pt3.y)
                     //    } else {
                     //       continue;
                     //    }
                     // }
                     path2 += ` L${pt2.x} ${pt2.y}`
                     // 计算各角度和对应的P2,P3坐标
                     let angle = (Math.atan2(fromY - toY, fromX - toX) * 180) / Math.PI,
                        angle1 = ((angle + theta) * Math.PI) / 180,
                        angle2 = ((angle - theta) * Math.PI) / 180,
                        topX = headlen * Math.cos(angle1),
                        topY = headlen * Math.sin(angle1),
                        botX = headlen * Math.cos(angle2),
                        botY = headlen * Math.sin(angle2);
                     let arrowX = fromX - topX,
                        arrowY = fromY - topY;
                     arrowX = toX + topX;
                     arrowY = toY + topY;
                     path2 += " L " + arrowX + " " + arrowY;
                     arrowX = toX + botX;
                     arrowY = toY + botY;
                     path2 += " M " + arrowX + " " + arrowY;
                     path2 += " L " + toX + " " + toY;
                  }
                  //   console.log(`箭头 L${pt2.x} ${pt2.y}`)
               } else {
                  //   console.log(`点 L${pt2.x} ${pt2.y} ${index} == ${len-1}`)
                  path2 += ` L${pt2.x} ${pt2.y}`
               }
            } else {
               main_road = pt.main_road
               if (main_road == 1) {
                  headlen = 15
               }
               path2 = `M${pt2.x} ${pt2.y}`
            }
            fromX = pt2.x
            fromY = pt2.y
         }
         //console.log(path2)
         // path2 += " Z"
         let strokeWidth = 1
         let stroke = "#95DE64"
         if (type == "station_teaching") {
            stroke = "#69C0FF"
         } else {
            if (main_road == 1) {
               stroke = "#69C0FF"
            }
         }
         let list = this.canvas.getObjects() || []
         list = list.filter((a) => a.eleType == "station_teaching" || a.eleType == "public_teaching")
         let lenTeaching = list.length
         if (main_road == 1) {
            list = list.filter((a) => a.eleType == "public_teaching" && a.mainRoad == 1)
            lenTeaching = list.length
         }
         let ptList = []
         const objPath = new fabric.Path(
            path2, {
               id: id,
               eleType: type,
               stroke: stroke,
               strokeWidth,
               // strokeDashArray: [5, 3],
               // strokeLineCap: 'butt',
               fill: "#ffffff00",
               hasControls: false,
               lockRotation: true,
               lockScalingX: true,
               lockScalingY: true,
               lockMovementX: true,
               lockMovementY: true,
               selectable: false,
               opacity: 0,
               mainRoad: main_road,
               data: teachingData
            })
         this.canvas.add(objPath)
         // this.canvas.sendObjectToBack(objPath);
         // lenTeaching = 0
         // for (let i = list.length - 1; i >= 0; i--) {
         //    const obj = list[i]
         //    if (this.compareOverlap(obj, objPath)) {
         //       lenTeaching = i + 1
         //       break
         //    }
         // }
         this.canvas.moveObjectTo(objPath, lenTeaching + 1);
         return objPath
      },
      isObjectFullyContained(outerObj, innerObj) {
         const outer = outerObj.getBoundingRect(true, true);
         const inner = innerObj.getBoundingRect(true, true);
         return (
            outer.left <= inner.left &&
            outer.top <= inner.top &&
            outer.left + outer.width >= inner.left + inner.width &&
            outer.top + outer.height >= inner.top + inner.height
         );
      },
      compareOverlap(bottomObj, topObj) {
         const bottomRect = bottomObj.getBoundingRect(true, true);
         const topRect = topObj.getBoundingRect(true, true);
         const maxLeft = Math.min(bottomRect.left + bottomRect.width, topObj.width + topObj.left)
         const maxTop = Math.min(bottomRect.top + bottomRect.height, topObj.height + topObj.top)
         const minRight = Math.min(bottomRect.left + bottomRect.width, topObj.width + topObj.left)
         const minBottom = Math.min(bottomRect.top + bottomRect.height, topObj.height + topObj.top)
         if (minRight <= maxLeft || minBottom <= maxTop) return true; // 无重叠
         const bottomArea = bottomRect.width * bottomRect.height;
         const topArea = topRect.width * topRect.height;
         const interArea = (minRight - maxLeft) * (minBottom - maxTop)
         if (bottomArea - interArea > topArea - -interArea) {
            return true; //底部未重叠面积大
         }
         return false;
      },
      showTeachingPath(show) {
         let list = this.canvas.getObjects() || []
         list = list.filter((a) => a.eleType == "station_teaching" || a.eleType == "public_teaching")
         for (let i2 in list) {
            const obj = list[i2]
            obj.set({
               opacity: show ? 1 : 0,
               strokeDashArray: [],
               strokeLineCap: '',
               hasControls: show,
               selectable: show,
            })
         }
      },
      showEditTeachingPath(teachingMode) {
         let list = this.canvas.getObjects() || []
         let eleType = ""
         let id = ""
         if (teachingMode.mode == "Public") {
            eleType = "public_teaching"
            id = `public_teaching_${teachingMode.name}`
         } else if (teachingMode.mode == "Stations") {
            eleType = "station_teaching"
            id = `station_teaching_${teachingMode.src_dst}`
         } else {
            return
         }
         let objTeaching;
         for (let i2 in list) {
            const obj = list[i2]
            if (obj.eleType == eleType && obj.id == id) {
               obj.set({
                  opacity: 1,
                  selectable: false
               })
               objTeaching = obj
            } else {
               obj.set({
                  opacity: 0.5
               })
            }
         }
         if (objTeaching) {
            var scale = this.canvas.getZoom()
            if (scale < 1) {
               scale = 1
            }
            const scale2 = 1 / scale
            this.canvas.discardActiveObject();
            const width = (objTeaching.width > 200 ? 200 : objTeaching.width < 100 ? 100 : objTeaching
               .width) * scale2
            const height = (objTeaching.height > 200 ? 200 : objTeaching.height < 100 ? 100 : objTeaching
               .height) * scale2
            const pt = teachingMode.point
            let left = objTeaching.left - 10
            let top = objTeaching.top - 10
            if (pt) {
               left = pt.x - width / 2
               top = pt.y - height / 2
            }
            console.log(left,
               top,
               width,
               height, scale2)
            const rect = new fabric.Rect({
               id: `edit_teaching`,
               eleType: "edit_teaching",
               left,
               top,
               width,
               height,
               stroke: "#ff4d4f",
               strokeWidth: 2 * scale2,
               strokeDashArray: [5, 3],
               strokeLineCap: 'butt',
               fill: "#ff4d4f20",
               hasControls: true,
               lockRotation: true,
               lockScalingX: true,
               lockScalingY: true,
               mainObj: objTeaching
            })
            this.canvas.add(rect)
            this.createOkCancelControl(rect)
            const ptList = []
            const path = [{
               x: rect.left,
               y: rect.top
            }, {
               x: rect.left,
               y: rect.top + rect.height
            }, {
               x: rect.left + rect.width,
               y: rect.top + rect.height
            }, {
               x: rect.left + rect.width,
               y: rect.top
            }]
            path.forEach((pt, index) => {
               let ellipse = new fabric.Ellipse({
                  id: `edit_teaching_pt_${index}`,
                  eleType: "edit_teaching_pt",
                  left: pt.x,
                  top: pt.y,
                  rx: 10,
                  ry: 10,
                  stroke: "#ff4d4f",
                  strokeWidth: 1,
                  fill: "#ff4d4f",
                  originX: "center",
                  originY: "center",
                  lockRotation: true,
                  lockScalingX: true,
                  lockScalingY: true,
                  scaleX: scale2,
                  scaleY: scale2,
                  mainObj: rect
               });
               this.canvas.add(ellipse)
               ptList.push(ellipse)
            })
            rect.set({
               ptObjs: ptList,
               oldLeft: rect.left,
               oldTop: rect.top,
            })
            /*const zoom = this.canvas.getZoom();
            const eleHeight = this.eleHeight - 150
            const info = {
               x: rect.left + rect.width / 2,
               y: rect.top + rect.height / 2
            }
            let deltaX = info.x * zoom - this.eleWidth / 2 // * scale;
            let deltaY = info.y * zoom - eleHeight / 2 //* scale;
            const vpt = this.canvas.viewportTransform;
            const oldX = vpt[4]
            const oldY = vpt[5]
            if (deltaX + this.eleWidth > this.workSpace.width)
               deltaX = this.workSpace.width - this.eleWidth
            if (deltaY + eleHeight > this.workSpace.height)
               deltaY = this.workSpace.height - eleHeight
            if (oldX + this.eleWidth >= info.x * zoom && info.x * zoom >= oldX) {
               deltaX = -oldX
               //console.log("move_canvas X", oldX)
            }
            if (oldY + eleHeight >= info.y * zoom && info.y * zoom >= oldY) {
               //   console.log("move_canvas Y", oldY)
               deltaY = -oldY
            }
            this.canvas.absolutePan(new fabric.Point(deltaX, deltaY));*/
         } else {
            for (let i2 in list) {
               const obj = list[i2]
               obj.set({
                  opacity: 1
               })
            }
         }
      },
      updateEditTeachingPath(obj) {
         const ptObjs = obj.ptObjs || []
         ptObjs[0].set({
            left: obj.left,
            top: obj.top
         })
         ptObjs[0].setCoords()
         ptObjs[1].set({
            left: obj.left,
            top: obj.top + obj.height
         })
         ptObjs[1].setCoords()
         ptObjs[2].set({
            left: obj.left + obj.width,
            top: obj.top + obj.height
         })
         ptObjs[2].setCoords()
         ptObjs[3].set({
            left: obj.left + obj.width,
            top: obj.top
         })
         ptObjs[3].setCoords()
         this.createOkCancelControl(obj)
         this.canvas.requestRenderAll();
      },
      updateAgv(info) {
         const _this = this
         return new Promise((resolve) => {
            //   const scale = this.getAutoScale()
            const left = info.x // * scale
            const top = info.y // * scale
            const angle = info.angle * 180 / 3.14
            const left = _this.getXOnImg(info.x) // * scale
            const top = _this.getYOnImg(info.y) //* scale
            const angle = info.angle * 180 / Math.PI
            if (this.agvObj) {
               this.agvObj.set({
                  left,
@@ -1163,6 +2519,11 @@
                     options
                  }) => {
                     var scale = this.canvas.getZoom()
                     if (scale < 1) {
                        scale = 1
                     }
                     const scale2 = 1 / scale
                     const obj = fabric.util.groupSVGElements(objects, options);
                     obj.set({
@@ -1182,11 +2543,14 @@
                        lockMovementX: true,
                        lockMovementY: true,
                        selectable: false,
                        scaleX: scale2,
                        scaleY: scale2,
                        lockEdit: true
                     });
                     console.log("agv", JSON.stringify(info))
                     _this.canvas.add(obj)
                     //   _this.canvas.bringObjectToFront(obj);
                     _this.agvObj = obj
                     resolve()
                  }
@@ -1195,6 +2559,684 @@
         })
      },
      addVirtualWallShow(info) {
         const path = info.path || []
         if (path.length != 2)
            return
         const line = new fabric.Line([this.getXOnImg(path[0].x), this.getYOnImg(path[0].y), this
            .getXOnImg(
               path[1]
               .x), this.getYOnImg(path[1].y)
         ], {
            id: `${info.id}`,
            eleType: "virtual_wall",
            stroke: info.color || "#ff4d4f",
            strokeWidth: 3,
            strokeDashArray: [5, 3],
            strokeLineCap: 'butt',
            hasControls: false,
            lockRotation: true,
            lockScalingX: true,
            lockScalingY: true,
            lockMovementX: true,
            lockMovementY: true,
            selectable: true,
            data: info
         });
         return line
      },
      addVirtualWall(info) {
         const path = info.path || []
         if (path.length != 2)
            return
         const line = new fabric.Line([this.getXOnImg(path[0].x), this.getYOnImg(path[0].y), this
            .getXOnImg(
               path[1]
               .x), this.getYOnImg(path[1].y)
         ], {
            id: `${info.id}`,
            eleType: "virtual_wall",
            stroke: info.color || "#ff4d4f",
            strokeWidth: 3,
            strokeDashArray: [5, 3],
            strokeLineCap: 'butt',
            hasControls: false,
            lockRotation: true,
            lockScalingX: true,
            lockScalingY: true,
            selectable: true,
            data: info
         });
         this.canvas.add(line)
         let ptImg = {
            x: this.getXOnImg(path[0].x),
            y: this.getYOnImg(path[0].y)
         }
         let pt = {
            x: path[0].x,
            y: path[0].y
         }
         let ellipse = new fabric.Ellipse({
            id: `${info.id}_${ pt.x}_${pt.y}`,
            eleType: "wall_pt",
            left: ptImg.x,
            top: ptImg.y,
            rx: 10,
            ry: 10,
            stroke: info.color || "#ff4d4f",
            strokeWidth: 1,
            fill: info.color || "#ff4d4f",
            originX: "center",
            originY: "center",
            lockEdit: true,
            lockRotation: true,
            lockScalingX: true,
            lockScalingY: true,
            mainObj: line
         });
         this.canvas.add(ellipse)
         ptImg = {
            x: this.getXOnImg(path[1].x),
            y: this.getYOnImg(path[1].y)
         }
         pt = {
            x: path[1].x,
            y: path[1].y
         }
         let ellipse2 = new fabric.Ellipse({
            id: `${info.id}_${ pt.x}_${pt.y}`,
            eleType: "wall_pt",
            left: ptImg.x,
            top: ptImg.y,
            rx: 10,
            ry: 10,
            stroke: info.color || "#ff4d4f",
            strokeWidth: 1,
            fill: info.color || "#ff4d4f",
            originX: "center",
            originY: "center",
            lockEdit: true,
            lockRotation: true,
            lockScalingX: true,
            lockScalingY: true,
            mainObj: line
         });
         this.canvas.add(ellipse2)
         line.set({
            ptObj1: ellipse,
            ptObj2: ellipse2
         })
         this.createOkCancelControl(line)
         this.editObject = line
         line.set({
            oldLeft: line.left,
            oldTop: line.top,
         })
         return line
      },
      updateVirtualWall(obj, info) {
         const path = info.path || []
         if (path.length != 2)
            return
         let ptImg1 = {
            x: this.getXOnImg(path[0].x),
            y: this.getYOnImg(path[0].y)
         }
         let ptImg2 = {
            x: this.getXOnImg(path[1].x),
            y: this.getYOnImg(path[1].y)
         }
         let pt1 = {
            x: path[0].x,
            y: path[0].y
         }
         let pt2 = {
            x: path[1].x,
            y: path[1].y
         }
         const left = ptImg2.x > ptImg1.x ? ptImg1.x : ptImg2.x;
         const top = ptImg2.y > ptImg1.y ? ptImg1.y : ptImg2.y;
         obj.set({
            left: left,
            top: top,
            x1: ptImg1.x,
            y1: ptImg1.y,
            x2: ptImg2.x,
            y2: ptImg2.y,
            data: info,
         });
         obj.setCoords()
         obj.ptObj1.set({
            id: `${info.id}_${ pt1.x}_${pt1.y}`,
            left: ptImg1.x,
            top: ptImg1.y,
         })
         obj.ptObj2.set({
            id: `${info.id}_${ pt2.x}_${pt2.y}`,
            left: ptImg2.x,
            top: ptImg2.y,
         })
         obj.ptObj1.setCoords()
         obj.ptObj2.setCoords()
         this.createOkCancelControl(obj)
         this.editObject = obj
         obj.set({
            oldLeft: obj.left,
            oldTop: obj.top,
         })
      },
      addVirtualWallFinish(obj) {
         this.canvas.remove(obj.ptObj1)
         this.canvas.remove(obj.ptObj2)
         delete obj.ptObj1
         delete obj.ptObj2
         this.canvas.sendObjectToBack(obj);
         this.canvas.moveObjectTo(obj, 1);
         obj.set({
            lockMovementX: true,
            lockMovementY: true,
            selectable: false
         })
      },
      removeVirtualWall(obj) {
         this.closeOkCancelControl()
         if (obj.ptObj1)
            this.canvas.remove(obj.ptObj1)
         if (obj.ptObj1)
            this.canvas.remove(obj.ptObj2)
         this.canvas.remove(obj)
      },
      addRegionShow(info) {
         const _this = this
         const path = info.path || []
         let path2 = ""
         path.forEach((pt, index) => {
            let pt2 = {
               x: this.getXOnImg(pt.x),
               y: this.getYOnImg(pt.y)
            }
            // if (pt.x < 10) {
            //    pt.x = 10
            // }
            // if (pt.y < 10) {
            //    pt.y = 10
            // }
            // if (pt.x > this.workSpace.width - 10) {
            //    pt.x = 10
            // }
            // if (pt.y > this.workSpace.height - 10) {
            //    pt.y = 10
            // }
            if (index > 0) {
               path2 += ` L${pt2} ${pt2.y}`
            } else {
               path2 = `M${pt2.x} ${pt2.y}`
            }
         })
         path2 += " Z"
         let ptList = []
         let fillColor = info.color || "#ff4538"
         const objPath = new fabric.Path(
            path2, {
               id: `${info.id}`,
               eleType: "region",
               stroke: info.color || "#ff4d4f",
               strokeWidth: 3,
               strokeDashArray: [5, 3],
               strokeLineCap: 'butt',
               fill: hexToRGBA(fillColor, 0.2),
               hasControls: false,
               lockRotation: true,
               lockScalingX: true,
               lockScalingY: true,
               lockMovementX: true,
               lockMovementY: true,
               selectable: false,
               data: info
            })
         this.canvas.add(objPath)
         return objPath
      },
      addRegion(info) {
         const _this = this
         const path = info.path || []
         let path2 = ""
         path.forEach((pt, index) => {
            // if (pt.x < 10) {
            //    pt.x = 10
            // }
            // if (pt.y < 10) {
            //    pt.y = 10
            // }
            // if (pt.x > this.workSpace.width - 10) {
            //    pt.x = this.workSpace.width - 10
            // }
            // if (pt.y > this.workSpace.height - 10) {
            //    pt.y = this.workSpace.height - 10
            // }
            let pt2 = {
               x: this.getXOnImg(pt.x),
               y: this.getYOnImg(pt.y)
            }
            if (index > 0) {
               path2 += ` L${pt2.x} ${pt2.y}`
            } else {
               path2 = `M${pt2.x} ${pt2.y}`
            }
         })
         path2 += " Z"
         let ptList = []
         const objPath = new fabric.Path(
            path2, {
               id: `${info.id}`,
               eleType: "region",
               stroke: info.color || "#ff4d4f",
               strokeWidth: 3,
               strokeDashArray: [5, 3],
               strokeLineCap: 'butt',
               fill: "rgba(255,255,255,0)",
               hasControls: false,
               lockRotation: true,
               lockScalingX: true,
               lockScalingY: true,
               data: info
            })
         this.canvas.add(objPath)
         path.forEach((pt, index) => {
            let pt2 = {
               x: this.getXOnImg(pt.x),
               y: this.getYOnImg(pt.y)
            }
            let ellipse = new fabric.Ellipse({
               id: `${info.id}_${ pt.x}_${pt.y}`,
               eleType: "region_pt",
               left: pt.x,
               top: pt.y,
               rx: 10,
               ry: 10,
               stroke: info.color || "#ff4d4f",
               strokeWidth: 1,
               fill: info.color || "#ff4d4f",
               originX: "center",
               originY: "center",
               lockRotation: true,
               lockScalingX: true,
               lockScalingY: true,
               mainObj: objPath
            });
            this.canvas.add(ellipse)
            ptList.push(ellipse)
         })
         const ptLast = path[0]
         const ptLast2 = path[path.length - 1]
         const objAddList = []
         let ellipse = new fabric.Ellipse({
            left: -10,
            top: -10,
            rx: 10,
            ry: 10,
            stroke: info.color || "#ff4d4f",
            strokeWidth: 1,
            fill: info.color || "#ff4d4f",
         });
         objAddList.push(ellipse)
         let line = new fabric.Line([-6, 0, 6, 0], {
            stroke: "#FEFEFE",
            strokeWidth: 2,
         });
         objAddList.push(line)
         line = new fabric.Line([0, -6, 0, 6], {
            stroke: "#FEFEFE",
            strokeWidth: 2,
         });
         objAddList.push(line)
         let objGroup = new fabric.Group(objAddList, {
            id: `${info.id}_add`,
            eleType: "region_pt_add",
            left: ptLast2.x + (ptLast.x - ptLast2.x) / 2,
            top: ptLast2.y + (ptLast.y - ptLast2.y) / 2,
            width: 20,
            height: 20,
            originX: "center",
            originY: "center",
            lockRotation: true,
            lockScalingX: true,
            lockScalingY: true,
            lockMovementX: true,
            lockMovementY: true,
            mainObj: objPath
         });
         this.canvas.add(objGroup)
         objPath.set({
            ptObjs: ptList,
            ptAddObj: objGroup,
            oldLeft: objPath.left,
            oldTop: objPath.top,
         })
         this.createOkCancelControl(objPath)
         this.editObject = objPath
         return objPath
      },
      updateRegion(obj, info) {
         const path = info.path || []
         let path2 = ""
         path.forEach((pt, index) => {
            let pt2 = {
               x: this.getXOnImg(pt.x),
               y: this.getYOnImg(pt.y)
            }
            if (index > 0) {
               path2 += ` L${pt2.x} ${pt2.y}`
            } else {
               path2 = `M${pt2.x} ${pt2.y}`
            }
         })
         path2 += " Z"
         const objList = obj.ptObjs || []
         const objAdd = obj.ptAddObj
         let listObj = this.canvas.getObjects() || []
         const curIndex = listObj.findIndex((a) => a.eleType == "region" && a.id == obj.id)
         let oldObj
         if (curIndex > -1) {
            oldObj = listObj[curIndex]
         }
         let objNew = new fabric.Path(
            path2, {
               id: `${info.id}`,
               eleType: "region",
               stroke: info.color || "#ff4d4f",
               strokeWidth: 3,
               strokeDashArray: [5, 3],
               strokeLineCap: 'butt',
               fill: "rgba(255,255,255,0)",
               hasControls: false,
               lockRotation: true,
               lockScalingX: true,
               lockScalingY: true,
               data: info,
               ptObjs: objList,
               ptAddObj: objAdd
            })
         this.canvas.add(objNew)
         // console.log("remove", curIndex, obj.id, obj)
         if (oldObj) {
            this.canvas.remove(oldObj)
         }
         if (objList.length > path.length) {
            for (let i = path.length; i < objList.length; i++) {
               this.canvas.remove(objList[i])
            }
         } else if (objList.length == path.length) {
            for (let i = 0; i < objList.length; i++) {
               const pt = path[i]
               let pt2 = {
                  x: this.getXOnImg(pt.x),
                  y: this.getYOnImg(pt.y)
               }
               objList[i].set({
                  id: `${info.id}_${ pt.x}_${pt.y}`,
                  left: pt2.x,
                  top: pt2.y,
                  mainObj: objNew
               })
               this.canvas.bringObjectToFront(objList[i]);
               objList[i].setCoords()
            }
         } else {
            for (let i = objList.length; i < path.length; i++) {
               const pt = path[i]
               let pt2 = {
                  x: this.getXOnImg(pt.x),
                  y: this.getYOnImg(pt.y)
               }
               let ellipse = new fabric.Ellipse({
                  id: `${info.id}_${ pt.x}_${pt.y}`,
                  eleType: "region_pt",
                  left: pt2.x,
                  top: pt2.y,
                  rx: 10,
                  ry: 10,
                  stroke: info.color || "#ff4d4f",
                  strokeWidth: 1,
                  fill: info.color || "#ff4d4f",
                  originX: "center",
                  originY: "center",
                  lockRotation: true,
                  lockScalingX: true,
                  lockScalingY: true,
                  mainObj: objNew
               });
               this.canvas.add(ellipse)
               objList.push(ellipse)
            }
         }
         const ptLast = {
            x: this.getXOnImg(path[0].x),
            y: this.getYOnImg(path[0].y)
         }
         const ptLast2 = {
            x: this.getXOnImg(path[path.length - 1].x),
            y: this.getYOnImg(path[path.length - 1].y)
         }
         objNew.ptAddObj.set({
            left: ptLast2.x + (ptLast.x - ptLast2.x) / 2,
            top: ptLast2.y + (ptLast.y - ptLast2.y) / 2,
            mainObj: objNew
         })
         objNew.set({
            oldLeft: objNew.left,
            oldTop: objNew.top,
         })
         this.canvas.bringObjectToFront(objNew.ptAddObj);
         objNew.ptAddObj.setCoords()
         let list = this.canvas.getObjects() || []
         list = list.filter((a) => a.eleType == "cmd")
         list.forEach((obj) => {
            obj.mainObj = objNew
         })
         this.editObject = objNew
         this.createOkCancelControl(objNew)
      },
      addRegionFinish(obj) {
         const objList = obj.ptObjs || []
         for (let i = 0; i < objList.length; i++) {
            this.canvas.remove(objList[i])
         }
         let color = obj.data.color || "#ff4d4f"
         obj.set({
            fill: hexToRGBA(color, 0.2)
         })
         this.canvas.remove(obj.ptAddObj)
         delete obj.ptObjs
         delete obj.ptAddObj
         this.canvas.sendObjectToBack(obj);
         this.canvas.moveObjectTo(obj, 1);
         obj.set({
            lockMovementX: true,
            lockMovementY: true,
            selectable: false
         })
      },
      removeRegion(obj) {
         this.closeOkCancelControl()
         const objList = obj.ptObjs || []
         for (let i = 0; i < objList.length; i++) {
            this.canvas.remove(objList[i])
         }
         if (obj.ptAddObj)
            this.canvas.remove(obj.ptAddObj)
         this.canvas.remove(obj)
      },
      updateAgvLaser(param) {
         const angle = param.angle - Math.PI / 2 // * 180 / Math.PI
         const pt = {
            x: this.getXOnImg(param.x),
            y: this.getYOnImg(param.y)
         }
         let ellipse = new fabric.Ellipse({
            id: "agv_laser",
            eleType: "agv_laser",
            left: pt.x,
            top: pt.y,
            rx: 2,
            ry: 2,
            stroke: "#00aa00",
            strokeWidth: 1,
            fill: "#00aa00",
            originX: "center",
            originY: "center",
            selectable: false,
            hasControls: true,
            lockRotation: true,
            lockScalingX: true,
            lockScalingY: true,
            lockMovementX: true,
            lockMovementY: true,
         });
         this.canvas.add(ellipse)
         const offX = 20 * Math.cos(angle)
         const offY = 20 * Math.sin(angle)
         console.log("angle", param.angle, offX, offY)
         if (this.objAgvLaser) {
            this.canvas.remove(this.objAgvLaser)
         }
         const line = new fabric.Line([pt.x, pt.y, pt.x + offX,
            pt.y + offY
         ], {
            id: "agv_laser_angle",
            eleType: "agv_laser_angle",
            stroke: "#00aa00",
            strokeWidth: 2,
            lockRotation: true,
            lockScalingX: true,
            lockScalingY: true,
            lockMovementX: true,
            lockMovementY: true,
         });
         this.objAgvLaser = line
         this.canvas.add(line)
      },
      updateLaserPoint(param) {
         let list2 = this.canvas.getObjects() || []
         list2 = list2.filter((a) => a.eleType == "laser_point")
         for (let i in list2) {
            const obj = list2[i]
            obj.set({
               fill: "#000",
            })
         }
         const list = param.xy || []
         for (let i in list) {
            const pt = list[i]
            const pt2 = {
               x: this.getXOnImg(pt[0]),
               y: this.getYOnImg(pt[1])
            }
            const point = new fabric.Rect({
               id: "laser_point",
               eleType: "laser_point",
               left: pt2.x,
               top: pt2.y,
               width: 1,
               height: 1,
               fill: "#F5222D",
               originX: "center",
               originY: "center",
               selectable: false,
               hasControls: true,
               lockRotation: true,
               lockScalingX: true,
               lockScalingY: true,
               lockMovementX: true,
               lockMovementY: true,
            });
            // let point = new fabric.Ellipse({
            //   id: "laser_point",
            //    eleType: "laser_point",
            //    left: pt[0],
            //    top: pt[1],
            //    rx: 2,
            //    ry: 1,
            //    strokeWidth: 1,
            //    stroke: "#F5222D",
            //    fill: "#F5222D",
            //    originX: "center",
            //    originY: "center",
            //    selectable: false,
            //    hasControls: true,
            //    lockRotation: true,
            //    lockScalingX: true,
            //    lockScalingY: true,
            //    lockMovementX: true,
            //    lockMovementY: true,
            // });
            this.canvas.add(point)
         }
      },
      ensurePointVisible(pt) {
         var zoom = this.canvas.getZoom();
         var vpt = this.canvas.viewportTransform; // 当前变换矩阵
         var newPanX = vpt[4];
         var newPanY = vpt[5];
         if (pt.x * zoom < vpt[4] + 80 || pt.x * zoom > vpt[4] + this.eleWidth - 80) {
            if (pt.x * zoom - this.eleWidth / 2 < 80) {
               newPanX = -80
            } else if (pt.x * zoom > this.mapInfo.img_x * zoom - 80) {
               newPanX = this.mapInfo.img_x * zoom - this.eleWidth + 80
            } else {
               newPanX = pt.x * zoom - this.eleWidth / 2
            }
         }
         if (pt.y * zoom < vpt[5] + 80 || pt.y * zoom > vpt[5] + this.eleHeight - 200) {
            if (pt.y * zoom - this.eleHeight / 2 < 80) {
               newPanY = -80
            } else if (pt.y * zoom > this.mapInfo.img_y * zoom - 200) {
               newPanY = this.mapInfo.img_y * zoom - this.eleHeight + 200
            } else {
               newPanY = pt.y * zoom - this.eleHeight / 2
            }
         }
         // 只有在需要时才平移
         if (newPanX !== vpt[4] || newPanY !== vpt[5]) {
            this.canvas.absolutePan({
               x: newPanX,
               y: newPanY
            });
         }
      },
      setAllObjectSelectable(selectable) {
         let flag = false
         this.canvas.forEachObject(function(obj) {
@@ -1225,13 +3267,13 @@
            } else {
               _this.receiveMsg(newValue, oldValue)
            }
         }, 500)
         }, 100)
      },
      async handleMsg(newValue, oldValue) {
         const _this = this
         try {
            //console.log("handleMsg", newValue)
            var data = JSON.parse(newValue);
            for (var i = 0; i < data.length; i++) {
               const item = data[i]
@@ -1243,47 +3285,32 @@
                     _this.editMode = false
                  }
               }
               if (item.method == "background") {
                  await _this.setBackground(item.param)
               } else if (item.method == "update_agv_state") {
                  const info = item.param || {}
                  await _this.updateAgv(info)
                  // const workSpaceWidth = this.workSpace.width
                  // const workSpaceHeight = this.workSpace.height
               } else if (item.method == "update_current_teaching") {
                  const info = item.param || []
                  await _this.updateCurrentTeaching(info)
               } else if (item.method == "move_canvas") {
                  const info = item.param || {}
                  //   const scale = this.getAutoScale()
                  const zoom = this.canvas.getZoom();
                  const eleHeight = this.eleHeight - 150
                  const info2 = item.param || {}
                  let deltaX = info.x * zoom - this.eleWidth / 2 // * scale;
                  let deltaY = info.y * zoom - eleHeight / 2 //* scale;
                  const vpt = this.canvas.viewportTransform;
                  const oldX = vpt[4]
                  const oldY = vpt[5]
                  if (deltaX + this.eleWidth > this.workSpace.width)
                     deltaX = this.workSpace.width - this.eleWidth
                  if (deltaY + eleHeight > this.workSpace.height)
                     deltaY = this.workSpace.height - eleHeight
                  if (oldX + this.eleWidth >= info.x * zoom && info.x * zoom >= oldX) {
                     deltaX = -oldX
                     //console.log("move_canvas X", oldX)
                  const pt = {
                     x: this.getXOnImg(info2.x),
                     y: this.getYOnImg(info2.y)
                  }
                  if (oldY + eleHeight >= info.y * zoom && info.y * zoom >= oldY) {
                     //   console.log("move_canvas Y", oldY)
                     deltaY = -oldY
                  }
                  _this.canvas.absolutePan(new fabric.Point(deltaX, deltaY));
                  this.ensurePointVisible(pt)
               } else if (item.method == "add_station") {
                  const stationList = item.param || []
                  let list = _this.canvas.getObjects() || []
                  for (let i2 in stationList) {
                     const station = stationList[i2]
                     const curIndex = list.findIndex((a) => a.data?.stationID == station.stationID)
                     const curIndex = list.findIndex((a) => a.data?.stationID == station
                        .stationID)
                     if (curIndex < 0) {
                        await _this.addStation(station)
                     }
@@ -1294,28 +3321,42 @@
                  list = list.filter((a) => a.eleType == "station")
                  for (let i2 in stationList) {
                     const station = stationList[i2]
                     const curIndex = list.findIndex((a) => a.data.stationID == station.stationID)
                     const curIndex = list.findIndex((a) => a.data.stationID == station
                        .stationID)
                     if (curIndex < 0) {
                        await _this.addStation(station)
                     } else {
                        //   _this.canvas.remove(list[curIndex])
                        const curStationObj = list[curIndex]
                        const angle = station.angle * 180 / 3.14
                        const angle = station.angle * 180 / Math.PI
                        //const scale = this.getAutoScale()
                        const left = station.x //* scale
                        const top = station.y //* scale
                        console.log("update_station", curIndex, left,
                           top,
                           angle)
                        if (curStationObj.left != left || top != station.y || curStationObj.angle !=
                           angle) {
                        const left = this.getXOnImg(station.x) //* scale
                        const top = this.getYOnImg(station.y) //* scale
                        if (curStationObj.left != left || top != station.y ||
                           curStationObj.angle != angle) {
                           curStationObj.set({
                              left,
                              top,
                              angle,
                              data: station
                           })
                           curStationObj.tipObj.set({
                              text: `${station.name}`,
                              left: curStationObj.left,
                              top: curStationObj.top - curStationObj.height / 2 -
                                 curStationObj
                                 .tipObj.height / 2,
                           })
                           curStationObj.tipObj.setCoords()
                           curStationObj.setCoords()
                           if (_this.editObject == curStationObj) {
                              _this.editObject.tipObj.set({
                                 visible: false
                              })
                              this.createOkCancelControl(curStationObj)
                           }
                        }
                     }
@@ -1324,46 +3365,39 @@
               } else if (item.method == "remove_station") {
                  const stationList = item.param || []
                  let list = _this.canvas.getObjects() || []
                  list = list.filter((a) => a.eleType == "station")
                  for (let i2 in stationList) {
                     const station = stationList[i2]
                     const curIndex = list.findIndex((a) => a.data.stationID == station.stationID)
                     const curIndex = list.findIndex((a) => a.data.stationID == station
                        .stationID)
                     if (curIndex > -1) {
                        const tipObj = list[curIndex].tipObj
                        _this.closeOkCancelControl()
                        console.log("remove_station", list[curIndex])
                        _this.canvas.remove(list[curIndex])
                        if (tipObj) {
                           _this.canvas.remove(tipObj)
                        }
                     }
                  }
               } else if (item.method == "select_station") {
                  const station = item.param || {}
                  let list = _this.canvas.getObjects() || []
                  list = list.filter((a) => a.eleType == "station")
                  _this.setAllObjectSelectable(false)
                  let listSel = []
                  const curIndex = list.findIndex((a) => a.data.stationID == station.stationID)
                  if (curIndex > -1) {
                     console.log("select_station", curIndex, list[curIndex])
                     listSel.push(list[curIndex])
                     list[curIndex].set({
                        selectable: true
                     })
                     _this.canvas.discardActiveObject();
                     // let sel = new fabric.ActiveSelection(listSel, {
                     //    canvas: _this.canvas,
                     // });
                     _this.canvas.setActiveObject(list[curIndex]);
                  }
               } else if (item.method == "edit_station_pos") {
                  const station = item.param || undefined
                  if (!station) {
                     if (_this.editObject) {
                        _this.editObject.set({
                           lockMovementX: true,
                           lockMovementY: true,
                        })
                        _this.editObject = null
                     }
                     _this.closeOkCancelControl()
                     continue
                  }
                  let list = _this.canvas.getObjects() || []
@@ -1387,9 +3421,187 @@
                     }
                     list[curIndex].set({
                        selectable: true,
                        lockEdit: false
                        lockEdit: false,
                        lockMovementX: false,
                        lockMovementY: false,
                     })
                     _this.editObject.tipObj.set({
                        left: _this.editObject.left,
                        top: _this.editObject.top - _this.editObject.height / 2 - _this
                           .editObject
                           .tipObj.height / 2,
                        visible: false
                     })
                     _this.createOkCancelControl(_this.editObject)
                  }
               } else if (item.method == "mark_station") {
                  const stationIdList = item.param || []
                  let list2 = _this.canvas.getObjects() || []
                  let list = list2.filter((a) => a.eleType == "station")
                  const flag = stationIdList.length == 2
                  _this.showTeachingPath(_this.showTeachPathFlag ? true : false)
                  let objStation1
                  let objStation2
                  for (let i2 in list) {
                     const obj = list[i2]
                     const curIndex = stationIdList.findIndex((a) => a == obj.data.stationID)
                     if (curIndex > -1) {
                        await _this.setMarkStation(obj, true)
                        obj.set({
                           selectable: true,
                           opacity: 1
                        })
                        if (!objStation1)
                           objStation1 = obj
                        else
                           objStation2 = obj
                     } else {
                        await _this.setMarkStation(obj, false)
                        if (flag) {
                           obj.set({
                              selectable: false,
                              opacity: 0.5
                           })
                        } else {
                           obj.set({
                              selectable: true,
                              opacity: 1
                           })
                        }
                     }
                  }
                  list = list2.filter((a) => a.eleType == "station_teaching")
                  if (objStation1 && objStation2) {
                     const id =
                        `station_teaching_${objStation1.data.stationID}_${objStation2.data.stationID}`
                     const curIndex = list.findIndex((a) => a.id == id)
                     if (curIndex > -1) {
                        list[curIndex].set({
                           opacity: 1,
                           strokeDashArray: [3, 2],
                           strokeLineCap: 'butt',
                        })
                     }
                  }
               } else if (item.method == "station_teaching") {
                  let list = _this.canvas.getObjects() || []
                  list.forEach((obj) => {
                     if (obj.eleType == "public_teaching" || obj.eleType ==
                        "station_teaching") {
                        obj.set({
                           hasControls: false,
                           selectable: false,
                        })
                     } else if (obj.eleType == "station" || obj.eleType ==
                        "station_tip") {
                        obj.set({
                           selectable: true,
                           opacity: 1
                        })
                     } else {
                        obj.set({
                           opacity: 0.5
                        })
                     }
                  })
               } else if (item.method == "public_teaching") {
                  let list = _this.canvas.getObjects() || []
                  list.forEach((obj) => {
                     if (obj.eleType == "public_teaching" || obj.eleType ==
                        "station_teaching") {
                        obj.set({
                           hasControls: false,
                           selectable: false,
                        })
                     } else if (obj.eleType == "agv")
                        obj.set({
                           opacity: 1
                        })
                     else
                        obj.set({
                           opacity: 0.5
                        })
                  })
               } else if (item.method == "teaching_finish") {
                  if (this.curTeachingObj) {
                     this.canvas.remove(this.curTeachingObj)
                     this.curTeachingObj = null
                  }
                  let list = _this.canvas.getObjects() || []
                  for (let i2 in list) {
                     const obj = list[i2]
                     obj.set({
                        selectable: false,
                        opacity: 1
                     })
                     // if (obj.eleType == "station") {
                     //    await _this.setMarkStation(obj, false)
                     // }
                  }
                  _this.showTeachingPath(_this.showTeachPathFlag ? true : false)
               } else if (item.method == "public_teaching_path") {
                  let list = _this.canvas.getObjects() || []
                  list = list.filter((a) => a.eleType == "public_teaching")
                  for (let i2 in list) {
                     this.canvas.remove(list[i2])
                  }
                  const teachingPathList = item.param || []
                  for (let i2 in teachingPathList) {
                     const teachingPath = teachingPathList[i2]
                     const id = `public_teaching_${teachingPath.name}`
                     await this.addTeachingPath(teachingPath, id, "public_teaching")
                  }
               } else if (item.method == "station_teaching_path") {
                  let list = _this.canvas.getObjects() || []
                  list = list.filter((a) => a.eleType == "station_teaching")
                  for (let i2 in list) {
                     this.canvas.remove(list[i2])
                  }
                  const teachingPathList = item.param || []
                  for (let i2 in teachingPathList) {
                     const teachingPath = teachingPathList[i2]
                     const id = `station_teaching_${teachingPath.src_dst}`
                     await this.addTeachingPath(teachingPath, id, "station_teaching")
                  }
               } else if (item.method == "show_teaching_path") {
                  _this.showTeachPathFlag = item.param.show
                  _this.showTeachingPath(item.param.show)
               } else if (item.method == "remove_teaching_path") {
                  let list = _this.canvas.getObjects() || []
                  if (item.param.mode == "Public") {
                     list = list.filter((a) => a.eleType == "public_teaching")
                     const id = `public_teaching_${item.param.name}`
                     const curIndex = list.findIndex((a) => a.id == id)
                     if (curIndex > -1) {
                        const obj = list[curIndex]
                        console.log(item.method, curIndex, id, obj)
                        _this.canvas.remove(obj)
                     }
                  } else if (item.param.mode == "Stations") {
                     list = list.filter((a) => a.eleType == "station_teaching")
                     const id = `station_teaching_${item.param.src_dst}`
                     const curIndex = list.findIndex((a) => a.id == id)
                     if (curIndex > -1) {
                        const obj = list[curIndex]
                        this.canvas.remove(obj)
                     }
                  }
               } else if (item.method == "edit_teaching") {
                  const teachingMode = item.param
                  _this.showEditTeachingPath(teachingMode)
               } else if (item.method == "set_selectable") {
                  if (item.param)
@@ -1403,18 +3615,83 @@
                     })
                     _this.editObject = null
                  }
               }
               } else if (item.method == "add_wall") {
                  const wallList = item.param || []
                  for (let i2 in wallList) {
                     const wall = wallList[i2]
                     const obj = await _this.addVirtualWall(wall)
                  }
               } else if (item.method == "wall_list") {
                  const wallList = item.param || []
                  let list = _this.canvas.getObjects() || []
                  list = list.filter((a) => a.eleType == "virtual_wall")
                  for (let i2 in wallList) {
                     const wall = wallList[i2]
                     const curIndex = list.findIndex((a) => a.id == wall.id)
                     if (curIndex < 0) {
                        await _this.addVirtualWallShow(wall)
                     }
                  }
               } else if (item.method == "add_region") {
                  const regionList = item.param || []
                  for (let i2 in regionList) {
                     const region = regionList[i2]
                     const obj = await _this.addRegion(region)
                  }
               } else if (item.method == "region_list") {
                  const regionList = item.param || []
                  let list = _this.canvas.getObjects() || []
                  list = list.filter((a) => a.eleType == "region")
                  for (let i2 in regionList) {
                     const region = regionList[i2]
                     const curIndex = list.findIndex((a) => a.id == region.id)
                     if (curIndex < 0) {
                        await _this.addRegionShow(region)
                     }
                  }
               } else if (item.method == "remove_wall") {
                  const wallList = item.param || []
                  let list = _this.canvas.getObjects() || []
                  list = list.filter((a) => a.eleType == "virtual_wall")
                  for (let i2 in wallList) {
                     const wall = wallList[i2]
                     const curIndex = list.findIndex((a) => a.id == wall.id)
                     if (curIndex > -1) {
                        _this.removeVirtualWall(list[curIndex])
                     }
                  }
               } else if (item.method == "remove_region") {
                  const regionList = item.param || []
                  let list = _this.canvas.getObjects() || []
                  list = list.filter((a) => a.eleType == "region")
                  for (let i2 in regionList) {
                     const region = regionList[i2]
                     const curIndex = list.findIndex((a) => a.id == region.id)
                     if (curIndex > -1) {
                        _this.removeRegion(list[curIndex])
                     }
                  }
               } else if (item.method == "agv_laser") {
                  this.updateAgvLaser(item.param || {})
               } else if (item.method == "point_cloud") {
                  this.updateLaserPoint(item.param || {})
               }
            }
            _this.canvas.renderAll()
         } catch (ex) {
            console.log(ex)
            this.showError(ex)
         }
      },
      showError(ex) {
         const type = typeof ex
         if (type == "string") {
            let tip = ex
            console.log(ex)
            plus.nativeUI.alert(tip, undefined, "错误");
            return
         }
@@ -1423,8 +3700,28 @@
            exStr = ex
         let tip = typeof ex.msg == "string" ? ex.msg : exStr
         console.log(tip)
         plus.nativeUI.alert(tip, undefined, "错误");
      },
      showToast(ex) {
         const type = typeof ex
         if (type == "string") {
            let tip = ex
            console.log(ex)
            plus.nativeUI.toast(tip); // undefined, "错误"
            return
         }
         let exStr = JSON.stringify(ex)
         if (exStr == "{}")
            exStr = ex
         let tip = typeof ex.msg == "string" ? ex.msg : exStr
         console.log(tip)
         plus.nativeUI.toast(tip, {
            duration: 'long',
            verticalAlign: "center"
         }); // undefined, "错误"
      },
   },
}