Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/FiDA_Front

This commit is contained in:
X1627315083@163.com
2026-03-25 13:04:34 +08:00
10 changed files with 371 additions and 163 deletions

View File

@@ -4,51 +4,63 @@
<div <div
v-for="item in list" v-for="item in list"
:key="item.type" :key="item.type"
:class="{ active: item.name === props.currentTool }" :class="{ active: item.type === currentTool }"
@click="onClickItem(item.type)"
> >
<span class="icon"><svg-icon :name="item.name" size="16" /></span> <span class="icon"><svg-icon :name="item.name" size="16" /></span>
<span class="label">{{ item.label }}</span> <span class="label">{{ item.label }}</span>
</div> </div>
<button>创建</button> <button @click="onCreate">创建</button>
</div> </div>
</transition> </transition>
<brush-control-panel :currentTool="show ? 'draw' : ''" style="top: 14rem" /> <brush-control-panel v-if="show" :currentTool="currentTool2" style="top: 14rem" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, inject, computed, watch } from 'vue' import { ref, inject, computed, watch } from 'vue'
import depthSlider from './tools/depth-slider.vue' import depthSlider from './tools/depth-slider.vue'
import { OperationType, AI_SELECTBOX_TYPE } from '../tools/layerHelper' import { OperationType } from '../tools/layerHelper'
import brushControlPanel from './brush-control-panel.vue'
const props = defineProps({ const props = defineProps({
currentTool: { required: true, type: [String, null] } currentTool: { required: true, type: [String, null] }
}) })
const stateManager = inject('stateManager') as any const stateManager = inject('stateManager') as any
const toolManager = inject('toolManager') as any const toolManager = inject('toolManager') as any
const showTools = [OperationType.SELECTBOX] const tool2 = {
const show = computed(() => showTools.includes(props.currentTool)) [OperationType.AISELECT_DRAW]: OperationType.ERASER,
[OperationType.AISELECT_ERASER]: OperationType.ERASER
}
const currentTool2 = computed(() => tool2[props.currentTool] || props.currentTool)
const show = computed(() => stateManager.aiSelectboxToolManager.tools.includes(props.currentTool))
const list = ref([ const list = ref([
{ {
type: AI_SELECTBOX_TYPE.ADD, type: OperationType.AISELECT_ADD,
name: 'dc-add_sb', name: 'dc-add_sb',
label: 'Add' label: 'Add'
}, },
{ {
type: AI_SELECTBOX_TYPE.REMOVE, type: OperationType.AISELECT_REMOVE,
name: 'dc-remove_sb', name: 'dc-remove_sb',
label: 'Remove' label: 'Remove'
}, },
{ {
type: AI_SELECTBOX_TYPE.DRAW, type: OperationType.AISELECT_DRAW,
name: 'dc-brush_sb', name: 'dc-brush_sb',
label: 'Brush' label: 'Brush'
}, },
{ {
type: AI_SELECTBOX_TYPE.ERASER, type: OperationType.AISELECT_ERASER,
name: 'dc-erase_sb', name: 'dc-erase_sb',
label: 'Erase' label: 'Erase'
} }
]) ])
const onClickItem = (type: string) => {
toolManager.setTool(type)
}
const onCreate = () => {
stateManager.aiSelectboxToolManager.createSelectbox()
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
// 淡入淡出动画 // 淡入淡出动画

View File

@@ -39,20 +39,22 @@
currentTool: { required: true, type: [String, null] } currentTool: { required: true, type: [String, null] }
}) })
const stateManager = inject('stateManager') as any const stateManager = inject('stateManager') as any
const toolManager = inject('toolManager') as any
const brushState = computed(() => stateManager.brushManager.brushStore.state) const brushState = computed(() => stateManager.brushManager.brushStore.state)
const showTools = [OperationType.DRAW, OperationType.ERASER] const showTools = [OperationType.DRAW, OperationType.ERASER]
const show = computed(() => showTools.includes(props.currentTool)) const show = computed(() => showTools.includes(props.currentTool))
watch(brushState, (value) => {
if (value) updateBrushState()
})
watch(
() => props.currentTool,
(value) => updateBrushState()
)
const brushSize = ref(40) const brushSize = ref(40)
const brushOpacity = ref(100) const brushOpacity = ref(100)
const brushColor = ref('#000000') const brushColor = ref('#000000')
const updateBrushState = () => {
brushSize.value = brushState.value.size
brushOpacity.value = brushState.value.opacity
brushColor.value = brushState.value.color
}
updateBrushState()
watch(() => brushState.value.size, updateBrushState)
watch(() => brushState.value.opacity, updateBrushState)
watch(() => brushState.value.color, updateBrushState)
const onInputSize = (value: number) => { const onInputSize = (value: number) => {
stateManager.brushManager.setBrushSize(value) stateManager.brushManager.setBrushSize(value)
} }
@@ -62,12 +64,6 @@
const onInputColor = () => { const onInputColor = () => {
stateManager.brushManager.setBrushColor(brushColor.value) stateManager.brushManager.setBrushColor(brushColor.value)
} }
const updateBrushState = () => {
brushSize.value = brushState.value.size
brushOpacity.value = brushState.value.opacity
brushColor.value = brushState.value.color
}
updateBrushState()
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
// 淡入淡出动画 // 淡入淡出动画

