Files
FiDA_Front/src/components/Canvas/DepthCanvas/manager/LayerManager.ts

471 lines
13 KiB
TypeScript
Raw Normal View History

2026-03-09 16:45:30 +08:00
import { ref } from 'vue'
2026-03-11 15:34:56 +08:00
import { fabric } from 'fabric-with-all'
import { createId } from '../../tools/tools'
2026-03-13 11:18:36 +08:00
import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod'
2026-03-24 11:49:53 +08:00
import { OperationType, BlendMode } from '../tools/layerHelper'
2026-03-17 17:17:48 +08:00
import { getArrowPath, cloneObjects, getStarArr } from '../tools/canvasMethod'
2026-03-09 16:45:30 +08:00
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("")
}
2026-03-16 16:51:12 +08:00
onMounted() { }
2026-03-11 15:34:56 +08:00
setActiveID(id: string, isActive = true) {
2026-03-24 11:49:53 +08:00
const layer = this.getLayerById(id)
2026-03-25 11:27:24 +08:00
// if (layer?.type === "group") {
// this.activeID.value = ""
// } else {
this.activeID.value = id
// }
2026-03-11 15:34:56 +08:00
if (isActive) {
2026-03-13 11:18:36 +08:00
this.canvasManager.setActiveObjectById(id)
2026-03-12 15:51:18 +08:00
this.stateManager.toolManager.setTool(OperationType.SELECT)
2026-03-11 15:34:56 +08:00
}
}
2026-03-13 17:31:47 +08:00
getActiveLayer() {
return this.getLayerById(this.activeID.value)
}
2026-03-13 11:18:36 +08:00
getLayerById(id) {
2026-03-23 16:43:08 +08:00
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)
2026-03-09 16:45:30 +08:00
}
2026-03-13 11:18:36 +08:00
setLayerNameById(id, name: string) {
const layer = this.getLayerById(id)
2026-03-09 16:45:30 +08:00
if (layer) {
layer.info.name = name
2026-03-13 14:08:40 +08:00
}
const object = this.canvasManager.getObjectById(id)
if (object) {
object.info.name = name
2026-03-09 16:45:30 +08:00
this.canvasManager.renderAll()
}
}
2026-03-18 13:56:27 +08:00
/** 设置图层显示状态 */
2026-03-13 11:18:36 +08:00
setLayerVisibleById(id, visible: boolean) {
const layer = this.getLayerById(id)
2026-03-13 14:08:40 +08:00
const object = this.canvasManager.getObjectById(id)
2026-03-25 11:27:24 +08:00
const call = (layer, object) => {
if (!layer || !object) return
layer.visible = visible
object.set({
visible: visible
})
}
call(layer, object)
if (object.type === "group") {
const children = this.canvasManager.getChildObjectsById(id)
children.forEach(v => {
const layer = this.getLayerById(v.info.id)
call(layer, v)
})
}
2026-03-18 13:56:27 +08:00
this.canvasManager.renderAll()
this.stateManager.recordState()
}
/** 设置图层锁定状态 */
setLayerLockById(id, lock: boolean) {
const layer = this.getLayerById(id)
const object = this.canvasManager.getObjectById(id)
2026-03-25 11:27:24 +08:00
const call = (layer, object) => {
if (!layer || !object) return
layer.info.lock = !!lock
layer.evented = !lock
object.info.lock = !!lock
if (object.type !== "group") {
object.set({
evented: !lock,
selectable: !lock,
})
}
}
call(layer, object)
if (object.type === "group") {
const children = this.canvasManager.getChildObjectsById(id)
children.forEach(v => {
const layer = this.getLayerById(v.info.id)
call(layer, v)
})
}
2026-03-18 13:56:27 +08:00
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()
}
}
2026-03-09 16:45:30 +08:00
}
2026-03-18 13:56:27 +08:00
this.canvasManager.renderAll()
this.stateManager.recordState()
2026-03-09 16:45:30 +08:00
}
2026-03-18 13:56:27 +08:00
2026-03-17 17:17:48 +08:00
/** 删除指定图层 */
2026-03-13 11:18:36 +08:00
deleteLayerById(id, isActive = true) {
2026-03-23 16:43:08 +08:00
const layer = this.getLayerById(id)
if (layer.children) {
layer.children.forEach(v => this.canvasManager.deleteObjectById(v.info.id, false))
}
2026-03-09 16:45:30 +08:00
this.canvasManager.deleteObjectById(id)
2026-03-11 15:34:56 +08:00
if (id === this.activeID.value && isActive) {
this.setActiveID(this.layers.value[0]?.info?.id || "")
}
2026-03-13 14:08:40 +08:00
if (isActive) this.stateManager.recordState()
2026-03-09 16:45:30 +08:00
}
2026-03-17 17:17:48 +08:00
/** 复制指定图层 */
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)
})
}
2026-03-23 16:43:08 +08:00
/** 根据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)
})
}
2026-03-09 16:45:30 +08:00
}
2026-03-23 16:43:08 +08:00
2026-03-09 16:45:30 +08:00
// 更新图层列表
2026-03-23 16:43:08 +08:00
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()
2026-03-11 15:34:56 +08:00
}
/** 设置图层位置-不设置默认居中 */
2026-03-13 14:08:40 +08:00
setLayerPosition(object, options?: any) {
2026-03-19 11:00:31 +08:00
const width = this.canvasManager.canvas.clipPath.width
const height = this.canvasManager.canvas.clipPath.height
2026-03-11 15:34:56 +08:00
if (options && options.top !== undefined) {
2026-03-13 14:08:40 +08:00
object.set({ top: options.top })
2026-03-11 15:34:56 +08:00
} else {
2026-03-13 14:08:40 +08:00
object.set({ top: height / 2 - object.height * object.scaleY / 2 })
2026-03-11 15:34:56 +08:00
}
if (options && options.left !== undefined) {
2026-03-13 14:08:40 +08:00
object.set({ left: options.left })
2026-03-11 15:34:56 +08:00
} else {
2026-03-13 14:08:40 +08:00
object.set({ left: width / 2 - object.width * object.scaleX / 2 })
2026-03-11 15:34:56 +08:00
}
}
/** 创建空图层 */
2026-03-23 16:43:08 +08:00
async createEmptyLayer(isRecord = true, isActive = false) {
2026-03-13 14:08:40 +08:00
const emptyObject = new fabric.Rect({
2026-03-11 15:34:56 +08:00
width: 0,
height: 0,
fill: 'transparent',
info: {
id: createId("image"),
name: '空图层',
}
})
2026-03-13 14:08:40 +08:00
this.setLayerPosition(emptyObject)
2026-03-23 16:43:08 +08:00
await this.canvasManager.add(emptyObject, isRecord)
2026-03-17 17:17:48 +08:00
if (isActive) this.setActiveID(emptyObject.info.id, false)
2026-03-13 14:08:40 +08:00
return emptyObject
2026-03-11 15:34:56 +08:00
}
2026-03-20 13:23:00 +08:00
/** 创建组图层 */
2026-03-23 16:43:08 +08:00
async createGroupLayer(options?: any, isRecord = true, isActive = false) {
2026-03-25 11:27:24 +08:00
const groupObject = new fabric.Group([], {
2026-03-23 16:43:08 +08:00
...(options || {}),
2026-03-25 11:27:24 +08:00
width: 1,
height: 1,
2026-03-23 16:43:08 +08:00
hasControls: false, // 不显示控制点
hasBorders: false, // 不显示边框
selectable: false, // 不可选中(可选)
2026-03-25 11:27:24 +08:00
evented: false,
2026-03-20 13:23:00 +08:00
info: {
id: createId("group"),
2026-03-23 17:06:38 +08:00
name: '智能选区组',
2026-03-23 16:43:08 +08:00
showChildren: true,
2026-03-20 13:23:00 +08:00
...(options?.info || {}),
}
})
2026-03-23 16:43:08 +08:00
this.setLayerPosition(groupObject, options)
await this.canvasManager.add(groupObject, isRecord)
2026-03-20 13:23:00 +08:00
if (isActive) this.setActiveID(groupObject.info.id, false)
return groupObject
}
2026-03-11 15:34:56 +08:00
/** 创建文本图层 */
2026-03-13 14:08:40 +08:00
async createTextLayer(text: string, options?: any) {
const textObject = new fabric.IText(text, {
2026-03-11 15:34:56 +08:00
fontSize: 24,
fill: '#000',
...(options || {}),
info: {
id: createId("text"),
name: '文本图层',
...(options?.info || {}),
}
})
2026-03-13 14:08:40 +08:00
this.setLayerPosition(textObject, options)
await this.canvasManager.add(textObject)
return textObject
2026-03-11 15:34:56 +08:00
}
/** 创建矩形图层 */
2026-03-23 16:43:08 +08:00
async createRectLayer(options?: any, isRecord = true, isActive = true) {
2026-03-13 14:08:40 +08:00
const rectObject = new fabric.Rect({
2026-03-11 15:34:56 +08:00
width: 100,
height: 100,
fill: '#000',
2026-03-18 17:25:19 +08:00
strokeWidth: 0,
2026-03-11 15:34:56 +08:00
...(options || {}),
info: {
id: createId("rect"),
name: '矩形图层',
...(options?.info || {}),
}
})
2026-03-13 14:08:40 +08:00
this.setLayerPosition(rectObject, options)
2026-03-23 16:43:08 +08:00
await this.canvasManager.add(rectObject, isRecord)
2026-03-13 14:08:40 +08:00
if (isActive) this.setActiveID(rectObject.info.id)
return rectObject
2026-03-11 15:34:56 +08:00
}
2026-03-17 17:17:48 +08:00
/** 创建直线图层 */
2026-03-23 16:43:08 +08:00
async createLineLayer(options?: any, isRecord = true, isActive = true) {
2026-03-17 17:17:48 +08:00
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)
2026-03-23 16:43:08 +08:00
await this.canvasManager.add(lineObject, isRecord)
2026-03-17 17:17:48 +08:00
if (isActive) this.setActiveID(lineObject.info.id)
return lineObject
}
/** 创建椭圆图层 */
2026-03-23 16:43:08 +08:00
async createEllipseLayer(options?: any, isRecord = true, isActive = true) {
2026-03-17 17:17:48 +08:00
const ellipseObject = new fabric.Ellipse({
2026-03-11 15:34:56 +08:00
fill: '#000',
2026-03-18 17:25:19 +08:00
strokeWidth: 0,
2026-03-11 15:34:56 +08:00
...(options || {}),
info: {
2026-03-17 17:17:48 +08:00
id: createId("ellipse"),
name: '椭圆图层',
...(options?.info || {}),
}
})
this.setLayerPosition(ellipseObject, options)
await this.canvasManager.add(ellipseObject)
if (isActive) this.setActiveID(ellipseObject.info.id)
return ellipseObject
}
/** 创建三角形图层 */
2026-03-23 16:43:08 +08:00
async createTriangleLayer(options?: any, isRecord = true, isActive = true) {
2026-03-17 17:17:48 +08:00
const triangleObject = new fabric.Triangle({
width: 100,
height: 100,
fill: '#000',
2026-03-18 17:25:19 +08:00
strokeWidth: 0,
2026-03-17 17:17:48 +08:00
...(options || {}),
info: {
id: createId("triangle"),
name: '三角形图层',
...(options?.info || {}),
}
})
this.setLayerPosition(triangleObject, options)
2026-03-23 16:43:08 +08:00
await this.canvasManager.add(triangleObject, isRecord)
2026-03-17 17:17:48 +08:00
if (isActive) this.setActiveID(triangleObject.info.id)
return triangleObject
}
/** 创建五角星图层 */
2026-03-23 16:43:08 +08:00
async createStarLayer(options?: any, isRecord = true, isActive = true) {
2026-03-17 17:17:48 +08:00
const width = options?.width || 100
const height = options?.height || 100
delete options.points
const starObject = new fabric.Polygon(getStarArr(width, height), {
fill: '#000',
2026-03-18 17:25:19 +08:00
strokeWidth: 0,
2026-03-17 17:17:48 +08:00
...(options || {}),
info: {
id: createId("star"),
name: '五角星图层',
2026-03-11 15:34:56 +08:00
...(options?.info || {}),
}
})
2026-03-17 17:17:48 +08:00
this.setLayerPosition(starObject, options)
2026-03-23 16:43:08 +08:00
await this.canvasManager.add(starObject, isRecord)
2026-03-17 17:17:48 +08:00
if (isActive) this.setActiveID(starObject.info.id)
return starObject
2026-03-11 15:34:56 +08:00
}
2026-03-17 17:17:48 +08:00
/** 创建箭头图层 */
2026-03-23 16:43:08 +08:00
async createArrowLayer(options?: any, isRecord = true, isActive = true) {
2026-03-17 17:17:48 +08:00
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)
2026-03-23 16:43:08 +08:00
await this.canvasManager.add(arrowObject, isRecord)
2026-03-17 17:17:48 +08:00
if (isActive) this.setActiveID(arrowObject.info.id)
return arrowObject
}
2026-03-11 15:34:56 +08:00
/** 创建图片图层 */
2026-03-23 16:43:08 +08:00
async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true, isActive = true) {
2026-03-19 11:00:31 +08:00
const { canvasWidth, canvasHeight } = this.canvasManager.getCanvasSize();
2026-03-11 15:34:56 +08:00
2026-03-13 14:08:40 +08:00
const imageObject = await new Promise((resolve) => {
2026-03-11 15:34:56 +08:00
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
2026-03-13 14:08:40 +08:00
this.setLayerPosition(imageObject, options)
2026-03-16 16:51:12 +08:00
await this.canvasManager.add(imageObject, isRecord)
2026-03-23 16:43:08 +08:00
if (isActive) this.setActiveID(imageObject.info.id)
2026-03-13 14:08:40 +08:00
return imageObject
2026-03-11 15:34:56 +08:00
}
/** 合并图层 */
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: {
2026-03-23 16:43:08 +08:00
...(targetLayer?.info || {}),
2026-03-11 15:34:56 +08:00
id: createId("image"),
name: targetLayer?.info?.name || "合并图层",
}
})
resolve(img)
2026-03-20 13:59:53 +08:00
}, { crossOrigin: 'anonymous' })
2026-03-11 15:34:56 +08:00
})
const index = this.canvasManager.getObjects().indexOf(targetLayer);
2026-03-13 11:18:36 +08:00
this.deleteLayerById(targetLayer.info.id, false)
2026-03-24 11:49:53 +08:00
const nid = mergedImage.info.id
2026-03-19 11:32:23 +08:00
await this.canvasManager.add(mergedImage, false);
2026-03-24 11:49:53 +08:00
this.setActiveID(nid, false)
2026-03-11 15:34:56 +08:00
this.canvasManager.canvas.moveTo(mergedImage, index);
2026-03-24 11:49:53 +08:00
// this.stateManager.objectManager.setBlendMode(nid, BlendMode.MULTIPLY)
// this.stateManager.objectManager.setFillRepeat(nid, false)
2026-03-11 15:34:56 +08:00
this.canvasManager.renderAll()
this.updateLayers()
2026-03-19 11:32:23 +08:00
this.stateManager.recordState()
2026-03-11 15:34:56 +08:00
return true;
}
/** 设置激活对象可擦除 */
setActiveObjectErasable() {
const objects = this.canvasManager.getObjects()
objects.forEach((item: any) => {
item.set({
2026-03-25 11:27:24 +08:00
erasable: (item.info.id === this.activeID.value && item.type !== "group")
2026-03-11 15:34:56 +08:00
})
})
2026-03-09 16:45:30 +08:00
}
2026-03-25 11:27:24 +08:00
/** 设置所有对象擦除属性 */
setAllObjectsErasable(isErasable = false) {
const objects = this.canvasManager.getObjects()
objects.forEach((item: any) => {
item.set({ erasable: isErasable })
})
}
2026-03-13 11:18:36 +08:00
/** 更新图层缩略图 */
2026-03-23 16:43:08 +08:00
async updateLayerThumbnailsById(id: string, thumbnail?: string, isUpdate = true) {
2026-03-13 11:18:36 +08:00
const object = this.canvasManager.getObjectById(id);
if (!object) return;
2026-03-23 16:43:08 +08:00
const url = thumbnail || await exportObjectToThumbnail(object);
2026-03-13 14:08:40 +08:00
object.thumbnail = url
2026-03-23 16:43:08 +08:00
if (isUpdate) this.updateLayers()
2026-03-13 11:18:36 +08:00
}
2026-03-12 15:51:18 +08:00
dispose() { }
2026-03-11 15:34:56 +08:00
}