Quellcode durchsuchen

fix(picture-editor): 修复画布撤销功能并优化事件监听器管理

- 修复撤销条件判断,确保只有在有可撤销状态时才允许撤销
- 添加键盘快捷键支持 (Ctrl+Z) 实现撤销功能
- 实现事件监听器的正确清理机制,避免内存泄漏和重复绑定
- 优化撤销状态管理,防止在撤销过程中重复保存状态
- 在画布切换时重置撤销状态并保存初始状态
- 添加详细的日志输出便于调试撤销功能
- 分离核心修改事件和对象添加删除事件的监听处理
panqiuyao vor 1 Woche
Ursprung
Commit
1c8a9e0f62

+ 130 - 11
frontend/src/views/components/PictureEditor/mixin/actions/index.js

@@ -22,6 +22,8 @@ export default  {
       canvasState: [],//操作记录
       stateIndex: -1,
       canUndoState:false,
+      undoKeyHandler: null, // 键盘撤销事件处理器
+      undoEventHandlers: [], // Fabric事件处理器引用,用于清理
 
       //画笔在层上操作
       group:null,
@@ -46,7 +48,7 @@ export default  {
   computed: {
     //是否可以撤销
     canUndo() {
-      return this.stateIndex >= 0;
+      return this.stateIndex > 0;
     },
   },
   watch: {
@@ -90,6 +92,14 @@ export default  {
 
   methods: {
     actionInit(){
+      // 先清理旧的事件监听器
+      this.removeUndoListeners();
+
+      // 重置撤销状态(每次切换画布时)
+      this.canvasState = []
+      this.stateIndex = -1
+      this.canUndoState = false
+
       //监听撤销
       if(this.toolConfig.includes('undo')){
         this.setUndo()
@@ -697,33 +707,134 @@ export default  {
     },
     //撤销
     historyState(index){
-      if (this.canUndo) {
+      console.log("↶ Undo requested, index:", index, "canUndo:", this.canUndo, "current stateIndex:", this.stateIndex);
+
+      if (this.canUndo && index >= 0 && index < this.canvasState.length) {
+        console.log("↶ Executing undo to state:", index);
         this.canUndoState = true;
-        this.fcanvas.loadFromJSON(this.canvasState[index], () => {
+
+        const stateJson = this.canvasState[index];
+        console.log("↶ Loading state JSON length:", stateJson.length);
+
+        this.fcanvas.loadFromJSON(stateJson, () => {
+          console.log("↶ State loaded successfully");
           this.fcanvas.renderAll();
           this.stateIndex = index;
-          this.canvasState.splice(this.stateIndex+1);
-          this.getLayers()
+          this.canvasState.splice(this.stateIndex+1); // 删除当前状态之后的所有状态
+
+          console.log("↶ Undo completed, new stateIndex:", this.stateIndex, "remaining states:", this.canvasState.length);
+
+          // 在撤销过程中禁用状态更新,避免重复保存状态
+          this.getLayers(false)
           this.undoAfterSelectLayers();
           this.canUndoState = false;
          });
+      } else {
+        console.log("↶ Undo not possible:", { canUndo: this.canUndo, index, totalStates: this.canvasState.length });
       }
     },
     setUndo(){
-      //监听撤销
-      this.fcanvas.on("object:modified", ()=>{
-        this.updateCanvasState()
+      // 先移除之前的所有撤销相关事件监听器,避免重复绑定
+      this.removeUndoListeners();
+
+      // 保存事件处理器引用,用于后续清理
+      this.undoEventHandlers = [];
+
+      // 只监听核心的修改事件,避免过度触发
+      const coreEvents = [
+        "object:modified",    // 对象修改(移动、缩放、旋转等)
+        "path:created",       // 绘制路径
+      ];
+
+      coreEvents.forEach(eventName => {
+        const handler = (e) => {
+          console.log(`🔄 Undo event: ${eventName}`, {
+            type: e?.target?.type,
+            id: e?.target?.id,
+            left: e?.target?.left,
+            top: e?.target?.top
+          });
+          this.updateCanvasState();
+        };
+        this.undoEventHandlers.push({ event: eventName, handler });
+        this.fcanvas.on(eventName, handler);
       });
+
+      // 监听对象添加和删除事件
+      const addHandler = (e) => {
+        console.log(`➕ Object added:`, e?.target?.type, e?.target?.id);
+        this.updateCanvasState();
+      };
+      const removeHandler = (e) => {
+        console.log(`➖ Object removed:`, e?.target?.type, e?.target?.id);
+        this.updateCanvasState();
+      };
+
+      this.undoEventHandlers.push(
+        { event: "object:added", handler: addHandler },
+        { event: "object:removed", handler: removeHandler }
+      );
+
+      this.fcanvas.on("object:added", addHandler);
+      this.fcanvas.on("object:removed", removeHandler);
+
+      // 添加键盘快捷键监听 (Ctrl+Z)
+      this.setupKeyboardUndo();
+
+      console.log("🎯 Undo system initialized with", this.undoEventHandlers.length, "event listeners");
+    },
+
+    // 移除所有撤销相关的事件监听器
+    removeUndoListeners(){
+      if (!this.fcanvas || !this.undoEventHandlers) return;
+
+      this.undoEventHandlers.forEach(({ event, handler }) => {
+        this.fcanvas.off(event, handler);
+      });
+
+      this.undoEventHandlers = [];
+      console.log("🧹 Removed all undo event listeners");
+    },
+    setupKeyboardUndo(){
+      if (!this.fcanvas) return;
+
+      const canvasWrapper = this.fcanvas.wrapperEl;
+      if (!canvasWrapper) return;
+
+      // 移除之前可能存在的监听器
+      if (this.undoKeyHandler) {
+        canvasWrapper.removeEventListener('keydown', this.undoKeyHandler);
+      }
+
+      // 添加新的键盘事件监听
+      this.undoKeyHandler = (e) => {
+        // Ctrl+Z 或 Cmd+Z (Mac)
+        if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
+          e.preventDefault();
+          e.stopPropagation();
+          if (this.canUndo) {
+            this.historyState(this.stateIndex - 1);
+          }
+        }
+      };
+
+      canvasWrapper.addEventListener('keydown', this.undoKeyHandler);
     },
     updateCanvasState(e) {
+      console.log("🔄 updateCanvasState called, canUndoState:", this.canUndoState);
+
       if (!this.canUndoState) {
         const canvasAsJson = JSON.stringify(this.fcanvas.toJSON(['name','sort','mtr','id','selectable','erasable']));
         this.canvasState.splice(this.stateIndex+1);
         this.canvasState.push(canvasAsJson);
         this.stateIndex = this.canvasState.length - 1;
+
+        console.log("💾 Canvas state saved, total states:", this.canvasState.length, "current index:", this.stateIndex);
+
        //  if(this.viewStatus) this.updateMiniMap();
 
       } else {
+        console.log("🚫 Skipping state save due to canUndoState flag");
         this.canUndoState = false;
       }
     },
@@ -817,9 +928,17 @@ export default  {
       if (!this.fcanvas) return
 
       const canvasWrapper = this.fcanvas.wrapperEl
-      if (canvasWrapper && this.handleKeyDown) {
-        canvasWrapper.removeEventListener('keydown', this.handleKeyDown)
-        this.handleKeyDown = null
+      if (canvasWrapper) {
+        // 清理键盘导航监听器
+        if (this.handleKeyDown) {
+          canvasWrapper.removeEventListener('keydown', this.handleKeyDown)
+          this.handleKeyDown = null
+        }
+        // 清理撤销键盘监听器
+        if (this.undoKeyHandler) {
+          canvasWrapper.removeEventListener('keydown', this.undoKeyHandler)
+          this.undoKeyHandler = null
+        }
       }
     }
 

+ 4 - 1
frontend/src/views/components/marketingEdit/index.vue

@@ -261,12 +261,15 @@ export default {
       }))
       this.fcanvasId = canvasId
       const hydrateCanvas = () => {
-        this.updateCanvasState()
         // this.minimapInit()
         this.actionInit()
         this.layerInit();
         this.loading = false // 加载完成
+
+        // 在所有初始化完成后,保存当前画布状态作为初始状态
+        // 这样用户就可以撤销到刚切换到画布时的状态
         this.$nextTick(() => {
+          this.updateCanvasState()
           this.$emit('init')
         })
       }