View File

@@ -7,7 +7,7 @@
class="icon" class="icon"
@click="onClickTool(v)" @click="onClickTool(v)"
:class="{ :class="{
active: v.name === tool, active: v.name === tool || v.tools?.includes(tool),
disabled: v.disabled disabled: v.disabled
}" }"
> >
@@ -72,7 +72,12 @@
{ name: OperationType.DRAW, icon: 'dc-brush', iconSize: 18 }, { name: OperationType.DRAW, icon: 'dc-brush', iconSize: 18 },
{ name: OperationType.ERASER, icon: 'dc-eraser', iconSize: 18 }, { name: OperationType.ERASER, icon: 'dc-eraser', iconSize: 18 },
{ icon: 'dc-image', iconSize: 17, on: () => onImageClick() }, { icon: 'dc-image', iconSize: 17, on: () => onImageClick() },
{ name: OperationType.SELECTBOX, icon: 'dc-selectbox', iconSize: 16 }, {
name: OperationType.AISELECT_ADD,
icon: 'dc-selectbox',
iconSize: 16,
tools: stateManager.aiSelectboxToolManager.tools
},
{ {
name: OperationType.RECTANGLE, name: OperationType.RECTANGLE,
icon: 'dc-rectangle', icon: 'dc-rectangle',

View File

@@ -1,6 +1,6 @@
import { fabric } from 'fabric-with-all' import { fabric } from 'fabric-with-all'
import { OperationType, AI_SELECTBOX_TYPE } from '../tools/layerHelper' import { OperationType } from '../tools/layerHelper'
import { getObjectAlphaToCanvas, traceImageContour } from '../tools/canvasMethod' import { getObjectAlphaToCanvas, traceImageContour, cloneObjects } from '../tools/canvasMethod'
/** 智能框选工具管理器 */ /** 智能框选工具管理器 */
export class AISelectboxToolManager { export class AISelectboxToolManager {
@@ -13,7 +13,17 @@ export class AISelectboxToolManager {
isDragging: boolean = false isDragging: boolean = false
startX: number = 0 startX: number = 0
startY: number = 0 startY: number = 0
demoObject: any
indicatorObject: any// 指示框对象
demoObject: any// 演示框对象
tcanvas: any// 临时画布对象
tools = [
OperationType.AISELECT_ADD,
OperationType.AISELECT_REMOVE,
OperationType.AISELECT_DRAW,
OperationType.AISELECT_ERASER
]
constructor(options) { constructor(options) {
this.canvasManager = options.canvasManager this.canvasManager = options.canvasManager
this.stateManager = options.stateManager this.stateManager = options.stateManager
@@ -22,17 +32,56 @@ export class AISelectboxToolManager {
} }
/** 处理切换工具 */ /** 处理切换工具 */
handleToolChange(oldTool: string, newTool: string) { handleToolChange(oldTool: string, newTool: string) {
if (newTool === OperationType.SELECTBOX) { const oldIsAAA = this.tools.includes(oldTool)
// 切换到智能框选工具 const newIsAAA = this.tools.includes(newTool)
} else { if (!oldIsAAA && newIsAAA) {
// 切换到普通框选工具 // 普通工具切换到智能框选工具
this.init()
} else if (oldIsAAA && !newIsAAA) {
// 智能框选工具切换到普通工具
this.clear()
} }
} }
mouseDownEvent(e) { /** 切换到橡皮擦工具 */
this.isDragging = true changeToolToEraser() {
this.startX = e.absolutePointer.x if (!this.demoObject) return;
this.startY = e.absolutePointer.y this.demoObject.set({ erasable: true })
}
init() {
console.log("初始化智能框选工具")
this.clear();
this.createDemoObject()
this.tcanvas = null;
}
clear() {
console.log("清除智能框选工具")
this.clearDemoObject()
this.clearIndicatorObject()
this.isDragging = false
this.canvasManager.canvas.renderAll()
}
createDemoObject() {
if (this.demoObject) this.clearDemoObject()
const { canvasWidth, canvasHeight } = this.canvasManager.getCanvasSize();
const canvas = document.createElement('canvas')
canvas.width = canvasWidth
canvas.height = canvasHeight
this.demoObject = new fabric.Image(canvas, {
left: 0,
top: 0,
evented: false,
selectable: false,
erasable: false,
})
this.canvasManager.canvas.add(this.demoObject)
this.canvasManager.canvas.renderAll()
}
clearDemoObject() {
this.canvasManager.canvas.remove(this.demoObject)
this.demoObject = null
}
createIndicatorObject() {
const rect = new fabric.Rect({ const rect = new fabric.Rect({
left: this.startX, left: this.startX,
top: this.startY, top: this.startY,
@@ -43,10 +92,44 @@ export class AISelectboxToolManager {
strokeWidth: 1, strokeWidth: 1,
evented: false, evented: false,
}) })
this.demoObject = rect this.indicatorObject = rect
this.canvasManager.canvas.add(rect) this.canvasManager.canvas.add(rect)
this.canvasManager.canvas.renderAll() this.canvasManager.canvas.renderAll()
} }
clearIndicatorObject() {
this.canvasManager.canvas.remove(this.indicatorObject)
this.indicatorObject = null
}
// 处理画笔绘制图像
async handleBrushDrawImage(fabricImage: fabric.Object) {
if (!this.demoObject) return;
const tcanvas = new fabric.StaticCanvas(document.createElement("canvas"), {
width: this.demoObject.width,
height: this.demoObject.height,
enableRetinaScaling: false,
});
const demoObject = await cloneObjects([this.demoObject]).then(v => v[0])
tcanvas.add(demoObject)
tcanvas.add(fabricImage)
tcanvas.renderAll();
const canvas = getObjectAlphaToCanvas(tcanvas, null, 0, this.rgba);
const image = new fabric.Image(canvas);
this.canvasManager.canvas.add(image)
this.canvasManager.canvas.remove(this.demoObject);
this.demoObject = image;
this.canvasManager.canvas.renderAll()
}
mouseDownEvent(e) {
// if (true) return
const tool = this.toolManager.currentTool.value
const tools = [OperationType.AISELECT_ADD, OperationType.AISELECT_REMOVE]
if (!tools.includes(tool)) return;
this.isDragging = true
this.startX = e.absolutePointer.x
this.startY = e.absolutePointer.y
this.createIndicatorObject()
}
mouseMoveEvent(e) { mouseMoveEvent(e) {
if (!this.isDragging) return; if (!this.isDragging) return;
var width = e.absolutePointer.x - this.startX var width = e.absolutePointer.x - this.startX
@@ -61,25 +144,23 @@ export class AISelectboxToolManager {
top += height top += height
height = -height height = -height
} }
this.demoObject.set({ width, height, left, top }) this.indicatorObject.set({ width, height, left, top })
this.canvasManager.canvas.renderAll() this.canvasManager.canvas.renderAll()
} }
mouseUpEvent(e) { mouseUpEvent(e) {
if (!this.isDragging) return; if (!this.isDragging) return;
this.isDragging = false; this.isDragging = false;
const object = this.demoObject.toJSON("evented") const object = this.indicatorObject.toJSON("evented")
if (object.width === 0) object.width = 100 if (object.width === 0) object.width = 100
if (object.height === 0) object.height = 100 if (object.height === 0) object.height = 100
// console.log(object) console.log(object.top, object.left, object.width, object.height)
this.canvasManager.canvas.remove(this.demoObject) this.clearIndicatorObject()
this.canvasManager.canvas.renderAll() this.canvasManager.canvas.renderAll()
// this.createSelectbox() // this.createSelectbox()
} }
loadImageToObject(url) { loadImageToObject(url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fabric.Image.fromURL(url, (img) => { fabric.Image.fromURL(url, (img) => {
@@ -87,7 +168,7 @@ export class AISelectboxToolManager {
}, { crossOrigin: "anonymous" });// 防止污染 }, { crossOrigin: "anonymous" });// 防止污染
}); });
} }
rgba = { r: 0, g: 255, b: 0, a: 200 }; rgba = { r: 255, g: 0, b: 0, a: 127.5 };
selectionStyle = { selectionStyle = {
stroke: "rgba(255, 77, 71, 1)", stroke: "rgba(255, 77, 71, 1)",
strokeWidth: 1.5, strokeWidth: 1.5,
@@ -99,12 +180,12 @@ export class AISelectboxToolManager {
absolutePositioned: true, absolutePositioned: true,
}; };
async createSelectbox() { async createSelectbox() {
const url = "http://118.31.39.42:3000/falls/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png" // const url = "http://118.31.39.42:3000/falls/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png"
const image = await this.loadImageToObject(url) // const image = await this.loadImageToObject(url)
const canvas = getObjectAlphaToCanvas(image, null, 0, this.rgba); // const fobject = this.canvasManager.canvas.clipPath
const fobject = this.canvasManager.canvas.clipPath const fobject = this.demoObject
// const top = fobject.top - fobject.height * scaleY / 2; this.clearDemoObject()
// const left = fobject.left - fobject.width * scaleX / 2; const canvas = getObjectAlphaToCanvas(fobject, null, 0, { r: 255, g: 0, b: 0, a: 255 });
const scaleY = fobject.scaleY const scaleY = fobject.scaleY
const scaleX = fobject.scaleX const scaleX = fobject.scaleX
const top = fobject.top const top = fobject.top
@@ -127,6 +208,8 @@ export class AISelectboxToolManager {
}); });
const group = await this.layerManager.createGroupLayer({ const group = await this.layerManager.createGroupLayer({
clipPath: path, clipPath: path,
top: path.top,
left: path.left,
}, false, false) }, false, false)
const rect = await this.layerManager.createRectLayer({ const rect = await this.layerManager.createRectLayer({
width: path.width, width: path.width,
@@ -145,5 +228,7 @@ export class AISelectboxToolManager {
dispose() { } dispose() {
this.clear()
}
} }

View File

@@ -25,6 +25,7 @@ fabric.Object.prototype.toObject = function () {
const object = this.toObject_(arr) const object = this.toObject_(arr)
if (object.info) { if (object.info) {
let lock = !!object.info.lock let lock = !!object.info.lock
if (object.type === "group") lock = true
object.evented = !lock object.evented = !lock
object.selectable = !lock object.selectable = !lock
} }
@@ -214,7 +215,13 @@ export class CanvasManager {
const obj = this.getObjectById(id) const obj = this.getObjectById(id)
if (!obj) return if (!obj) return
if (obj.type === "group") { if (obj.type === "group") {
const objects = []; obj._originPosition = {
top: obj.top,
left: obj.left,
cpTop: obj.clipPath.top,
cpLeft: obj.clipPath.left,
}
const objects = [obj];
this.getObjects().forEach((item: any) => { this.getObjects().forEach((item: any) => {
if (item?.info?.parentId === id) objects.push(item) if (item?.info?.parentId === id) objects.push(item)
}) })
@@ -258,6 +265,9 @@ export class CanvasManager {
getObjectById(id: string) { getObjectById(id: string) {
return this.getObjects().find((item: any) => item?.info?.id === id) return this.getObjects().find((item: any) => item?.info?.id === id)
} }
getChildObjectsById(id: string) {
return this.getObjects().filter((item: any) => item?.info?.parentId === id)
}
/** 获取选中对象 */ /** 获取选中对象 */
getSelectedObject() { getSelectedObject() {
return this.canvas.getActiveObject() return this.canvas.getActiveObject()
@@ -285,6 +295,8 @@ export class CanvasManager {
const currentTool = this.stateManager.toolManager.currentTool.value; const currentTool = this.stateManager.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) { if (currentTool === OperationType.DRAW) {
this.handleDrawImage(fabricImage) this.handleDrawImage(fabricImage)
} else if (currentTool === OperationType.AISELECT_DRAW) {
this.stateManager.aiSelectboxToolManager.handleBrushDrawImage(fabricImage)
} }
return true return true
}; };
@@ -293,7 +305,7 @@ export class CanvasManager {
async handleDrawImage(fabricImage: fabric.Object) { async handleDrawImage(fabricImage: fabric.Object) {
const activeID = this.stateManager.layerManager.activeID.value const activeID = this.stateManager.layerManager.activeID.value
const activeLayer = this.getObjectById(activeID) const activeLayer = this.getObjectById(activeID)
if (activeLayer && activeLayer.fill?.repeat !== "repeat") { if (activeLayer && activeLayer.fill?.repeat !== "repeat" && activeLayer.type !== "group") {
this.layerManager.imageMergeToLayer(activeLayer, fabricImage) this.layerManager.imageMergeToLayer(activeLayer, fabricImage)
} else { } else {
const emptyLayer = await this.layerManager.createEmptyLayer(false); const emptyLayer = await this.layerManager.createEmptyLayer(false);

View File

@@ -19,11 +19,11 @@ export class LayerManager {
onMounted() { } onMounted() { }
setActiveID(id: string, isActive = true) { setActiveID(id: string, isActive = true) {
const layer = this.getLayerById(id) const layer = this.getLayerById(id)
if (layer?.type === "group") { // if (layer?.type === "group") {
this.activeID.value = "" // this.activeID.value = ""
} else { // } else {
this.activeID.value = id this.activeID.value = id
} // }
if (isActive) { if (isActive) {
this.canvasManager.setActiveObjectById(id) this.canvasManager.setActiveObjectById(id)
this.stateManager.toolManager.setTool(OperationType.SELECT) this.stateManager.toolManager.setTool(OperationType.SELECT)
@@ -62,11 +62,21 @@ export class LayerManager {
setLayerVisibleById(id, visible: boolean) { setLayerVisibleById(id, visible: boolean) {
const layer = this.getLayerById(id) const layer = this.getLayerById(id)
const object = this.canvasManager.getObjectById(id) const object = this.canvasManager.getObjectById(id)
if (!layer || !object) return const call = (layer, object) => {
layer.visible = visible if (!layer || !object) return
object.set({ layer.visible = visible
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)
})
}
this.canvasManager.renderAll() this.canvasManager.renderAll()
this.stateManager.recordState() this.stateManager.recordState()
} }
@@ -74,14 +84,26 @@ export class LayerManager {
setLayerLockById(id, lock: boolean) { setLayerLockById(id, lock: boolean) {
const layer = this.getLayerById(id) const layer = this.getLayerById(id)
const object = this.canvasManager.getObjectById(id) const object = this.canvasManager.getObjectById(id)
if (!layer || !object) return const call = (layer, object) => {
layer.info.lock = !!lock if (!layer || !object) return
layer.evented = !lock layer.info.lock = !!lock
object.info.lock = !!lock layer.evented = !lock
object.set({ object.info.lock = !!lock
evented: !lock, if (object.type !== "group") {
selectable: !lock, 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)
})
}
if (lock) { if (lock) {
// 取消选中对象 // 取消选中对象
const e = this.canvasManager.getSelectedObject() const e = this.canvasManager.getSelectedObject()
@@ -194,13 +216,14 @@ export class LayerManager {
} }
/** 创建组图层 */ /** 创建组图层 */
async createGroupLayer(options?: any, isRecord = true, isActive = false) { async createGroupLayer(options?: any, isRecord = true, isActive = false) {
const children = options?.children || [] const groupObject = new fabric.Group([], {
delete options.children
const groupObject = new fabric.Group(children, {
...(options || {}), ...(options || {}),
width: 1,
height: 1,
hasControls: false, // 不显示控制点 hasControls: false, // 不显示控制点
hasBorders: false, // 不显示边框 hasBorders: false, // 不显示边框
selectable: false, // 不可选中(可选) selectable: false, // 不可选中(可选)
evented: false,
info: { info: {
id: createId("group"), id: createId("group"),
name: '智能选区组', name: '智能选区组',
@@ -423,10 +446,18 @@ export class LayerManager {
const objects = this.canvasManager.getObjects() const objects = this.canvasManager.getObjects()
objects.forEach((item: any) => { objects.forEach((item: any) => {
item.set({ item.set({
erasable: item.info.id === this.activeID.value erasable: (item.info.id === this.activeID.value && item.type !== "group")
}) })
}) })
} }
/** 设置所有对象擦除属性 */
setAllObjectsErasable(isErasable = false) {
const objects = this.canvasManager.getObjects()
objects.forEach((item: any) => {
item.set({ erasable: isErasable })
})
}
/** 更新图层缩略图 */ /** 更新图层缩略图 */
async updateLayerThumbnailsById(id: string, thumbnail?: string, isUpdate = true) { async updateLayerThumbnailsById(id: string, thumbnail?: string, isUpdate = true) {
const object = this.canvasManager.getObjectById(id); const object = this.canvasManager.getObjectById(id);

View File

@@ -46,9 +46,25 @@ export class ToolManager {
}, },
/** 智能选框工具 */ /** 智能选框工具 */
{ {
name: OperationType.SELECTBOX, name: OperationType.AISELECT_ADD,
cursor: "crosshair", cursor: "crosshair",
}, },
{
name: OperationType.AISELECT_REMOVE,
cursor: "crosshair",
},
{
name: OperationType.AISELECT_DRAW,
cursor: "crosshair",
setup: this.setupAISelectBrushTool.bind(this),
isDrawingMode: true,
},
{
name: OperationType.AISELECT_ERASER,
cursor: "crosshair",
setup: this.setupAISelectEraserTool.bind(this),
isDrawingMode: true,
},
/** 矩形工具 */ /** 矩形工具 */
{ {
name: OperationType.RECTANGLE, name: OperationType.RECTANGLE,
@@ -120,7 +136,7 @@ export class ToolManager {
setCanvasEvented(value: boolean) { setCanvasEvented(value: boolean) {
this.canvasManager.canvas.selection = value this.canvasManager.canvas.selection = value
this.canvasManager.canvas.getObjects().forEach((v) => { this.canvasManager.canvas.getObjects().forEach((v) => {
if (v.info?.lock) return if (v.info?.lock || v.type === "group") return
v.evented = value v.evented = value
}) })
} }
@@ -179,6 +195,46 @@ export class ToolManager {
this._enableBrushIndicator(); this._enableBrushIndicator();
} }
/** 智能选区画笔工具 */
setupAISelectBrushTool() {
if (!this.canvasManager.canvas) return;
// 确保有笔刷管理器
if (this.brushManager) {
// 检查画笔是否正在更新中
if (this.brushManager.isUpdatingBrush) {
console.warn("画笔正在更新中,请稍候...");
return;
}
this.brushManager.setBrushSize(5);
this.brushManager.setBrushColor("rgb(255, 0, 0)");
this.brushManager.setBrushOpacity(0.5);
this.brushManager.setBrushType("pencil");
// 更新应用到画布
this.brushManager.updateBrush();
}
// 启用笔刷指示器并同步颜色
this._enableBrushIndicator();
}
/** 智能选区橡皮擦工具 */
setupAISelectEraserTool() {
if (!this.canvasManager.canvas) return;
// 确保有笔刷管理器
if (this.brushManager) {
this.brushManager.createEraser();
this.brushManager.setBrushSize(5);
}
this.stateManager.layerManager.setAllObjectsErasable(false)
// 启用笔刷指示器
this._enableBrushIndicator();
this.stateManager.aiSelectboxToolManager.changeToolToEraser()
}
/** /**
* 启用笔刷指示器 * 启用笔刷指示器
* @param {String} color 笔刷颜色(可选) * @param {String} color 笔刷颜色(可选)

View File

@@ -187,6 +187,58 @@ export class CanvasEventManager {
} }
} }
_handleMouseDown(opt) {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (this.aiSelectboxToolManager.tools.includes(currentTool)) {
// 选择框模式
this.aiSelectboxToolManager.mouseDownEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseDownEvent(opt);
} else {
return false
}
return true
}
_handleMouseMove(opt) {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (this.aiSelectboxToolManager.tools.includes(currentTool)) {
// 选择框模式
this.aiSelectboxToolManager.mouseMoveEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseMoveEvent(opt);
} else {
return false
}
return true
}
_handleMouseUp(opt) {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (this.aiSelectboxToolManager.tools.includes(currentTool)) {
// 选择框模式
this.aiSelectboxToolManager.mouseUpEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseUpEvent(opt);
} else {
return false
}
return true
}
/** /**
* 设置鼠标事件处理 * 设置鼠标事件处理
*/ */
@@ -202,16 +254,7 @@ export class CanvasEventManager {
// } else // } else
const currentTool = this.toolManager.currentTool.value; const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) { if (this._handleMouseDown(opt)) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (currentTool === OperationType.SELECTBOX) {
// 选择框模式
this.aiSelectboxToolManager.mouseDownEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseDownEvent(opt);
} else if (opt.e.altKey || opt.e.which === 2 || currentTool === OperationType.PAN) { } else if (opt.e.altKey || opt.e.which === 2 || currentTool === OperationType.PAN) {
this.canvas.isDragging = true; this.canvas.isDragging = true;
this.canvas.lastPosX = opt.e.clientX; this.canvas.lastPosX = opt.e.clientX;
@@ -230,16 +273,7 @@ export class CanvasEventManager {
// 鼠标移动事件 // 鼠标移动事件
this.canvas.on("mouse:move", (opt) => { this.canvas.on("mouse:move", (opt) => {
const currentTool = this.toolManager.currentTool.value; const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) { if (this._handleMouseMove(opt)) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (currentTool === OperationType.SELECTBOX) {
// 选择框模式
this.aiSelectboxToolManager.mouseMoveEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseMoveEvent(opt);
} else if (this.canvas.isDragging) { } else if (this.canvas.isDragging) {
const vpt = this.canvas.viewportTransform; const vpt = this.canvas.viewportTransform;
vpt[4] += opt.e.clientX - this.canvas.lastPosX; vpt[4] += opt.e.clientX - this.canvas.lastPosX;
@@ -314,16 +348,7 @@ export class CanvasEventManager {
this.canvas.on("mouse:down", (opt) => { this.canvas.on("mouse:down", (opt) => {
// 只在PAN模式下处理触摸事件 // 只在PAN模式下处理触摸事件
const currentTool = this.toolManager.currentTool.value; const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) { if (this._handleMouseDown(opt)) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (currentTool === OperationType.SELECTBOX) {
// 选择框模式
this.aiSelectboxToolManager.mouseUpEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseDownEvent(opt);
} else if (currentTool === OperationType.PAN) { } else if (currentTool === OperationType.PAN) {
// 平滑停止任何正在进行的惯性动画 // 平滑停止任何正在进行的惯性动画
@@ -379,16 +404,7 @@ export class CanvasEventManager {
this.canvas.on("mouse:move", (opt) => { this.canvas.on("mouse:move", (opt) => {
const currentTool = this.toolManager.currentTool.value; const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) { if (this._handleMouseMove(opt)) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (currentTool === OperationType.SELECTBOX) {
// 选择框模式
this.aiSelectboxToolManager.mouseMoveEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseMoveEvent(opt);
} else if (currentTool === OperationType.PAN) { } else if (currentTool === OperationType.PAN) {
// 检查是否是触摸事件 // 检查是否是触摸事件
@@ -489,16 +505,7 @@ export class CanvasEventManager {
// 触摸结束事件 // 触摸结束事件
this.canvas.on("mouse:up", (opt) => { this.canvas.on("mouse:up", (opt) => {
const currentTool = this.toolManager.currentTool.value; const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) { if (this._handleMouseUp(opt)) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (currentTool === OperationType.SELECTBOX) {
// 选择框模式
this.aiSelectboxToolManager.mouseUpEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseUpEvent(opt);
} else if (currentTool === OperationType.PAN) { } else if (currentTool === OperationType.PAN) {
// 重置触摸状态 // 重置触摸状态
@@ -661,16 +668,7 @@ export class CanvasEventManager {
*/ */
handleDragEnd(opt, isTouch = false) { handleDragEnd(opt, isTouch = false) {
const currentTool = this.toolManager.currentTool.value; const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) { if (this._handleMouseUp(opt)) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (currentTool === OperationType.SELECTBOX) {
// 选择框模式
this.aiSelectboxToolManager.mouseUpEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseUpEvent(opt);
} else if (this.canvas.isDragging) { } else if (this.canvas.isDragging) {
// if (this.lastMousePositions.length > 1 && opt && opt.e) { // if (this.lastMousePositions.length > 1 && opt && opt.e) {
// this.animationManager.applyInertiaEffect( // this.animationManager.applyInertiaEffect(
@@ -691,9 +689,16 @@ export class CanvasEventManager {
this.canvas.on("selection:updated", (opt) => this.updateSelectedLayer(opt)); this.canvas.on("selection:updated", (opt) => this.updateSelectedLayer(opt));
// this.canvas.on("selection:cleared", () => this.clearSelectedElements()); this.canvas.on("selection:cleared", (opt) => this.clearSelectedElements(opt));
}
clearSelectedElements(opt) {
if (opt.deselected && opt.deselected.length > 0) {
opt.deselected.forEach((object) => {
if (object.type !== "group") return;
if (object._originPosition) delete object._originPosition
})
}
} }
setupObjectEvents() { setupObjectEvents() {
// 监听对象变化事件,用于更新缩略图 // 监听对象变化事件,用于更新缩略图
// this.canvas.on("object:added", (e) => { // this.canvas.on("object:added", (e) => {
@@ -706,34 +711,43 @@ export class CanvasEventManager {
// } // }
// }); // });
const updateLayers = (e) => {
if (e.target._objects) return;
// this.layerManager.updateLayers();// 先不用数据大了非常卡
};
// 添加对象开始变换时的状态捕获 // 添加对象开始变换时的状态捕获
this.canvas.on("object:moving", (e) => { this.canvas.on("object:moving", (e) => {
// console.log("object:moving", e); console.log("object:moving", e);
// updateLayers(e); const target = e.target;
if (target._objects && target._objects.length > 0) {
target._objects.forEach((object) => {
if (object.type !== "group") return;
if (!object._originPosition) return
const originTop = object._originPosition.top
const originLeft = object._originPosition.left
const originCpTop = object._originPosition.cpTop
const originCpLeft = object._originPosition.cpLeft
const top = object.top + target.top + target.height / 2;
const left = object.left + target.left + target.width / 2;
object.clipPath.set({
top: originCpTop + (top - originTop),
left: originCpLeft + (left - originLeft),
})
})
this.canvasManager.updateSubLayerClipPath()
}
}); });
this.canvas.on("object:scaling", (e) => { this.canvas.on("object:scaling", (e) => {
// console.log("object:scaling", e); // console.log("object:scaling", e);
// updateLayers(e);
}); });
this.canvas.on("object:rotating", (e) => { this.canvas.on("object:rotating", (e) => {
// console.log("object:rotating", e); // console.log("object:rotating", e);
// updateLayers(e);
}); });
this.canvas.on("object:skewing", (e) => { this.canvas.on("object:skewing", (e) => {
// console.log("object:skewing", e); // console.log("object:skewing", e);
// updateLayers(e);
}); });
this.canvas.on("object:modified", async (e) => { this.canvas.on("object:modified", async (e) => {
// updateLayers(e); // console.log("object:modified", e);
const target = e.target; const target = e.target;
const id = target?.info?.id; const id = target?.info?.id;
if (id) await this.layerManager.updateLayerThumbnailsById(id) if (id) await this.layerManager.updateLayerThumbnailsById(id)
if (target.type === "group") await this.canvasManager.updateSubLayerClipPath()
this.stateManager.recordState(); this.stateManager.recordState();
}); });
this.canvas.on("object:removed", (e) => { this.canvas.on("object:removed", (e) => {

View File

@@ -29,7 +29,11 @@ export const OperationType = {
PAN: "pan", // 拖拽模式 PAN: "pan", // 拖拽模式
DRAW: "draw", // 绘画模式 DRAW: "draw", // 绘画模式
ERASER: "eraser", // 橡皮擦模式 ERASER: "eraser", // 橡皮擦模式
SELECTBOX: "selectbox",// 选择框工具模式
AISELECT_ADD: "aiSelectAdd",// 智能框选添加模式
AISELECT_REMOVE: "aiSelectRemove",// 智能框选删除模式
AISELECT_DRAW: "aiSelectDraw",// 智能框选绘制模式
AISELECT_ERASER: "aiSelectEraser",// 智能框选橡皮擦模式
RECTANGLE: "rectangle",// 矩形工具模式 RECTANGLE: "rectangle",// 矩形工具模式
LINE: "line",// 直线工具模式 LINE: "line",// 直线工具模式
@@ -68,11 +72,3 @@ export const BlendMode = {
DESTINATION_IN: "destination-in", // 目标内 DESTINATION_IN: "destination-in", // 目标内
DESTINATION_OUT: "destination-out", // 目标外 DESTINATION_OUT: "destination-out", // 目标外
}; };
/** 智能框选工具类型枚举 */
export const AI_SELECTBOX_TYPE = {
ADD: "add", // 添加模式
REMOVE: "remove", // 删除模式
DRAW: "draw", // 绘画模式
ERASER: "eraser", // 橡皮擦模式
}

View File

@@ -10,12 +10,13 @@
import { computed, onMounted } from 'vue' import { computed, onMounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
const route = useRoute() const route = useRoute()
const url = 'https://www.minio-api.aida.com.hk/fida-test/furniture/sketches/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20260320%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260320T020948Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=7dc192bac887bce7b02c99d7037c08d9d684310f00add9b0e63b74b36ee63d37' const url =
'https://www.minio-api.aida.com.hk/fida-test/furniture/sketches/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20260320%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260320T020948Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=7dc192bac887bce7b02c99d7037c08d9d684310f00add9b0e63b74b36ee63d37'
const openCanvas = () => { const openCanvas = () => {
myEvent.emit('openFlowCanvas', { url }) myEvent.emit('openFlowCanvas', { url })
} }
const openDepthCanvas = () => { const openDepthCanvas = () => {
myEvent.emit('openDepthCanvas', { url }) myEvent.emit('openDepthCanvas', { url, canvasId: '69c34539ce996b52f07e625f' })
} }
onMounted(() => { onMounted(() => {
if (route.query.depth) { if (route.query.depth) {