371 lines
10 KiB
TypeScript
371 lines
10 KiB
TypeScript
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
|
||
}
|
||
/** 创建文本图层 */
|
||
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);
|
||
this.canvasManager.canvas.moveTo(mergedImage, index);
|
||
this.canvasManager.renderAll()
|
||
this.updateLayers()
|
||
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() { }
|
||
} |