import * as fabric from "fabric" //'../../../components/fabric'; import { v4 as uuidv4 } from 'uuid'; import { Base64 } from '../../../comm/base64.js'; // import okIcon from '../../../static/images/confirm.svg'; // import cancelIcon from '../../../static/images/remove.svg'; export default { data() { return { vehicleIp: "", canvasId: "", canvas: null, eleWidth: 0, eleHeight: 0, touchStartPoint: { x: 0, y: 0 }, isDrawing: false, drawType: "", drawSvg: "", initialDistance: null, initialZoom: 1, isDragging: false, lastPosX: 0, lastPosY: 0, agvObj: null, initFlag: false, editMode: false, } }, mounted() { console.log("ctx mounted") this.init() }, methods: { async init(ip) { try { this.agvObj = null const _this = this fabric.Object.prototype.setControlsVisibility({ mt: false, // 中间上 mb: false, // 中间下 ml: false, // 中间左 mr: false, // 中间右 mtr: false, // 旋转控制 tl: false, //是否显示中间横杠 bl: false, //是否显示中间横杠 tr: false, //是否显示中间横杠 br: false, //是否显示中间横杠 }) // 全局样式调整 fabric.Object.prototype.set({ borderColor: '#1890FF', cornerColor: '#fff', cornerStrokeColor: '#1890FF', cornerSize: 36, // touchCornerSize: 48, transparentCorners: false, cornerStyle: 'circle', borderScaleFactor: 2, padding: 10, }); this.canvasId = `canvas_${uuidv4()}` const cantainerEl = document.getElementById("canvasMap") let canvas = document.getElementById(this.canvasId) canvas = document.createElement("canvas") canvas.setAttribute("id", this.canvasId) canvas.setAttribute("type", "2d") cantainerEl.appendChild(canvas) this.canvas = new fabric.Canvas(this.canvasId, { allowTouchScrolling: true, // 允许触摸滚动 selection: true, isTouchDevice: true, // 关键配置 stopContextMenu: true, // 禁止长按菜单 fireRightClick: true, fireMiddleClick: true, targetFindTolerance: 15, // 增大触摸容差 isTouchSupported: true }) this.canvas.clear() this.eleWidth = cantainerEl.clientWidth this.eleHeight = cantainerEl.clientHeight this.canvas.setWidth(this.eleWidth); this.canvas.setHeight(this.eleHeight); this.workSpace = new fabric.Rect({ id: "workspace", eleType: "workspace", left: 0, top: 0, width: this.eleWidth, height: this.eleHeight, selectable: false, hasControls: false, fill: "#FFFFFF" }) this.canvas.add(this.workSpace) this.patchFabricForUniApp(this.canvas) this.initFlag = true await this.$nextTick(); this.canvasEventListener() } catch (ex) { this.showError(ex) } }, patchFabricForUniApp(canvas) { // 保存原始方法 const originalSetActiveObject = canvas.setActiveObject.bind(canvas); // 重写setActiveObject canvas.setActiveObject = function(object, e) { originalSetActiveObject(object, e); if (object) { object.canvas = this; this._activeObject = object; } }; // 确保对象初始化时设置正确的canvas引用 fabric.Object.prototype.initialize = function(options) { const originalInit = fabric.util.getOriginalMethod(this, 'initialize'); originalInit.call(this, options); if (this.canvas && this.canvas._activeObject === this) { this.canvas._activeObject = this; } 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", station: target.data, }); target.set({ lockEdit: true }) this.canvas.requestRenderAll() }, editOkObject(eventData, transform, x, y) { const target = transform.target; const canvas = target.canvas; this.$ownerInstance.callMethod('receiveRenderData', { method: "edit_finish", cmd: "ok", station: target.data, }); target.set({ lockEdit: true }) this.canvas.requestRenderAll() }, 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) => { 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 (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' // 指针样式 }); }, addObject(obj) { obj.set({ lockEdit: true }) this.canvas.add(obj) this.createOkCancelControl(obj) }, canvasEventListener() { const _this = this const cantainerEl = document.getElementById("canvasMap") _this.canvas.on('touch:gesture', function(e) { console.log("touch:gesture", e); // 处理手势操作 }); _this.canvas.on("selection:created", function(e) { const activeObj = _this.canvas.getActiveObject(); activeObj.set({ borderColor: '#1890ff', borderScaleFactor: 3, padding: 5, borderDashArray: [5, 5], //5px 实线和5px 间隔 }); _this.canvas.requestRenderAll(); _this.onSelectionChanage() //_this.selectionChangeCanvas(); }); _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, borderDashArray: [5, 5], //5px 实线和5px 间隔 }); _this.canvas.requestRenderAll(); _this.onSelectionChanage() }); _this.canvas.on("selection:cleared", function(e) { console.log("selection:cleared", e); //_this.selectionChangeCanvas(); }); _this.canvas.on("object:removed", function(e) { // console.log("object:removed", e.target); if (e.target) { e.target.isRemoved = true; } }); _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); }); var pressObjTimer cantainerEl.addEventListener('touchstart', function(e) { console.log('touchstart:', e); e.preventDefault(); // 阻止默认行为 _this.canvas.fire('touch:start', { e: e }); // _this.canvas._onMouseDown(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; 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) 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; _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 } }); } }, 3000); // } } } }); cantainerEl.addEventListener('touchmove', function(e) { // _this.canvas._onMouseMove(e); e.preventDefault(); // 阻止默认行为 // 处理移动 if (!_this.canvas.getActiveObject()) { if (e.touches.length === 1) { _this.handleSingleTouchMove(e.touches[0]); } else if (e.touches.length >= 2) { _this.handleMultiTouchMove(e.touches); } } 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); } } }); cantainerEl.addEventListener('touchend', function(e) { // _this.canvas._onMouseUp(e); e.preventDefault(); // 阻止默认行为 _this.touchPoint = { x: 0, y: 0 }; if (!_this.canvas.getActiveObject()) { // 处理结束事件 if (e.touches.length === 0) { _this.handleTouchEnd(); } if (_this.editObject) { _this.canvas.setActiveObject(_this.editObject) } } else { clearTimeout(pressObjTimer); } }); cantainerEl.addEventListener('touchcancel', function(e) { if (_this.canvas.getActiveObject()) { // 处理结束事件 clearTimeout(pressObjTimer); } else { if (_this.editObject) { _this.canvas.setActiveObject(_this.editObject) } } }) }, 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) return new Promise((resolve) => { const img = new Image(); img.onload = () => { // 检查尺寸是否超出限制 const scale = Math.min( maxSize / Math.max(img.width, img.height), 1 ); resolve(new fabric.Image(img, { scaleX: scale, scaleY: scale })); }; img.onerror = () => { console.error('图片加载失败'); resolve(null); }; img.src = url; }); }, safeLoadImageData(data, maxSize = 2048) { //console.log("safeLoadImageData") const _this = this return new Promise((resolve) => { let base64Image = data if (base64Image.indexOf("data:image/png;base64,") < 0) { base64Image = "data:image/png;base64," + data } // var img = new fabric.Image(); // img.setSrc(base64Image, function() { // console.log("img", JSON.stringify(img)) // const scale = Math.min( // maxSize / Math.max(img.width, img.height), // 1 // ); // img.set({ // scaleX: scale, // scaleY: scale // }) // resolve(img) // }); fabric.Image.fromURL(base64Image, { crossOrigin: 'anonymous' // 重要:设置跨域 }).then((img) => { //console.log("img",JSON.stringify(img)) const scale = Math.min( maxSize / Math.max(img.width, img.height), 1 ); img.set({ scaleX: scale, scaleY: scale, }) resolve(img) }).catch((err) => { console.error("图片加载失败", err) _this.showError("图片加载失败") resolve(null); }) }); }, async loadBase64ImageWithProgress(data, maxSize = 2048) { const _this = this return new Promise((resolve, reject) => { // 创建临时Image对象监控加载进度 var base64Image = data if (base64Image.indexOf("data:image/png;base64,") < 0) { base64Image = "data:image/png;base64," + data } _this.$ownerInstance.callMethod('receiveRenderData', { method: "set_backgroud_progress", type: "start", }); const img = new Image(); img.onloadstart = () => { _this.$ownerInstance.callMethod('receiveRenderData', { method: "set_backgroud_progress", type: "progress", percent: 5 }); }; img.onprogress = (e) => { if (e.lengthComputable) { const percent = Math.min(99, Math.round((e.loaded / e.total) * 100)); _this.$ownerInstance.callMethod('receiveRenderData', { method: "set_backgroud_progress", type: "progress", percent }); } else { // 无法计算进度时的模拟进度 const percent = Math.min(this.progressPercent + 10, 95); _this.$ownerInstance.callMethod('receiveRenderData', { method: "set_backgroud_progress", type: "progress", percent }); } }; img.onload = () => { const percent = Math.min(this.progressPercent + 10, 95); _this.$ownerInstance.callMethod('receiveRenderData', { method: "set_backgroud_progress", type: "progress", percent: 100 }); // 转换为Fabric图像 const scale = Math.min( maxSize / Math.max(img.width, img.height), 1 ); const fabricImg = new fabric.Image(img, { left: 0, top: 0, scaleX: scale, scaleY: scale, }); _this.$ownerInstance.callMethod('receiveRenderData', { method: "set_backgroud_progress", type: "end", }); resolve(fabricImg); }; img.onerror = (err) => { _this.$ownerInstance.callMethod('receiveRenderData', { method: "set_backgroud_progress", type: "error", }); console.error("图片加载失败", err) reject(new Error('图片加载失败')); }; // 开始加载 img.src = base64Image; }); }, // 监控内存使用情况 checkMemoryUsage() { if (window.performance && window.performance.memory) { const usedMB = window.performance.memory.usedJSHeapSize / (1024 * 1024); console.log(`内存使用: ${usedMB.toFixed(2)}MB`); return usedMB; } return null; }, setBackground(info) { const _this = this if (!this.canvas) return 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 this.eleHeight = cantainerEl.clientHeight console.log("client", this.eleWidth, this.eleHeight) this.canvas.setWidth(this.eleWidth); this.canvas.setHeight(this.eleHeight); //console.log("setBackground", JSON.stringify(info)) 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) { 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.checkMemoryUsage() resolve() }).catch((err) => { resolve() }) } else { 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 if (!this.workSpace) return 1 const width = this.workSpace.width const height = this.workSpace.height if (eleWidth / eleHeight < width / height) { return eleWidth / width; } // 按照宽度缩放 return eleHeight / height; }, auto() { const scale = this.getAutoScale() this.setZoomAuto((98 * scale) / 100); }, one() { this.setZoomAuto(1, ); // this.canvas.requestRenderAll(); }, setZoomAuto(scale, zoomPoint) { const _this = this let center = this.canvas.getCenter(); if (zoomPoint) { this.canvas.zoomToPoint(new fabric.Point(zoomPoint.x, zoomPoint.y), scale); } else { this.canvas.zoomToPoint(new fabric.Point(center.left, center.top), scale); } this.canvas.fire("transform", { scale: scale }); if (!this.workSpace) return; this.setCenterFromObject(this.workSpace); // 超出画布不展示 _this.workSpace.clone().then((cloned) => { _this.canvas.clipPath = cloned; _this.canvas.requestRenderAll(); }); }, setDrawingType(type, svg) { if (svg) { if (type == "svg") { this.drawType = "svg" this.drawSvg = svg return } } if (type == "line") { this.drawType = "line" } else { this.drawType = "" } }, deleteSelected() { const list = this.canvas.getActiveObjects() list.map((item) => this.canvas.remove(item)); this.canvas.requestRenderAll(); this.canvas.discardActiveObject(); this.canvas.fire("object:modified"); }, /** * 设置画布中心到指定对象中心点上 * @param {Object} obj 指定的对象 */ setCenterFromObject(obj) { const objCenter = obj.getCenterPoint(); const viewportTransform = this.canvas.viewportTransform; if (this.canvas.width === undefined || this.canvas.height === undefined || !viewportTransform) return; viewportTransform[4] = this.canvas.width / 2 - objCenter.x * viewportTransform[0]; viewportTransform[5] = this.canvas.height / 2 - objCenter.y * viewportTransform[3]; this.canvas.setViewportTransform(viewportTransform); this.canvas.renderAll(); }, handleSingleTouch(touch) { this.touchStartPoint = { x: touch.clientX, y: touch.clientY }; console.log('单点触摸开始'); let activeObj = this.canvas.getActiveObject(); if (!activeObj) { if (!this.drawType) { this.isDrawing = false; this.isDragging = true; this.lastPosX = touch.clientX; this.lastPosY = touch.clientY; } else { this.isDragging = false; this.isDrawing = true; } } // 单点触摸逻辑 }, handleMultiTouch(touches) { const _this = this const touch1 = touches[0]; const touch2 = touches[1]; this.initialDistance = Math.sqrt( Math.pow(touch2.clientX - touch1.clientX, 2) + Math.pow(touch2.clientY - touch1.clientY, 2) ); this.initialZoom = this.canvas.getZoom(); // 多点触摸初始逻辑 }, handleSingleTouchMove(touch) { console.log('单点移动', touch.clientX, touch.clientY); if (this.isDragging) { const deltaX = touch.clientX - this.lastPosX; const deltaY = touch.clientY - this.lastPosY; // 移动视口 console.log('relativePan', deltaX, deltaY); this.canvas.relativePan(new fabric.Point(deltaX, deltaY)); this.lastPosX = touch.clientX; this.lastPosY = touch.clientY; } else if (this.isDrawing) { const vpt = this.canvas.viewportTransform; 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] let endY = touch.clientY - vpt[5] const scale = this.canvas.getZoom(); startX /= scale startY /= scale endX /= scale endY /= scale console.log("viewportTransform", startX, startY, endX, endY) const left = endX > startX ? startX : endX; const top = endY > startY ? startY : endY; let width = Math.abs(endX - startX) let height = Math.abs(endY - startY) if (this.drawType == "rect") { if (this.drawingObj) { this.drawingObj.set({ left, top, width, height }) this.drawingObj.setCoords() } else { const rect = new fabric.Rect({ id: `${new Date().getTime()}`, eleType: "rect", left, top, width, height, stroke: "#333", strokeWidth: 1, fill: "#fff", lockEdit: true }); this.canvas.add(rect); this.drawingObj = rect } this.canvas.requestRenderAll(); } else if (this.drawType == "ellipse") { if (this.drawingObj) { this.drawingObj.set({ left, top, rx: width / 2, ry: height / 2, }) this.drawingObj.setCoords() } else { const ellipse = new fabric.Ellipse({ id: `${new Date().getTime()}`, eleType: "ellipse", left, top, rx: width / 2, ry: height / 2, stroke: "#333", strokeWidth: 1, fill: "#fff", lockEdit: true }); this.canvas.add(ellipse); this.drawingObj = ellipse } this.canvas.requestRenderAll(); } else if (this.drawType == "line") { if (this.drawingObj) { this.drawingObj.set({ left, top, x1: startX, y1: startY, x2: endX, y2: endY, }) this.drawingObj.setCoords() } else { const line = new fabric.Line([startX, startY, endX, endY ], { id: `${new Date().getTime()}`, eleType: "line", stroke: "#333", strokeWidth: 5, lockEdit: true }); this.canvas.add(line); this.drawingObj = line } this.canvas.requestRenderAll(); } else if (this.drawType == "arrow") { if (this.drawingObj) { this.setArrowPath(this.drawingObj, startX, startY, endX, endY); this.drawingObj.setCoords() } else { let path = this.getArrowPath( startX, startY, endX, endY, 30, 40 ) const line = new fabric.Path(path, { id: `${new Date().getTime()}`, eleType: "arrow", stroke: "#333", strokeWidth: 5, lockEdit: true }); this.canvas.add(line); this.drawingObj = line } this.canvas.requestRenderAll(); } else if (this.drawType == "svg") { if (this.drawingObj) { this.drawingObj.set({ left, top, width, height, }); this.drawingObj.setCoords() } else { const _this = this fabric.loadSVGFromURL(this.drawSvg).then(({ objects, options }) => { const objGroup = fabric.util.groupSVGElements(objects, options); objGroup.set({ id: `${new Date().getTime()}`, eleType: "svg", left, top, width, height, viewBoxWidth: options.viewBoxWidth || options.width, viewBoxHeight: options.viewBoxHeight || options.height, lockEdit: true }); _this.canvas.add(objGroup) }); this.drawingObj = line } this.canvas.requestRenderAll(); } } // 单点移动逻辑 }, handleMultiTouchMove(touches) { // 计算两点间距离(缩放) if (!this.initialDistance) { return } const _this = this const touch1 = touches[0]; const touch2 = touches[1]; console.log('多点移动', touch1.identifier, touch2.identifier); const distance = Math.sqrt( Math.pow(touch2.clientX - touch1.clientX, 2) + Math.pow(touch2.clientY - touch1.clientY, 2) ); // 计算旋转角度 const angle = Math.atan2( touch2.clientY - touch1.clientY, touch2.clientX - touch1.clientX ) * 180 / Math.PI; // 计算中心点 const center = { x: (touch1.clientX + touch2.clientX) / 2, y: (touch1.clientY + touch2.clientY) / 2 }; // 计算缩放比例 (限制在0.1-10倍之间) let scale = Math.min(Math.max( this.initialZoom * (distance / this.initialDistance), 0.1), 10); let scaleAuto = this.getAutoScale() if (scale < scaleAuto) { scale = scaleAuto } else if (scale == scaleAuto) { return } console.log(scale, scaleAuto) this.setZoomAuto(scale, center) console.log('多点移动 - 距离:', distance, '角度:', angle); // 多点移动逻辑 }, handleTouchEnd() { console.log('所有触摸结束'); this.isDrawing = false this.drawingObj = undefined this.initialDistance = null; // 触摸结束逻辑 }, 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) fabric.loadSVGFromURL(svg).then( ({ objects, options }) => { const obj = fabric.util.groupSVGElements(objects, options); obj.set({ id: `station_${new Date().getTime()}`, eleType: "station", left, top, angle, data: info, originX: "center", originY: "center", hasControls: this.editMode, lockRotation: true, lockScalingX: true, lockScalingY: true, lockMovementX: true, lockMovementY: true, canSelect: true, lockEdit: true }); _this.addObject(obj) resolve() } ) }) }, 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 if (this.agvObj) { this.agvObj.set({ left, top, angle, data: info }); this.agvObj.setCoords() _this.canvas.requestRenderAll(); resolve() } else { const zoom = _this.canvas.getZoom(); fabric.loadSVGFromURL("static/images/van.svg").then( ({ objects, options }) => { const obj = fabric.util.groupSVGElements(objects, options); obj.set({ id: "agv", eleType: "agv", left, top, angle, data: info, originX: "center", originY: "center", // scale: 1 / zoom, hasControls: false, lockRotation: true, lockScalingX: true, lockScalingY: true, lockMovementX: true, lockMovementY: true, selectable: false, lockEdit: true }); console.log("agv", JSON.stringify(info)) _this.canvas.add(obj) _this.agvObj = obj resolve() } ) } }) }, setAllObjectSelectable(selectable) { let flag = false this.canvas.forEachObject(function(obj) { if (obj.canSelect) { if (!obj.flag) { flag = true } obj.set({ selectable: selectable, lockEdit: true }) } }); if (flag) { this.canvas.requestRenderAll() } }, receiveMsg(newValue, oldValue) { if (typeof newValue == "undefined") return; const _this = this //console.log("receiveMsg",_this.initFlag) setTimeout(() => { if (_this.initFlag) { _this.handleMsg(newValue, oldValue) } else { _this.receiveMsg(newValue, oldValue) } }, 500) }, async handleMsg(newValue, oldValue) { const _this = this try { var data = JSON.parse(newValue); for (var i = 0; i < data.length; i++) { const item = data[i] if (item.method == "init") { if (item.param?.editMode) { _this.editMode = true } else { _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 == "move_canvas") { const info = item.param || {} // const scale = this.getAutoScale() const zoom = this.canvas.getZoom(); const eleHeight = this.eleHeight - 150 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 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) if (curIndex < 0) { await _this.addStation(station) } } } else if (item.method == "update_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) 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 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) { curStationObj.set({ left, top, angle, data: station }) curStationObj.setCoords() } } } } 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) if (curIndex > -1) { _this.canvas.remove(list[curIndex]) } } } 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 } continue } let list = _this.canvas.getObjects() || [] list = list.filter((a) => a.eleType == "station") _this.setAllObjectSelectable(false) const curIndex = list.findIndex((a) => a.data.stationID == station.stationID) if (curIndex > -1) { console.log("edit_station_pos", curIndex, list[curIndex]) if (_this.editObject != list[curIndex]) { if (_this.editObject) { _this.editObject.set({ lockMovementX: true, lockMovementY: true, }) } _this.editObject = list[curIndex] _this.editObject.set({ lockMovementX: false, lockMovementY: false, }) } list[curIndex].set({ selectable: true, lockEdit: false }) } } else if (item.method == "set_selectable") { if (item.param) _this.setAllObjectSelectable(true) else _this.setAllObjectSelectable(false) if (_this.editObject) { _this.editObject.set({ lockMovementX: true, lockMovementY: true, }) _this.editObject = null } } } _this.canvas.renderAll() } catch (ex) { console.log(ex) } }, showError(ex) { const type = typeof ex if (type == "string") { let tip = ex plus.nativeUI.alert(tip, undefined, "错误"); return } let exStr = JSON.stringify(ex) if (exStr == "{}") exStr = ex let tip = typeof ex.msg == "string" ? ex.msg : exStr plus.nativeUI.alert(tip, undefined, "错误"); }, }, }