import { ref } from 'vue' import { fabric } from 'fabric-with-all' import { createId } from '../../tools/tools' import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod' import { OperationType } from '../tools/layerHelper' import { getArrowPath, cloneObjects, getStarArr } from '../tools/canvasMethod' export class LayerManager { stateManager: any canvasManager: any layers: any activeID: any constructor(options) { this.stateManager = options.stateManager; this.canvasManager = options.canvasManager; this.layers = ref([]) this.activeID = ref("") } onMounted() { } setActiveID(id: string, isActive = true) { this.activeID.value = id if (isActive) { this.canvasManager.setActiveObjectById(id) this.stateManager.toolManager.setTool(OperationType.SELECT) } } getActiveLayer() { return this.getLayerById(this.activeID.value) } getLayerById(id) { return this.layers.value.find((item: any) => item.info.id === id) } setLayerNameById(id, name: string) { const layer = this.getLayerById(id) if (layer) { layer.info.name = name } const object = this.canvasManager.getObjectById(id) if (object) { object.info.name = name this.canvasManager.renderAll() } } /** 设置图层显示状态 */ setLayerVisibleById(id, visible: boolean) { const layer = this.getLayerById(id) const object = this.canvasManager.getObjectById(id) if (!layer || !object) return layer.visible = visible object.set({ visible: visible }) this.canvasManager.renderAll() this.stateManager.recordState() } /** 设置图层锁定状态 */ setLayerLockById(id, lock: boolean) { const layer = this.getLayerById(id) const object = this.canvasManager.getObjectById(id) if (!layer || !object) return layer.info.lock = !!lock layer.evented = !lock object.info.lock = !!lock object.set({ evented: !lock, selectable: !lock, }) if (lock) { // 取消选中对象 const e = this.canvasManager.getSelectedObject() if (e) { const objects = [...(e._objects || [e])] if (objects.some(v => v.info?.id === object.info.id)) { this.canvasManager.discardActiveObject() } } } this.canvasManager.renderAll() this.stateManager.recordState() } /** 删除指定图层 */ deleteLayerById(id, isActive = true) { this.canvasManager.deleteObjectById(id) if (id === this.activeID.value && isActive) { this.setActiveID(this.layers.value[0]?.info?.id || "") } if (isActive) this.stateManager.recordState() } /** 复制指定图层 */ copyLayerById(id) { const object = this.canvasManager.getObjectById(id) if (!object) return console.warn('复制图层失败,对象不存在ID:', id) cloneObjects([object]).then(objects => { const newObject = objects[0] const info = JSON.parse(JSON.stringify(newObject.info)) info.id = createId("image") // info.name = info.name newObject.set({ top: newObject.top + 15, left: newObject.left + 15, info: info, }) this.canvasManager.add(newObject) this.setActiveID(newObject.info.id) }) } // 拖拽排序 dragSort(id, newIndex) { const index = Math.abs(this.layers.value.length - newIndex - 1) this.canvasManager.dragSort(id, index) } // 更新图层列表 updateLayers() { this.layers.value = this.canvasManager.getObjects() .filter((v: any) => !!v?.info?.id) .reverse() .map(v => v.toObject()) } /** 设置图层位置-不设置默认居中 */ setLayerPosition(object, options?: any) { const width = this.canvasManager.canvas.clipPath.width const height = this.canvasManager.canvas.clipPath.height if (options && options.top !== undefined) { object.set({ top: options.top }) } else { object.set({ top: height / 2 - object.height * object.scaleY / 2 }) } if (options && options.left !== undefined) { object.set({ left: options.left }) } else { object.set({ left: width / 2 - object.width * object.scaleX / 2 }) } } /** 创建空图层 */ createEmptyLayer(isRecord = true, isActive = false) { const emptyObject = new fabric.Rect({ width: 0, height: 0, fill: 'transparent', info: { id: createId("image"), name: '空图层', } }) this.setLayerPosition(emptyObject) this.canvasManager.add(emptyObject, isRecord) if (isActive) this.setActiveID(emptyObject.info.id, false) return emptyObject } /** 创建组图层 */ createGroupLayer(options?: any, isRecord = true, isActive = false) { const child = options?.child || [] delete options.child const groupObject = new fabric.Group(child, { subTargetCheck: true, // 关键:检测子对象 interactive: true, // 启用交互 hasControls: true, hasBorders: true, // // 子对象样式 // cornerColor: 'blue', // cornerSize: 8, // borderColor: 'green', // // 允许子对象独立变换 // lockScalingX: false, // lockScalingY: false, // lockRotation: false, info: { id: createId("group"), name: '组图层', ...(options?.info || {}), } }) // this.setLayerPosition(groupObject) this.canvasManager.add(groupObject, isRecord) if (isActive) this.setActiveID(groupObject.info.id, false) return groupObject } /** 创建文本图层 */ async createTextLayer(text: string, options?: any) { const textObject = new fabric.IText(text, { fontSize: 24, fill: '#000', ...(options || {}), info: { id: createId("text"), name: '文本图层', ...(options?.info || {}), } }) this.setLayerPosition(textObject, options) await this.canvasManager.add(textObject) return textObject } /** 创建矩形图层 */ async createRectLayer(options?: any, isActive = false) { const rectObject = new fabric.Rect({ width: 100, height: 100, fill: '#000', strokeWidth: 0, ...(options || {}), info: { id: createId("rect"), name: '矩形图层', ...(options?.info || {}), } }) this.setLayerPosition(rectObject, options) await this.canvasManager.add(rectObject) if (isActive) this.setActiveID(rectObject.info.id) return rectObject } /** 创建直线图层 */ async createLineLayer(options?: any, isActive = false) { const line = [options?.x1 || 0, options?.y1 || 0, options?.x2 || 100, options?.y2 || 0] delete options.x1 delete options.y1 delete options.x2 delete options.y2 const lineObject = new fabric.Line(line, { stroke: 'black', // 线条颜色 strokeWidth: 2, // 线条粗细 ...(options || {}), info: { id: createId("line"), name: '直线图层', ...(options?.info || {}), } }) this.setLayerPosition(lineObject, options) await this.canvasManager.add(lineObject) if (isActive) this.setActiveID(lineObject.info.id) return lineObject } /** 创建椭圆图层 */ async createEllipseLayer(options?: any, isActive = false) { const ellipseObject = new fabric.Ellipse({ radius: 50, fill: '#000', strokeWidth: 0, ...(options || {}), info: { id: createId("ellipse"), name: '椭圆图层', ...(options?.info || {}), } }) this.setLayerPosition(ellipseObject, options) await this.canvasManager.add(ellipseObject) if (isActive) this.setActiveID(ellipseObject.info.id) return ellipseObject } /** 创建三角形图层 */ async createTriangleLayer(options?: any, isActive = false) { const triangleObject = new fabric.Triangle({ width: 100, height: 100, fill: '#000', strokeWidth: 0, ...(options || {}), info: { id: createId("triangle"), name: '三角形图层', ...(options?.info || {}), } }) this.setLayerPosition(triangleObject, options) await this.canvasManager.add(triangleObject) if (isActive) this.setActiveID(triangleObject.info.id) return triangleObject } /** 创建五角星图层 */ async createStarLayer(options?: any, isActive = false) { const width = options?.width || 100 const height = options?.height || 100 delete options.points const starObject = new fabric.Polygon(getStarArr(width, height), { fill: '#000', strokeWidth: 0, ...(options || {}), info: { id: createId("star"), name: '五角星图层', ...(options?.info || {}), } }) this.setLayerPosition(starObject, options) await this.canvasManager.add(starObject) if (isActive) this.setActiveID(starObject.info.id) return starObject } /** 创建箭头图层 */ async createArrowLayer(options?: any, isActive = false) { const width = options?.width || 100 const height = options?.height || 10 delete options.width delete options.height const arrowObject = new fabric.Path(getArrowPath(width, height), { stroke: '#000', // 只设置边框颜色 strokeWidth: 3, // 边框宽度 fill: 'transparent', // 不填充 strokeLineCap: 'round', strokeLineJoin: 'round', ...(options || {}), info: { id: createId("star"), name: '箭头图层', ...(options?.info || {}), } }); this.setLayerPosition(arrowObject, options) this.canvasManager.add(arrowObject) if (isActive) this.setActiveID(arrowObject.info.id) return arrowObject } /** 创建图片图层 */ async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true) { const { canvasWidth, canvasHeight } = this.canvasManager.getCanvasSize(); const imageObject = await new Promise((resolve) => { const url = typeof imgOrUrl === 'string' ? imgOrUrl : imgOrUrl.src fabric.Image.fromURL(url, (img) => { const width = img.width const height = img.height const scaleX = width > canvasWidth ? canvasWidth * 0.8 / width : 1 const scaleY = height > canvasHeight ? canvasHeight * 0.8 / height : 1 const scale = Math.min(scaleX, scaleY) img.set({ scaleX: scale, scaleY: scale, ...(options || {}), info: { id: createId("image"), name: "图片图层", ...(options?.info || {}), } }) resolve(img) }) }) as fabric.Object this.setLayerPosition(imageObject, options) await this.canvasManager.add(imageObject, isRecord) this.setActiveID(imageObject.info.id) return imageObject } /** 合并图层 */ async imageMergeToLayer(targetLayer: fabric.Object, fabricImage: fabric.Object) { const info = await exportObjectsToImage([targetLayer, fabricImage], true) const mergedImage = await new Promise((resolve) => { fabric.Image.fromURL(info.url, (img) => { img.set({ left: info.left, top: info.top, info: { id: createId("image"), name: targetLayer?.info?.name || "合并图层", } }) resolve(img) }) }) // console.log(mergedImage) const index = this.canvasManager.getObjects().indexOf(targetLayer); this.deleteLayerById(targetLayer.info.id, false) this.setActiveID(mergedImage.info.id, false) await this.canvasManager.add(mergedImage, false); this.canvasManager.canvas.moveTo(mergedImage, index); this.canvasManager.renderAll() this.updateLayers() this.stateManager.recordState() return true; } /** 设置激活对象可擦除 */ setActiveObjectErasable() { const objects = this.canvasManager.getObjects() objects.forEach((item: any) => { item.set({ erasable: item.info.id === this.activeID.value }) }) } /** 更新图层缩略图 */ async updateLayerThumbnailsById(id: string) { const object = this.canvasManager.getObjectById(id); if (!object) return; const url = await exportObjectToThumbnail(object); object.thumbnail = url this.updateLayers() } dispose() { } }