111
This commit is contained in:
@@ -51,7 +51,8 @@
|
||||
const activeObject = ref(null)
|
||||
const updateActiveObject = () => {
|
||||
const obj = layers.value.find((v: any) => v.info.id === activeID.value)
|
||||
activeObject.value = obj?.toJSON('info') || null
|
||||
// activeObject.value = obj?.toJSON('info') || null
|
||||
activeObject.value = obj
|
||||
}
|
||||
watch(layers, () => updateActiveObject())
|
||||
watch(activeID, () => updateActiveObject())
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="layer-item" @click="onClickLayer">
|
||||
<div class="drag"><svg-icon name="dc-drag" size="18" /></div>
|
||||
<div class="thumb"></div>
|
||||
<div class="thumb">
|
||||
<img v-if="layer.thumbnail" :src="layer.thumbnail" />
|
||||
</div>
|
||||
<div class="name">
|
||||
<div @dblclick="onClickEditName" v-if="!editName" :title="layer.info.name">
|
||||
{{ layer.info.name || '未命名图层' }}
|
||||
@@ -50,14 +52,14 @@
|
||||
editName.value = false
|
||||
const name = props.layer.info.name
|
||||
if (name !== value) {
|
||||
layerManager.setLayerNameByID(props.layer.info.id, value)
|
||||
layerManager.setLayerNameById(props.layer.info.id, value)
|
||||
}
|
||||
}
|
||||
const onClickShowHide = () => {
|
||||
layerManager.setLayerVisibleByID(props.layer.info.id, !props.layer.visible)
|
||||
layerManager.setLayerVisibleById(props.layer.info.id, !props.layer.visible)
|
||||
}
|
||||
const onClickDelete = () => {
|
||||
layerManager.deleteLayerByID(props.layer.info.id)
|
||||
layerManager.deleteLayerById(props.layer.info.id)
|
||||
}
|
||||
const onClickLayer = () => {
|
||||
layerManager.setActiveID(props.layer.info.id)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { fabric } from 'fabric-with-all'
|
||||
/** 智能框选工具管理器 */
|
||||
export class AISelectboxToolManager {
|
||||
// 管理器
|
||||
canvasManager: any
|
||||
stateManager: any
|
||||
layerManager: any
|
||||
|
||||
isDragging: boolean = false
|
||||
startX: number = 0
|
||||
startY: number = 0
|
||||
demoObject: any
|
||||
constructor(options) {
|
||||
this.canvasManager = options.canvasManager
|
||||
this.stateManager = options.stateManager
|
||||
this.layerManager = options.layerManager
|
||||
}
|
||||
mouseDownEvent(e) {
|
||||
this.isDragging = true
|
||||
this.startX = e.absolutePointer.x
|
||||
this.startY = e.absolutePointer.y
|
||||
const rect = new fabric.Rect({
|
||||
left: this.startX,
|
||||
top: this.startY,
|
||||
width: 0,
|
||||
height: 0,
|
||||
fill: 'transparent',
|
||||
stroke: '#000',
|
||||
strokeWidth: 1,
|
||||
evented: false,
|
||||
})
|
||||
this.demoObject = rect
|
||||
this.canvasManager.canvas.add(rect)
|
||||
this.canvasManager.canvas.renderAll()
|
||||
}
|
||||
mouseMoveEvent(e) {
|
||||
if (!this.isDragging) return;
|
||||
var width = e.absolutePointer.x - this.startX
|
||||
var height = e.absolutePointer.y - this.startY
|
||||
var left = this.startX
|
||||
var top = this.startY
|
||||
if(width < 0) {
|
||||
left += width
|
||||
width = -width
|
||||
}
|
||||
if(height < 0) {
|
||||
top += height
|
||||
height = -height
|
||||
}
|
||||
this.demoObject.set({ width, height, left, top })
|
||||
this.canvasManager.canvas.renderAll()
|
||||
}
|
||||
mouseUpEvent(e) {
|
||||
if (!this.isDragging) return;
|
||||
this.isDragging = false;
|
||||
const object = this.demoObject.toJSON("evented")
|
||||
if (object.width === 0) object.width = 100
|
||||
if (object.height === 0) object.height = 100
|
||||
// console.log(object)
|
||||
this.canvasManager.canvas.remove(this.demoObject)
|
||||
this.canvasManager.canvas.renderAll()
|
||||
|
||||
}
|
||||
dispose() { }
|
||||
}
|
||||
@@ -6,6 +6,22 @@ import { detectDeviceType } from '../tools/index'
|
||||
import { CanvasEventManager } from "./events/CanvasEventManager";
|
||||
import { OperationType } from '../tools/layerHelper'
|
||||
|
||||
// 自定义画布转对象属性
|
||||
fabric.Object.prototype.customProperties = ["top", "left", "width", "height", "scaleX", "scaleY", "info", "thumbnail"];
|
||||
fabric.Object.prototype.toObject_ = fabric.Object.prototype.toObject
|
||||
fabric.Object.prototype.toObject = function () {
|
||||
const args = [...arguments]
|
||||
const arr = [...fabric.Object.prototype.customProperties]
|
||||
args.forEach(v => {
|
||||
if (typeof v === 'string') {
|
||||
arr.push(v)
|
||||
} else if (Array.isArray(v)) {
|
||||
arr.push(...v)
|
||||
}
|
||||
})
|
||||
return this.toObject_(arr)
|
||||
}
|
||||
|
||||
interface CanvasInitOptions {
|
||||
canvasRef: any
|
||||
canvasViewWidth?: number
|
||||
@@ -43,8 +59,6 @@ export class CanvasManager {
|
||||
this.canvas = createCanvas(options.canvasRef.value, {
|
||||
preserveObjectStacking: true,
|
||||
enableRetinaScaling: true,
|
||||
stopContextMenu: true,
|
||||
fireRightClick: true,
|
||||
backgroundColor: '#fff',
|
||||
})
|
||||
this.setCanvasViewSize(options)
|
||||
@@ -118,7 +132,7 @@ export class CanvasManager {
|
||||
this.animationManager.setupInteractionAnimations();
|
||||
}
|
||||
/** 设置激活对象 */
|
||||
setActiveObjectByID(id: string) {
|
||||
setActiveObjectById(id: string) {
|
||||
const obj = this.getObjectById(id)
|
||||
console.log(obj)
|
||||
if (obj) this.canvas.setActiveObject(obj)
|
||||
@@ -184,8 +198,7 @@ export class CanvasManager {
|
||||
|
||||
/** 导出画布为JSON */
|
||||
getCanvasJSON() {
|
||||
const keys = ["top", "left", "width", "height", "scaleX", "scaleY", "info",]
|
||||
const json = this.canvas.toJSON(keys)
|
||||
const json = this.canvas.toJSON()
|
||||
return JSON.stringify(json)
|
||||
}
|
||||
/** 加载画布JSON */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { fabric } from 'fabric-with-all'
|
||||
import { createId } from '../../tools/tools'
|
||||
import { exportObjectsToImage } from '../tools/exportMethod'
|
||||
import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod'
|
||||
import { OperationType } from '../tools/layerHelper'
|
||||
|
||||
export class LayerManager {
|
||||
@@ -18,22 +18,22 @@ export class LayerManager {
|
||||
setActiveID(id: string, isActive = true) {
|
||||
this.activeID.value = id
|
||||
if (isActive) {
|
||||
this.canvasManager.setActiveObjectByID(id)
|
||||
this.canvasManager.setActiveObjectById(id)
|
||||
this.stateManager.toolManager.setTool(OperationType.SELECT)
|
||||
}
|
||||
}
|
||||
getLayerByID(id) {
|
||||
getLayerById(id) {
|
||||
return this.layers.value.find((item: any) => item.info.id === id)
|
||||
}
|
||||
setLayerNameByID(id, name: string) {
|
||||
const layer = this.getLayerByID(id)
|
||||
setLayerNameById(id, name: string) {
|
||||
const layer = this.getLayerById(id)
|
||||
if (layer) {
|
||||
layer.info.name = name
|
||||
this.canvasManager.renderAll()
|
||||
}
|
||||
}
|
||||
setLayerVisibleByID(id, visible: boolean) {
|
||||
const layer = this.getLayerByID(id)
|
||||
setLayerVisibleById(id, visible: boolean) {
|
||||
const layer = this.getLayerById(id)
|
||||
if (layer) {
|
||||
layer.set({
|
||||
visible: visible
|
||||
@@ -41,7 +41,7 @@ export class LayerManager {
|
||||
this.canvasManager.renderAll()
|
||||
}
|
||||
}
|
||||
deleteLayerByID(id, isActive = true) {
|
||||
deleteLayerById(id, isActive = true) {
|
||||
this.canvasManager.deleteObjectById(id)
|
||||
if (id === this.activeID.value && isActive) {
|
||||
this.setActiveID(this.layers.value[0]?.info?.id || "")
|
||||
@@ -54,18 +54,10 @@ export class LayerManager {
|
||||
}
|
||||
// 更新图层列表
|
||||
updateLayers() {
|
||||
this.layers.value = this.canvasManager.getObjects().filter((v: any) => !!v?.info?.id).reverse()
|
||||
window["layers"] = this.layers
|
||||
}
|
||||
// 更新图层参数
|
||||
updateLayerParams(layer, keys = []) {
|
||||
this.layers.value.forEach((item: any) => {
|
||||
if (item.info.id === layer.info.id) {
|
||||
keys.forEach((key: string) => {
|
||||
item.set(key, layer[key])
|
||||
})
|
||||
}
|
||||
})
|
||||
this.layers.value = this.canvasManager.getObjects()
|
||||
.filter((v: any) => !!v?.info?.id)
|
||||
.reverse()
|
||||
.map(v => v.toObject())
|
||||
}
|
||||
|
||||
/** 设置图层位置-不设置默认居中 */
|
||||
@@ -202,7 +194,7 @@ export class LayerManager {
|
||||
})
|
||||
// console.log(mergedImage)
|
||||
const index = this.canvasManager.getObjects().indexOf(targetLayer);
|
||||
this.deleteLayerByID(targetLayer.info.id, false)
|
||||
this.deleteLayerById(targetLayer.info.id, false)
|
||||
this.setActiveID(mergedImage.info.id, false)
|
||||
this.canvasManager.add(mergedImage, false);
|
||||
this.canvasManager.canvas.moveTo(mergedImage, index);
|
||||
@@ -219,5 +211,19 @@ export class LayerManager {
|
||||
})
|
||||
})
|
||||
}
|
||||
/** 更新图层缩略图 */
|
||||
async updateLayerThumbnailsById(id: string) {
|
||||
const object = this.canvasManager.getObjectById(id);
|
||||
if (!object) return;
|
||||
const url = await exportObjectToThumbnail(object);
|
||||
object.set({
|
||||
thumbnail: url
|
||||
})
|
||||
// object.thumbnail = url
|
||||
this.updateLayers()
|
||||
// this.canvasManager.renderAll()
|
||||
|
||||
}
|
||||
|
||||
dispose() { }
|
||||
}
|
||||
@@ -33,28 +33,27 @@ export class RectToolManager {
|
||||
}
|
||||
mouseMoveEvent(e) {
|
||||
if (!this.isDragging) return;
|
||||
const width = e.absolutePointer.x - this.startX
|
||||
const height = e.absolutePointer.y - this.startY
|
||||
this.demoObject.set({ width, height })
|
||||
var width = e.absolutePointer.x - this.startX
|
||||
var height = e.absolutePointer.y - this.startY
|
||||
var left = this.startX
|
||||
var top = this.startY
|
||||
if (width < 0) {
|
||||
left += width
|
||||
width = -width
|
||||
}
|
||||
if (height < 0) {
|
||||
top += height
|
||||
height = -height
|
||||
}
|
||||
this.demoObject.set({ width, height, left, top })
|
||||
this.canvasManager.canvas.renderAll()
|
||||
}
|
||||
mouseUpEvent(e) {
|
||||
if (!this.isDragging) return;
|
||||
this.isDragging = false;
|
||||
var width = e.absolutePointer.x - this.startX
|
||||
var height = e.absolutePointer.y - this.startY
|
||||
if(width === 0) width = 50
|
||||
if(height === 0) height = 50
|
||||
this.demoObject.set({ width, height })
|
||||
const object = this.demoObject.toJSON("evented")
|
||||
if(object.width < 0) {
|
||||
object.left += object.width
|
||||
object.width = -object.width
|
||||
}
|
||||
if(object.height < 0) {
|
||||
object.top += object.height
|
||||
object.height = -object.height
|
||||
}
|
||||
if (object.width === 0) object.width = 100
|
||||
if (object.height === 0) object.height = 100
|
||||
this.layerManager.createRectLayer(object, true)
|
||||
this.canvasManager.canvas.remove(this.demoObject)
|
||||
this.canvasManager.canvas.renderAll()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { isBoolean } from "lodash-es";
|
||||
import { OperationType, OperationTypes } from "../../tools/layerHelper";
|
||||
import { RectToolManager } from "../RectToolManager"
|
||||
import { AISelectboxToolManager } from "../AISelectboxToolManager"
|
||||
|
||||
|
||||
export class CanvasEventManager {
|
||||
constructor(canvas, options = {}) {
|
||||
@@ -31,6 +33,7 @@ export class CanvasEventManager {
|
||||
layerManager: this.layerManager,
|
||||
}
|
||||
this.rectToolManager = new RectToolManager(managers)
|
||||
this.aiSelectboxToolManager = new AISelectboxToolManager(managers)
|
||||
|
||||
// 初始化所有事件
|
||||
this.initEvents();
|
||||
@@ -205,6 +208,7 @@ export class CanvasEventManager {
|
||||
// 橡皮擦模式
|
||||
} else if (currentTool === OperationType.SELECTBOX) {
|
||||
// 选择框模式
|
||||
this.aiSelectboxToolManager.mouseDownEvent(opt);
|
||||
} else if (currentTool === OperationType.RECTANGLE) {
|
||||
// 矩形模式
|
||||
this.rectToolManager.mouseDownEvent(opt);
|
||||
@@ -232,6 +236,7 @@ export class CanvasEventManager {
|
||||
// 橡皮擦模式
|
||||
} else if (currentTool === OperationType.SELECTBOX) {
|
||||
// 选择框模式
|
||||
this.aiSelectboxToolManager.mouseMoveEvent(opt);
|
||||
} else if (currentTool === OperationType.RECTANGLE) {
|
||||
// 矩形模式
|
||||
this.rectToolManager.mouseMoveEvent(opt);
|
||||
@@ -315,6 +320,7 @@ export class CanvasEventManager {
|
||||
// 橡皮擦模式
|
||||
} else if (currentTool === OperationType.SELECTBOX) {
|
||||
// 选择框模式
|
||||
this.aiSelectboxToolManager.mouseUpEvent(opt);
|
||||
} else if (currentTool === OperationType.RECTANGLE) {
|
||||
// 矩形模式
|
||||
this.rectToolManager.mouseDownEvent(opt);
|
||||
@@ -379,6 +385,7 @@ export class CanvasEventManager {
|
||||
// 橡皮擦模式
|
||||
} else if (currentTool === OperationType.SELECTBOX) {
|
||||
// 选择框模式
|
||||
this.aiSelectboxToolManager.mouseMoveEvent(opt);
|
||||
} else if (currentTool === OperationType.RECTANGLE) {
|
||||
// 矩形模式
|
||||
this.rectToolManager.mouseMoveEvent(opt);
|
||||
@@ -488,6 +495,7 @@ export class CanvasEventManager {
|
||||
// 橡皮擦模式
|
||||
} else if (currentTool === OperationType.SELECTBOX) {
|
||||
// 选择框模式
|
||||
this.aiSelectboxToolManager.mouseUpEvent(opt);
|
||||
} else if (currentTool === OperationType.RECTANGLE) {
|
||||
// 矩形模式
|
||||
this.rectToolManager.mouseUpEvent(opt);
|
||||
@@ -659,6 +667,7 @@ export class CanvasEventManager {
|
||||
// 橡皮擦模式
|
||||
} else if (currentTool === OperationType.SELECTBOX) {
|
||||
// 选择框模式
|
||||
this.aiSelectboxToolManager.mouseUpEvent(opt);
|
||||
} else if (currentTool === OperationType.RECTANGLE) {
|
||||
// 矩形模式
|
||||
this.rectToolManager.mouseUpEvent(opt);
|
||||
@@ -722,6 +731,7 @@ export class CanvasEventManager {
|
||||
this.canvas.on("object:modified", (e) => {
|
||||
updateLayers(e);
|
||||
this.stateManager.recordState();
|
||||
this.layerManager.updateLayerThumbnailsById(e.target.info.id);
|
||||
});
|
||||
this.canvas.on("object:removed", (e) => {
|
||||
updateLayers(e);
|
||||
@@ -1065,6 +1075,7 @@ export class CanvasEventManager {
|
||||
|
||||
dispose() {
|
||||
this.rectToolManager?.dispose()
|
||||
this.aiSelectboxToolManager?.dispose()
|
||||
// 移除所有事件监听
|
||||
this.canvas.off();
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ export const createCanvas = (elementId, options = {}) => {
|
||||
// skipOffscreen: true, // 跳过离屏渲染
|
||||
imageSmoothingEnabled: true, // 启用图像平滑 - 抗锯齿
|
||||
imageSmoothingQuality: "high", // 设置高质量图像平滑
|
||||
fireMiddleClick: true,// 启用中键点击事件
|
||||
fireRightClick: true,// 启用右键点击事件
|
||||
stopContextMenu: true,// 阻止浏览器默认的右键菜单弹出,避免干扰
|
||||
...options,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { fabric } from 'fabric-with-all'
|
||||
import { createStaticCanvas } from './canvasFactory'
|
||||
import { getObjectsBoundingBox, cloneObjects } from './canvasMethod'
|
||||
/** 导出画布为图片 */
|
||||
@@ -39,4 +40,33 @@ export async function exportObjectsToImage(objects = [], isDetails = false) {
|
||||
url: dataURL,
|
||||
...boundingBox,
|
||||
} : dataURL
|
||||
}
|
||||
|
||||
/** 导出指定对象为缩略图 */
|
||||
export async function exportObjectToThumbnail(object, width = 100, height = 100) {
|
||||
const url = await exportObjectsToImage([object]);
|
||||
const staticCanvas = createStaticCanvas(document.createElement('canvas'))
|
||||
staticCanvas.setWidth(width)
|
||||
staticCanvas.setHeight(height)
|
||||
const image = await new Promise((resolve, reject) => {
|
||||
fabric.Image.fromURL(url, (img) => {
|
||||
const scale = Math.min(width / img.width, height / img.height)
|
||||
const left = (width - img.width * scale) / 2
|
||||
const top = (height - img.height * scale) / 2
|
||||
img.set({
|
||||
left,
|
||||
top,
|
||||
scaleX: scale,
|
||||
scaleY: scale,
|
||||
})
|
||||
resolve(img)
|
||||
})
|
||||
})
|
||||
staticCanvas.add(image)
|
||||
// 导出图片
|
||||
const dataURL = staticCanvas.toDataURL({
|
||||
type: 'image/png',
|
||||
quality: 1,
|
||||
})
|
||||
return dataURL
|
||||
}
|
||||
@@ -120,7 +120,7 @@
|
||||
|
||||
const onGenerateClick = async () => {
|
||||
const data = componentRef.value.data
|
||||
const subordNode = stateManager.getSubordNodeByID(attrs.node.id)
|
||||
const subordNode = stateManager.getSubordNodeById(attrs.node.id)
|
||||
emit('update-data', data)
|
||||
console.log(attrs.node,data)
|
||||
if(!attrs.node?.data?.originalImage)console.log('originalImage 找不到原始图片')
|
||||
|
||||
@@ -19,7 +19,7 @@ export class FlowManager {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
getSubordNodeByID(id: string) {
|
||||
getSubordNodeById(id: string) {
|
||||
return this.vueFlow.value.getNodes?.find((v) => v.data.superiorID === id)
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ export class StateManager {
|
||||
/** 获取节点 */
|
||||
getNodeById(id: string) { return this.nodes.value.find((node: NodesItem) => node.id === id) }
|
||||
/** 获取下级节点 */
|
||||
getSubordNodeByID(id: string) { return this.nodes.value.find((node: NodesItem) => node.data.superiorID === id) }
|
||||
getSubordNodeById(id: string) { return this.nodes.value.find((node: NodesItem) => node.data.superiorID === id) }
|
||||
getLastNode() { return this.nodes.value[this.nodes.value.length - 1] }
|
||||
/** 设置工具 */
|
||||
setTool(tool: string) { this.tool.value = tool }
|
||||
|
||||
Reference in New Issue
Block a user