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, "错误");
|
},
|
|
},
|
}
|