import { ref } from 'vue' import { fabric } from 'fabric-with-all' import { createId } from '../../tools/tools' import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod' import { OperationType, BlendMode } 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) { const layer = this.getLayerById(id) if (layer?.type === "group") { this.activeID.value = "" } else { 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) { function call(arr) { for (let i = 0; i < arr.length; i++) { let v = arr[i] if (v.info.id === id) return v if (v.children) { let layer = call(v.children) if (layer) return layer } } return null } return call(this.layers.value) } 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) { const layer = this.getLayerById(id) if (layer.children) { layer.children.forEach(v => this.canvasManager.deleteObjectById(v.info.id, false)) } 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) }) } /** 根据layers排序图层 */ async sortLayers(isRecord?: boolean) { const ids = []; call(this.layers.value) await this.canvasManager.sortObjectByIds(ids.reverse(), isRecord) function call(arr) { arr.forEach(v => { ids.push(v.info.id) if (v.children) call(v.children) }) } } // 更新图层列表 async updateLayers(isSort = false) { const objects = this.canvasManager.getObjects().map(v => v.toObject()).filter(v => !!v.info?.id).reverse() objects.forEach(v => { if (v.type === "group") { if (!v.children) v.children = [] return; } const parentId = v.info?.parentId if (!parentId) return objects.forEach((obj: any) => { if (obj.info?.id !== parentId) return if (!obj.children) obj.children = [] obj.children.push(v) }) }) const layers = objects.filter(v => !v.info?.parentId) this.layers.value = layers if (isSort) await this.sortLayers() } /** 设置图层位置-不设置默认居中 */ 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 }) } } /** 创建空图层 */ async createEmptyLayer(isRecord = true, isActive = false) { const emptyObject = new fabric.Rect({ width: 0, height: 0, fill: 'transparent', info: { id: createId("image"), name: '空图层', } }) this.setLayerPosition(emptyObject) await this.canvasManager.add(emptyObject, isRecord) if (isActive) this.setActiveID(emptyObject.info.id, false) return emptyObject } /** 创建组图层 */ async createGroupLayer(options?: any, isRecord = true, isActive = false) { const children = options?.children || [] delete options.children const groupObject = new fabric.Group(children, { ...(options || {}), hasControls: false, // 不显示控制点 hasBorders: false, // 不显示边框 selectable: false, // 不可选中(可选) info: { id: createId("group"), name: '智能选区组', showChildren: true, ...(options?.info || {}), } }) this.setLayerPosition(groupObject, options) await 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, isRecord = true, isActive = true) { 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, isRecord) if (isActive) this.setActiveID(rectObject.info.id) return rectObject } /** 创建直线图层 */ async createLineLayer(options?: any, isRecord = true, isActive = true) { 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, isRecord) if (isActive) this.setActiveID(lineObject.info.id) return lineObject } /** 创建椭圆图层 */ async createEllipseLayer(options?: any, isRecord = true, isActive = true) { const ellipseObject = new fabric.Ellipse({ 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, isRecord = true, isActive = true) { 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, isRecord) if (isActive) this.setActiveID(triangleObject.info.id) return triangleObject } /** 创建五角星图层 */ async createStarLayer(options?: any, isRecord = true, isActive = true) { 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, isRecord) if (isActive) this.setActiveID(starObject.info.id) return starObject } /** 创建箭头图层 */ async createArrowLayer(options?: any, isRecord = true, isActive = true) { 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) await this.canvasManager.add(arrowObject, isRecord) if (isActive) this.setActiveID(arrowObject.info.id) return arrowObject } /** 创建图片图层 */ async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true, isActive = 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) if (isActive) 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: { ...(targetLayer?.info || {}), id: createId("image"), name: targetLayer?.info?.name || "合并图层", } }) resolve(img) }, { crossOrigin: 'anonymous' }) }) const index = this.canvasManager.getObjects().indexOf(targetLayer); this.deleteLayerById(targetLayer.info.id, false) const nid = mergedImage.info.id await this.canvasManager.add(mergedImage, false); this.setActiveID(nid, false) this.canvasManager.canvas.moveTo(mergedImage, index); // this.stateManager.objectManager.setBlendMode(nid, BlendMode.MULTIPLY) // this.stateManager.objectManager.setFillRepeat(nid, false) 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, thumbnail?: string, isUpdate = true) { const object = this.canvasManager.getObjectById(id); if (!object) return; const url = thumbnail || await exportObjectToThumbnail(object); object.thumbnail = url if (isUpdate) this.updateLayers() } dispose() { } }