2025-06-09 10:25:54 +08:00
|
|
|
|
import { CompositeCommand } from "../../commands/Command.js";
|
|
|
|
|
|
import { PerformanceManager } from "./PerformanceManager.js";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 简化版命令管理器
|
|
|
|
|
|
* 基于经典撤销/重做模式,支持命令队列
|
|
|
|
|
|
* 使用复合命令替代事务处理
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class CommandManager {
|
|
|
|
|
|
constructor(options = {}) {
|
2026-01-02 11:24:11 +08:00
|
|
|
|
this.canvas = options.canvas;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.undoStack = [];
|
|
|
|
|
|
this.redoStack = [];
|
|
|
|
|
|
this.maxHistorySize = options.maxHistorySize || 50;
|
|
|
|
|
|
this.executing = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 命令执行队列
|
|
|
|
|
|
this.commandQueue = [];
|
|
|
|
|
|
this.processing = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 可选的性能管理器
|
|
|
|
|
|
this.performanceManager = options.performanceManager || null;
|
|
|
|
|
|
|
|
|
|
|
|
// 状态变化回调
|
|
|
|
|
|
this.onStateChange = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 兼容旧的executeCommand方法
|
|
|
|
|
|
async executeCommand(command) {
|
|
|
|
|
|
return this.execute(command);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 执行命令并添加到撤销栈
|
|
|
|
|
|
*/
|
|
|
|
|
|
async execute(command) {
|
|
|
|
|
|
if (!command || typeof command.execute !== "function") {
|
|
|
|
|
|
throw new Error("无效的命令对象");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return this._executeDirectly(command);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 直接执行命令(绕过事务检查)
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _executeDirectly(command) {
|
|
|
|
|
|
// 返回Promise,等待命令执行完成
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
// 将命令添加到队列
|
|
|
|
|
|
this.commandQueue.push({
|
|
|
|
|
|
type: "execute",
|
|
|
|
|
|
command,
|
|
|
|
|
|
resolve,
|
|
|
|
|
|
reject,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 开始处理队列
|
|
|
|
|
|
this._processQueue();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 撤销最后一个命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
// 检查是否可以撤销
|
|
|
|
|
|
if (this.undoStack.length === 0) {
|
|
|
|
|
|
console.warn("无法撤销:撤销栈为空");
|
|
|
|
|
|
resolve(null);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将撤销操作添加到队列
|
|
|
|
|
|
this.commandQueue.push({
|
|
|
|
|
|
type: "undo",
|
|
|
|
|
|
resolve,
|
|
|
|
|
|
reject,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 开始处理队列
|
|
|
|
|
|
this._processQueue();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 重做最后一个撤销的命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
async redo() {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
// 检查是否可以重做
|
|
|
|
|
|
if (this.redoStack.length === 0) {
|
|
|
|
|
|
console.warn("无法重做:重做栈为空");
|
|
|
|
|
|
resolve(null);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将重做操作添加到队列
|
|
|
|
|
|
this.commandQueue.push({
|
|
|
|
|
|
type: "redo",
|
|
|
|
|
|
resolve,
|
|
|
|
|
|
reject,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 开始处理队列
|
|
|
|
|
|
this._processQueue();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理命令队列
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _processQueue() {
|
|
|
|
|
|
// 如果正在处理或队列为空,直接返回
|
|
|
|
|
|
if (this.processing || this.commandQueue.length === 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.processing = true;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
while (this.commandQueue.length > 0) {
|
|
|
|
|
|
const task = this.commandQueue.shift();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
let result = null;
|
|
|
|
|
|
|
|
|
|
|
|
switch (task.type) {
|
|
|
|
|
|
case "execute":
|
|
|
|
|
|
result = await this._executeCommandInternal(task.command);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "undo":
|
|
|
|
|
|
result = await this._undoInternal();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "redo":
|
|
|
|
|
|
result = await this._redoInternal();
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new Error(`未知的任务类型: ${task.type}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
task.resolve(result);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
task.reject(error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.processing = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 内部执行命令方法
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _executeCommandInternal(command) {
|
|
|
|
|
|
this.executing = true;
|
|
|
|
|
|
const startTime = performance.now();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log(`🔄 执行命令: ${command.constructor.name}`);
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
const result = await this._executeCommand(command);
|
|
|
|
|
|
|
|
|
|
|
|
// 只有可撤销的命令才加入撤销栈
|
|
|
|
|
|
if (command.undoable !== false) {
|
|
|
|
|
|
this.undoStack.push(command);
|
|
|
|
|
|
this.redoStack = []; // 清空重做栈
|
|
|
|
|
|
|
|
|
|
|
|
// 限制历史记录大小
|
|
|
|
|
|
this._trimHistory();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 记录性能
|
|
|
|
|
|
const duration = performance.now() - startTime;
|
|
|
|
|
|
this._recordPerformance("execute", command.constructor.name, duration);
|
|
|
|
|
|
|
|
|
|
|
|
// 通知状态变化
|
|
|
|
|
|
this._notifyStateChange();
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`✅ 命令执行成功: ${command.constructor.name}`);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`❌ 命令执行失败: ${command.constructor.name}`, error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.executing = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 内部撤销方法
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _undoInternal() {
|
|
|
|
|
|
if (this.undoStack.length === 0) {
|
|
|
|
|
|
console.warn("无法撤销:撤销栈为空");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.executing = true;
|
|
|
|
|
|
const startTime = performance.now();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-01-02 11:24:11 +08:00
|
|
|
|
this.canvas?.discardActiveObject();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
const command = this.undoStack.pop();
|
|
|
|
|
|
console.log(`↩️ 撤销命令: ${command.constructor.name}`);
|
|
|
|
|
|
|
|
|
|
|
|
const result = await this._undoCommand(command);
|
|
|
|
|
|
|
|
|
|
|
|
this.redoStack.push(command);
|
|
|
|
|
|
|
|
|
|
|
|
// 记录性能
|
|
|
|
|
|
const duration = performance.now() - startTime;
|
|
|
|
|
|
this._recordPerformance("undo", command.constructor.name, duration);
|
|
|
|
|
|
|
|
|
|
|
|
// 通知状态变化
|
|
|
|
|
|
this._notifyStateChange();
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`✅ 命令撤销成功: ${command.constructor.name}`);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`❌ 命令撤销失败`, error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.executing = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 内部重做方法
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _redoInternal() {
|
|
|
|
|
|
if (this.redoStack.length === 0) {
|
|
|
|
|
|
console.warn("无法重做:重做栈为空");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.executing = true;
|
|
|
|
|
|
const startTime = performance.now();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-01-02 11:24:11 +08:00
|
|
|
|
this.canvas?.discardActiveObject();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
const command = this.redoStack.pop();
|
|
|
|
|
|
console.log(`↪️ 重做命令: ${command.constructor.name}`);
|
|
|
|
|
|
|
|
|
|
|
|
const result = await this._executeCommand(command);
|
|
|
|
|
|
|
|
|
|
|
|
this.undoStack.push(command);
|
|
|
|
|
|
|
|
|
|
|
|
// 记录性能
|
|
|
|
|
|
const duration = performance.now() - startTime;
|
|
|
|
|
|
this._recordPerformance("redo", command.constructor.name, duration);
|
|
|
|
|
|
|
|
|
|
|
|
// 通知状态变化
|
|
|
|
|
|
this._notifyStateChange();
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`✅ 命令重做成功: ${command.constructor.name}`);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`❌ 命令重做失败`, error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.executing = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 批量执行命令(使用 CompositeCommand)
|
|
|
|
|
|
* 推荐使用此方法替代原来的事务机制
|
|
|
|
|
|
*/
|
|
|
|
|
|
async executeBatch(commands, batchName = "批量操作") {
|
|
|
|
|
|
if (!Array.isArray(commands) || commands.length === 0) {
|
|
|
|
|
|
throw new Error("命令数组不能为空");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const compositeCommand = new CompositeCommand(commands, {
|
|
|
|
|
|
name: batchName,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return this.execute(compositeCommand);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清空历史记录
|
|
|
|
|
|
*/
|
|
|
|
|
|
clear() {
|
|
|
|
|
|
// 清空队列中的所有任务
|
|
|
|
|
|
while (this.commandQueue.length > 0) {
|
|
|
|
|
|
const task = this.commandQueue.shift();
|
|
|
|
|
|
task.reject(new Error("命令管理器已被清空"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.undoStack = [];
|
|
|
|
|
|
this.redoStack = [];
|
|
|
|
|
|
this._notifyStateChange();
|
|
|
|
|
|
console.log("📝 命令历史已清空");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取管理器状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
getState() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
canUndo: this.undoStack.length > 0,
|
|
|
|
|
|
canRedo: this.redoStack.length > 0,
|
|
|
|
|
|
undoCount: this.undoStack.length,
|
|
|
|
|
|
redoCount: this.redoStack.length,
|
|
|
|
|
|
isExecuting: this.executing,
|
|
|
|
|
|
isProcessing: this.processing,
|
|
|
|
|
|
queueLength: this.commandQueue.length,
|
|
|
|
|
|
|
|
|
|
|
|
lastCommand:
|
|
|
|
|
|
this.undoStack.length > 0
|
|
|
|
|
|
? this.undoStack[this.undoStack.length - 1].constructor.name
|
|
|
|
|
|
: null,
|
|
|
|
|
|
nextRedoCommand:
|
|
|
|
|
|
this.redoStack.length > 0
|
|
|
|
|
|
? this.redoStack[this.redoStack.length - 1].constructor.name
|
|
|
|
|
|
: null,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取命令历史信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
getHistory() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
undoHistory: this.undoStack.map((cmd) => ({
|
|
|
|
|
|
name: cmd.constructor.name,
|
|
|
|
|
|
info: cmd.getInfo ? cmd.getInfo() : {},
|
|
|
|
|
|
timestamp: cmd.timestamp,
|
|
|
|
|
|
})),
|
|
|
|
|
|
redoHistory: this.redoStack.map((cmd) => ({
|
|
|
|
|
|
name: cmd.constructor.name,
|
|
|
|
|
|
info: cmd.getInfo ? cmd.getInfo() : {},
|
|
|
|
|
|
timestamp: cmd.timestamp,
|
|
|
|
|
|
})),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setChangeCallback(callback) {
|
|
|
|
|
|
if (typeof callback === "function") {
|
|
|
|
|
|
this.onStateChange = callback;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error("回调必须是一个函数");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 执行单个命令
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _executeCommand(command) {
|
|
|
|
|
|
const result = command.execute();
|
|
|
|
|
|
return this._isPromise(result) ? await result : result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 撤销单个命令
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _undoCommand(command) {
|
|
|
|
|
|
if (typeof command.undo !== "function") {
|
|
|
|
|
|
throw new Error(`命令 ${command.constructor.name} 不支持撤销`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const result = command.undo();
|
|
|
|
|
|
return this._isPromise(result) ? await result : result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查是否为Promise
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_isPromise(value) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
value &&
|
|
|
|
|
|
typeof value === "object" &&
|
|
|
|
|
|
typeof value.then === "function" &&
|
|
|
|
|
|
typeof value.catch === "function"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 限制历史记录大小
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_trimHistory() {
|
|
|
|
|
|
if (this.undoStack.length > this.maxHistorySize) {
|
|
|
|
|
|
this.undoStack.shift();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 记录性能数据
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_recordPerformance(type, commandName, duration) {
|
|
|
|
|
|
if (this.performanceManager) {
|
|
|
|
|
|
if (type === "execute") {
|
|
|
|
|
|
this.performanceManager.recordExecution(commandName, duration);
|
|
|
|
|
|
} else if (type === "undo") {
|
|
|
|
|
|
this.performanceManager.recordUndo(commandName, duration);
|
|
|
|
|
|
} else if (type === "redo") {
|
|
|
|
|
|
this.performanceManager.recordRedo(commandName, duration);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 通知状态变化
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_notifyStateChange() {
|
|
|
|
|
|
if (this.onStateChange) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.onStateChange(this.getState());
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("状态变化回调执行失败:", error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建命令管理器实例的工厂函数
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createCommandManager(options = {}) {
|
|
|
|
|
|
return new CommandManager(options);
|
|
|
|
|
|
}
|