Tz2/scene-step-editor-web/script-fixed.js

1441 lines
56 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 主脚本文件 - Scene Step 编辑器 (修复版)
function SceneStepEditor() {
this.fileManager = new FileManager();
this.codeGenerator = new CodeGenerator();
this.currentSteps = [];
this.stepFoldouts = [];
this.actionFoldouts = [];
this.initializeElements();
this.bindEvents();
this.loadInitialData();
}
// 初始化DOM元素引用
SceneStepEditor.prototype.initializeElements = function() {
this.fileSelect = document.getElementById('fileSelect');
this.newProcessBtn = document.getElementById('newProcessBtn');
this.stepsContainer = document.getElementById('stepsContainer');
this.saveBtn = document.getElementById('saveBtn');
this.generateCodeBtn = document.getElementById('generateCodeBtn');
// 目录选择相关元素
this.selectDirBtn = document.getElementById('selectDirBtn');
this.directoryInput = document.getElementById('directoryInput');
this.currentDirDisplay = document.getElementById('currentDirDisplay');
// 树状导航相关元素
this.sidebar = document.getElementById('sidebar');
this.sidebarToggle = document.getElementById('sidebarToggle');
this.treeContainer = document.getElementById('treeContainer');
this.scrollContainer = document.getElementById('scrollContainer');
// 模态框元素
this.newProcessModal = document.getElementById('newProcessModal');
this.newProcessName = document.getElementById('newProcessName');
this.confirmNewProcess = document.getElementById('confirmNewProcess');
this.cancelNewProcess = document.getElementById('cancelNewProcess');
this.confirmModal = document.getElementById('confirmModal');
this.confirmTitle = document.getElementById('confirmTitle');
this.confirmMessage = document.getElementById('confirmMessage');
this.confirmOk = document.getElementById('confirmOk');
this.confirmCancel = document.getElementById('confirmCancel');
this.codeModal = document.getElementById('codeModal');
this.generatedCode = document.getElementById('generatedCode');
this.copyCodeBtn = document.getElementById('copyCodeBtn');
this.closeCodeModal = document.getElementById('closeCodeModal');
};
// 绑定事件
SceneStepEditor.prototype.bindEvents = function() {
var self = this;
// 文件选择
this.fileSelect.addEventListener('change', function(e) {
self.loadProcessFile(e.target.value);
});
// 新增流程
this.newProcessBtn.addEventListener('click', function() {
self.showNewProcessModal();
});
// 目录选择 - 智能判断使用哪种方式
this.selectDirBtn.addEventListener('click', function(e) {
e.preventDefault();
self.handleDirectoryButtonClick();
});
this.directoryInput.addEventListener('change', function(e) {
self.handleLegacyDirectorySelection(e);
});
// 保存
this.saveBtn.addEventListener('click', function() {
self.saveConfiguration();
});
// 生成代码
this.generateCodeBtn.addEventListener('click', function() {
self.generateProcessEventsCode();
});
// 新增流程模态框
this.confirmNewProcess.addEventListener('click', function() {
self.createNewProcess();
});
this.cancelNewProcess.addEventListener('click', function() {
self.hideNewProcessModal();
});
// 确认模态框
this.confirmOk.addEventListener('click', function() {
// 先保存回调函数避免在hideConfirmModal中被清空
var callback = self.confirmCallback;
self.hideConfirmModal();
if (callback) {
callback();
}
});
this.confirmCancel.addEventListener('click', function() {
self.hideConfirmModal();
});
// 代码模态框
this.copyCodeBtn.addEventListener('click', function() {
self.copyGeneratedCode();
});
this.closeCodeModal.addEventListener('click', function() {
self.hideCodeModal();
});
// 点击模态框背景关闭
window.addEventListener('click', function(e) {
if (e.target === self.newProcessModal) {
self.hideNewProcessModal();
}
if (e.target === self.confirmModal) {
self.hideConfirmModal();
}
if (e.target === self.codeModal) {
self.hideCodeModal();
}
});
// 键盘事件
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
self.hideNewProcessModal();
self.hideConfirmModal();
self.hideCodeModal();
}
});
// 侧边栏切换
this.sidebarToggle.addEventListener('click', function() {
self.toggleSidebar();
});
};
// 加载初始数据
SceneStepEditor.prototype.loadInitialData = function() {
var self = this;
// 更新界面提示信息
this.updateDirectoryHelp();
// 尝试恢复之前保存的目录句柄
if (this.fileManager.supportsFileSystemAccess) {
this.fileManager.restoreDirectoryHandle().then(function(handle) {
if (handle) {
console.log("已恢复目录句柄,自动加载文件");
self.fileManager.loadFilesFromDirectoryHandle(handle).then(function(result) {
if (result.success) {
self.updateFileDropdown();
self.renderTreeNavigation();
self.showAlert("目录已恢复",
"已自动恢复之前选择的目录 '" + result.directory + "' 并读取 " + result.loadedFiles + " 个文件");
}
});
} else {
// 加载默认文件
self.loadProcessFile(self.fileManager.getCurrentFileName());
}
});
} else {
// 加载默认文件
this.loadProcessFile(this.fileManager.getCurrentFileName());
}
// 更新文件下拉列表
this.updateFileDropdown();
};
// 更新目录帮助信息
SceneStepEditor.prototype.updateDirectoryHelp = function() {
var helpElement = document.getElementById('directoryHelp');
if (!helpElement) return;
if (this.fileManager.supportsFileSystemAccess) {
helpElement.innerHTML = '🎉 您的浏览器支持直接文件写入!选择项目文件夹后,新建和保存的文件将直接写入该文件夹';
helpElement.style.borderLeft = '2px solid #28a745';
helpElement.style.background = 'rgba(40, 167, 69, 0.1)';
} else {
helpElement.innerHTML = '⚠️ 您的浏览器不支持直接文件写入,新建和保存的文件将下载到默认下载文件夹';
helpElement.style.borderLeft = '2px solid #ffc107';
helpElement.style.background = 'rgba(255, 193, 7, 0.1)';
}
};
// 更新文件下拉列表
SceneStepEditor.prototype.updateFileDropdown = function() {
var fileNames = this.fileManager.getFilesInCurrentDirectory();
var currentFile = this.fileManager.getCurrentFileName();
var currentDir = this.fileManager.getCurrentDirectory();
this.fileSelect.innerHTML = '';
for (var i = 0; i < fileNames.length; i++) {
var fileName = fileNames[i];
var option = document.createElement('option');
option.value = fileName;
option.textContent = fileName;
if (fileName === currentFile) {
option.selected = true;
}
this.fileSelect.appendChild(option);
}
// 更新当前目录显示
this.currentDirDisplay.textContent = '当前目录: ' + currentDir;
// 更新保存按钮文本
this.saveBtn.textContent = '保存到 ' + currentFile;
};
// 加载流程文件
SceneStepEditor.prototype.loadProcessFile = function(fileName) {
if (!fileName) return;
if (this.fileManager.setCurrentFile(fileName)) {
this.currentSteps = this.fileManager.loadProcessFile(fileName);
this.stepFoldouts = [];
this.actionFoldouts = [];
for (var i = 0; i < this.currentSteps.length; i++) {
this.stepFoldouts.push(true);
var actionFoldout = [];
for (var j = 0; j < this.currentSteps[i].Actions.length; j++) {
actionFoldout.push(true);
}
this.actionFoldouts.push(actionFoldout);
}
this.updateFileDropdown();
this.renderSteps();
}
};
// 渲染步骤列表
SceneStepEditor.prototype.renderSteps = function() {
this.stepsContainer.innerHTML = '';
if (this.currentSteps.length === 0) {
this.renderEmptyState();
this.renderTreeNavigation(); // 同时更新树状导航
return;
}
for (var i = 0; i < this.currentSteps.length; i++) {
this.renderStep(this.currentSteps[i], i);
}
// 渲染树状导航
this.renderTreeNavigation();
};
// 渲染空状态
SceneStepEditor.prototype.renderEmptyState = function() {
var emptyDiv = document.createElement('div');
emptyDiv.className = 'empty-state';
emptyDiv.innerHTML =
'<p>暂无步骤</p>' +
'<div class="add-button-container">' +
'<button class="btn-add-large" onclick="editor.addNewStep()">添加第一个步骤</button>' +
'</div>';
this.stepsContainer.appendChild(emptyDiv);
};
// HTML转义函数
SceneStepEditor.prototype.escapeHtml = function(text) {
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
};
// 渲染单个步骤
SceneStepEditor.prototype.renderStep = function(step, stepIndex) {
var stepDiv = document.createElement('div');
stepDiv.className = 'step-item';
// 确保折叠状态数组长度正确
while (this.stepFoldouts.length <= stepIndex) {
this.stepFoldouts.push(true);
}
while (this.actionFoldouts.length <= stepIndex) {
this.actionFoldouts.push([]);
}
var isExpanded = this.stepFoldouts[stepIndex];
stepDiv.innerHTML =
'<div class="separator-line gray"></div>' +
'<div class="step-header">' +
'<div class="step-controls">' +
(stepIndex === this.currentSteps.length - 1 ?
'<button class="btn btn-small btn-add" onclick="editor.addNewStep()" title="添加步骤">+</button>' :
''
) +
'<button class="btn btn-small btn-remove" onclick="editor.removeStep(' + stepIndex + ')" title="删除步骤">-</button>' +
'</div>' +
'<button class="foldout ' + (isExpanded ? 'expanded' : '') + '" onclick="editor.toggleStepFoldout(' + stepIndex + ')">' +
this.escapeHtml(step.StepDescription) + ' 编号: ' + stepIndex +
'</button>' +
'</div>' +
'<div class="step-content ' + (isExpanded ? 'expanded' : '') + '">' +
'<div class="form-group">' +
'<label>描述</label>' +
'<input type="text" class="form-control" value="' + this.escapeHtml(step.StepDescription) + '" ' +
'onchange="editor.updateStepDescription(' + stepIndex + ', this.value)">' +
'</div>' +
'<div class="actions-container" id="actionsContainer_' + stepIndex + '">' +
'<!-- 动作将在这里渲染 -->' +
'</div>' +
'</div>' +
'<div class="separator-line green"></div>';
this.stepsContainer.appendChild(stepDiv);
// 渲染动作列表
if (isExpanded) {
this.renderActions(stepIndex);
}
};
// 渲染动作列表
SceneStepEditor.prototype.renderActions = function(stepIndex) {
var actionsContainer = document.getElementById('actionsContainer_' + stepIndex);
if (!actionsContainer) return;
var step = this.currentSteps[stepIndex];
actionsContainer.innerHTML = '';
if (step.Actions.length === 0) {
var emptyDiv = document.createElement('div');
emptyDiv.className = 'empty-state';
emptyDiv.innerHTML =
'<p>暂无动作</p>' +
'<div class="add-button-container">' +
'<button class="btn-add-large" onclick="editor.addNewAction(' + stepIndex + ')">添加第一个动作</button>' +
'</div>';
actionsContainer.appendChild(emptyDiv);
return;
}
for (var i = 0; i < step.Actions.length; i++) {
this.renderAction(stepIndex, step.Actions[i], i, actionsContainer);
}
};
// 渲染单个动作
SceneStepEditor.prototype.renderAction = function(stepIndex, action, actionIndex, container) {
// 确保动作折叠状态数组长度正确
while (this.actionFoldouts[stepIndex].length <= actionIndex) {
this.actionFoldouts[stepIndex].push(true);
}
var isExpanded = this.actionFoldouts[stepIndex][actionIndex];
var actionDiv = document.createElement('div');
actionDiv.className = 'action-item';
actionDiv.innerHTML =
'<div class="action-header">' +
'<div class="step-controls">' +
(actionIndex === this.currentSteps[stepIndex].Actions.length - 1 ?
'<button class="btn btn-small btn-add" onclick="editor.addNewAction(' + stepIndex + ')" title="添加动作">+</button>' :
''
) +
'<button class="btn btn-small btn-remove" onclick="editor.removeAction(' + stepIndex + ', ' + actionIndex + ')" title="删除动作">-</button>' +
'</div>' +
'<button class="foldout ' + (isExpanded ? 'expanded' : '') + '" onclick="editor.toggleActionFoldout(' + stepIndex + ', ' + actionIndex + ')">' +
this.escapeHtml(action.Title) + ' 编号: ' + stepIndex + '-' + actionIndex +
'</button>' +
'</div>' +
'<div class="action-content ' + (isExpanded ? 'expanded' : '') + '" id="actionContent_' + stepIndex + '_' + actionIndex + '">' +
'<!-- 动作详情将在这里渲染 -->' +
'</div>';
container.appendChild(actionDiv);
// 渲染动作详情
if (isExpanded) {
this.renderActionDetails(stepIndex, actionIndex);
}
};
// 基本的方法实现
SceneStepEditor.prototype.toggleStepFoldout = function(stepIndex) {
this.stepFoldouts[stepIndex] = !this.stepFoldouts[stepIndex];
this.renderSteps();
};
SceneStepEditor.prototype.toggleActionFoldout = function(stepIndex, actionIndex) {
this.actionFoldouts[stepIndex][actionIndex] = !this.actionFoldouts[stepIndex][actionIndex];
this.renderActions(stepIndex);
};
SceneStepEditor.prototype.updateStepDescription = function(stepIndex, value) {
if (this.currentSteps[stepIndex]) {
this.currentSteps[stepIndex].StepDescription = value;
// 同步更新树状导航显示
this.renderTreeNavigation();
}
};
SceneStepEditor.prototype.addNewStep = function() {
var newStep = DataUtils.createNewStep();
this.currentSteps.push(newStep);
this.stepFoldouts.push(true);
this.actionFoldouts.push([]);
this.renderSteps();
};
SceneStepEditor.prototype.removeStep = function(stepIndex) {
var self = this;
this.showConfirmModal(
"确认删除",
"确定要删除步骤 \"" + this.currentSteps[stepIndex].StepDescription + "\" 吗?",
function() {
self.currentSteps.splice(stepIndex, 1);
self.stepFoldouts.splice(stepIndex, 1);
self.actionFoldouts.splice(stepIndex, 1);
self.renderSteps();
}
);
};
SceneStepEditor.prototype.addNewAction = function(stepIndex) {
var newAction = DataUtils.createNewAction();
this.currentSteps[stepIndex].Actions.push(newAction);
this.actionFoldouts[stepIndex].push(true);
this.renderActions(stepIndex);
// 同步更新树状导航
this.renderTreeNavigation();
};
SceneStepEditor.prototype.removeAction = function(stepIndex, actionIndex) {
var self = this;
var action = this.currentSteps[stepIndex].Actions[actionIndex];
this.showConfirmModal(
"确认删除",
"确定要删除动作 \"" + action.Title + "\" 吗?",
function() {
self.currentSteps[stepIndex].Actions.splice(actionIndex, 1);
self.actionFoldouts[stepIndex].splice(actionIndex, 1);
self.renderActions(stepIndex);
// 同步更新树状导航
self.renderTreeNavigation();
}
);
};
SceneStepEditor.prototype.renderActionDetails = function(stepIndex, actionIndex) {
var actionContent = document.getElementById('actionContent_' + stepIndex + '_' + actionIndex);
if (!actionContent) return;
var action = this.currentSteps[stepIndex].Actions[actionIndex];
actionContent.innerHTML =
'<div class="form-group">' +
'<label>标题</label>' +
'<input type="text" class="form-control" value="' + this.escapeHtml(action.Title) + '" ' +
'onchange="editor.updateActionProperty(' + stepIndex + ', ' + actionIndex + ', \'Title\', this.value)">' +
'</div>' +
'<div class="form-group">' +
'<label>描述</label>' +
'<input type="text" class="form-control" value="' + this.escapeHtml(action.Description) + '" ' +
'onchange="editor.updateActionProperty(' + stepIndex + ', ' + actionIndex + ', \'Description\', this.value)">' +
'</div>' +
'<div class="form-group">' +
'<label>动作类型</label>' +
'<select class="form-control" onchange="editor.updateActionType(' + stepIndex + ', ' + actionIndex + ', parseInt(this.value))">' +
'<option value="' + ProcessActionType.DEFAULT + '"' + (action.ActionType === ProcessActionType.DEFAULT ? ' selected' : '') + '>默认</option>' +
'<option value="' + ProcessActionType.JUDGMENT + '"' + (action.ActionType === ProcessActionType.JUDGMENT ? ' selected' : '') + '>判断题</option>' +
'<option value="' + ProcessActionType.MULTIPLE_CHOICE + '"' + (action.ActionType === ProcessActionType.MULTIPLE_CHOICE ? ' selected' : '') + '>多选题</option>' +
'</select>' +
'</div>' +
'<div id="actionTypeContent_' + stepIndex + '_' + actionIndex + '">' +
'<!-- 根据动作类型显示不同内容 -->' +
'</div>';
this.renderActionTypeContent(stepIndex, actionIndex);
};
SceneStepEditor.prototype.updateActionProperty = function(stepIndex, actionIndex, property, value) {
if (this.currentSteps[stepIndex] && this.currentSteps[stepIndex].Actions[actionIndex]) {
this.currentSteps[stepIndex].Actions[actionIndex][property] = value;
// 如果修改的是Title属性需要更新树状导航显示
if (property === 'Title') {
this.renderTreeNavigation();
}
}
};
SceneStepEditor.prototype.updateActionType = function(stepIndex, actionIndex, newType) {
var action = this.currentSteps[stepIndex].Actions[actionIndex];
if (action.ActionType !== newType) {
action.ActionType = newType;
// 清理不相关的数据
if (newType === ProcessActionType.DEFAULT) {
action.JudgmentQuestions = [];
action.MultipleChoiceQuestions = [];
if (!action.TargetObjects) {
action.TargetObjects = [];
}
} else if (newType === ProcessActionType.JUDGMENT) {
action.TargetObjects = [];
action.MultipleChoiceQuestions = [];
action.IsSequential = false;
if (!action.JudgmentQuestions) {
action.JudgmentQuestions = [];
}
} else if (newType === ProcessActionType.MULTIPLE_CHOICE) {
action.TargetObjects = [];
action.JudgmentQuestions = [];
action.IsSequential = false;
if (!action.MultipleChoiceQuestions) {
action.MultipleChoiceQuestions = [];
}
}
this.renderActionTypeContent(stepIndex, actionIndex);
}
};
SceneStepEditor.prototype.renderActionTypeContent = function(stepIndex, actionIndex) {
var actionTypeContent = document.getElementById('actionTypeContent_' + stepIndex + '_' + actionIndex);
if (!actionTypeContent) return;
var action = this.currentSteps[stepIndex].Actions[actionIndex];
var content =
'<div class="form-group">' +
'<label>分数</label>' +
'<input type="number" class="form-control" value="' + action.Score + '" step="0.1"' +
' onchange="editor.updateActionProperty(' + stepIndex + ', ' + actionIndex + ', \'Score\', parseFloat(this.value) || 0)">' +
'</div>' +
'<div class="form-group">' +
'<label>' +
'<input type="checkbox" class="checkbox"' + (action.RequireCorrectCompletion ? ' checked' : '') +
' onchange="editor.updateActionProperty(' + stepIndex + ', ' + actionIndex + ', \'RequireCorrectCompletion\', this.checked)">' +
' 需要正确完成' +
'</label>' +
'</div>';
if (action.ActionType === ProcessActionType.DEFAULT) {
content +=
'<div class="form-group">' +
'<label>' +
'<input type="checkbox" class="checkbox"' + (action.IsSequential ? ' checked' : '') +
' onchange="editor.updateActionProperty(' + stepIndex + ', ' + actionIndex + ', \'IsSequential\', this.checked)">' +
' 按顺序点击' +
'</label>' +
'</div>' +
'<div id="targetObjectsContainer_' + stepIndex + '_' + actionIndex + '">' +
'<!-- 目标对象列表 -->' +
'</div>';
} else if (action.ActionType === ProcessActionType.JUDGMENT) {
content +=
'<div id="judgmentQuestionsContainer_' + stepIndex + '_' + actionIndex + '">' +
'<!-- 判断题列表 -->' +
'</div>';
} else if (action.ActionType === ProcessActionType.MULTIPLE_CHOICE) {
content +=
'<div id="multipleChoiceContainer_' + stepIndex + '_' + actionIndex + '">' +
'<!-- 多选题列表 -->' +
'</div>';
}
actionTypeContent.innerHTML = content;
// 渲染具体内容
if (action.ActionType === ProcessActionType.DEFAULT) {
this.renderTargetObjects(stepIndex, actionIndex);
} else if (action.ActionType === ProcessActionType.JUDGMENT) {
this.renderJudgmentQuestions(stepIndex, actionIndex);
} else if (action.ActionType === ProcessActionType.MULTIPLE_CHOICE) {
this.renderMultipleChoiceQuestions(stepIndex, actionIndex);
}
};
// 渲染目标对象列表
SceneStepEditor.prototype.renderTargetObjects = function(stepIndex, actionIndex) {
var container = document.getElementById('targetObjectsContainer_' + stepIndex + '_' + actionIndex);
if (!container) return;
var action = this.currentSteps[stepIndex].Actions[actionIndex];
var content = '<h4>目标对象列表</h4>';
if (action.TargetObjects.length === 0) {
content +=
'<div class="empty-state">' +
'<p>暂无目标对象</p>' +
'<div class="add-button-container">' +
'<button class="btn-add-large" onclick="editor.addTargetObject(' + stepIndex + ', ' + actionIndex + ')">添加目标对象</button>' +
'</div>' +
'</div>';
} else {
for (var i = 0; i < action.TargetObjects.length; i++) {
var target = action.TargetObjects[i];
content +=
'<div class="target-object-item">' +
'<div class="target-object-header">' +
'<strong>目标对象 ' + (i + 1) + '</strong>' +
'<button class="btn btn-small btn-remove" onclick="editor.removeTargetObject(' + stepIndex + ', ' + actionIndex + ', ' + i + ')" title="删除">-</button>' +
'</div>' +
'<div class="form-group">' +
'<label>对象名称</label>' +
'<input type="text" class="form-control" value="' + this.escapeHtml(target.ObjectName) + '" ' +
'onchange="editor.updateTargetObjectProperty(' + stepIndex + ', ' + actionIndex + ', ' + i + ', \'ObjectName\', this.value)">' +
'</div>' +
'<div class="form-group">' +
'<label>对象类型</label>' +
'<select class="form-control" onchange="editor.updateTargetObjectProperty(' + stepIndex + ', ' + actionIndex + ', ' + i + ', \'Type\', parseInt(this.value))">' +
'<option value="' + ProcessTargetType.MODEL + '"' + (target.Type === ProcessTargetType.MODEL ? ' selected' : '') + '>Model</option>' +
'<option value="' + ProcessTargetType.EVENT + '"' + (target.Type === ProcessTargetType.EVENT ? ' selected' : '') + '>Event</option>' +
'</select>' +
'</div>' +
'<div class="form-group">' +
'<label>分数</label>' +
'<input type="number" class="form-control" value="' + target.Score + '" step="0.1"' +
' onchange="editor.updateTargetObjectProperty(' + stepIndex + ', ' + actionIndex + ', ' + i + ', \'Score\', parseFloat(this.value) || 0)">' +
'</div>' +
'</div>';
}
content +=
'<div class="add-button-container">' +
'<button class="btn-add-large" onclick="editor.addTargetObject(' + stepIndex + ', ' + actionIndex + ')">添加目标对象</button>' +
'</div>';
}
container.innerHTML = content;
};
// 添加目标对象
SceneStepEditor.prototype.addTargetObject = function(stepIndex, actionIndex) {
var newTarget = DataUtils.createNewTargetObject();
this.currentSteps[stepIndex].Actions[actionIndex].TargetObjects.push(newTarget);
this.renderTargetObjects(stepIndex, actionIndex);
};
// 删除目标对象
SceneStepEditor.prototype.removeTargetObject = function(stepIndex, actionIndex, targetIndex) {
var self = this;
var target = this.currentSteps[stepIndex].Actions[actionIndex].TargetObjects[targetIndex];
this.showConfirmModal(
"确认删除",
"确定要删除目标对象 \"" + target.ObjectName + "\" 吗?",
function() {
self.currentSteps[stepIndex].Actions[actionIndex].TargetObjects.splice(targetIndex, 1);
self.renderTargetObjects(stepIndex, actionIndex);
}
);
};
// 更新目标对象属性
SceneStepEditor.prototype.updateTargetObjectProperty = function(stepIndex, actionIndex, targetIndex, property, value) {
var target = this.currentSteps[stepIndex].Actions[actionIndex].TargetObjects[targetIndex];
if (target) {
target[property] = value;
}
};
// 模态框方法
SceneStepEditor.prototype.showNewProcessModal = function() {
this.newProcessName.value = "新流程";
this.newProcessModal.style.display = 'block';
this.newProcessName.focus();
this.newProcessName.select();
};
SceneStepEditor.prototype.hideNewProcessModal = function() {
this.newProcessModal.style.display = 'none';
};
SceneStepEditor.prototype.createNewProcess = function() {
var fileName = this.newProcessName.value.trim();
if (!fileName) {
alert("请输入流程名称");
return;
}
var self = this;
// 尝试直接写入到选择的目录
if (this.fileManager.supportsFileSystemAccess && this.fileManager.directoryHandle) {
this.fileManager.writeFileToDirectory(fileName, []).then(function(result) {
self.hideNewProcessModal();
self.updateFileDropdown();
self.loadProcessFile(result.fileName);
self.showAlert("新流程创建成功",
"已在目录 '" + result.directory + "' 中直接创建新流程:" + result.fileName +
"\n\n🎉 文件已直接保存到您选择的项目文件夹中!");
}).catch(function(error) {
console.error("直接写入失败,降级到下载模式:", error);
self.createNewProcessFallback(fileName);
});
return;
}
// 降级到传统模式
this.createNewProcessFallback(fileName);
};
// 降级的新建流程方法
SceneStepEditor.prototype.createNewProcessFallback = function(fileName) {
var result = this.fileManager.createNewProcessFileInCurrentDir(fileName);
if (result.success) {
this.hideNewProcessModal();
this.updateFileDropdown();
this.loadProcessFile(result.fileName);
// 触发下载保存到本地
this.downloadProcessFile(result.fileName);
var message = "已创建新流程:" + result.fileName;
if (this.fileManager.directoryHandle) {
message += "\n\n💡 文件已自动下载,请将其保存到您选择的项目文件夹 '" + result.directory + "' 中,然后重新选择该文件夹以刷新文件列表。";
} else {
message += "\n\n💡 文件已自动下载到默认下载文件夹。建议先选择项目文件夹以启用直接保存功能。";
}
this.showAlert("新流程创建成功", message);
} else {
alert("创建失败:" + result.error);
}
};
// 处理目录按钮点击
SceneStepEditor.prototype.handleDirectoryButtonClick = function() {
var self = this;
// 优先使用 File System Access API
if (this.fileManager.supportsFileSystemAccess) {
this.currentDirDisplay.textContent = "正在选择目录...";
this.selectDirBtn.disabled = true;
this.selectDirBtn.textContent = "📂 正在选择目录...";
this.fileManager.selectDirectoryWithAPI().then(function(result) {
self.selectDirBtn.disabled = false;
self.selectDirBtn.textContent = "📂 选择本地项目文件夹";
if (result.success) {
// 更新界面
self.updateFileDropdown();
// 如果有文件,加载第一个
var fileNames = self.fileManager.getFilesInCurrentDirectory();
if (fileNames.length > 0) {
var firstFile = fileNames[0];
self.fileManager.setCurrentFile(firstFile);
self.loadProcessFile(firstFile);
}
self.showAlert("目录选择成功",
"已选择目录 '" + result.directory + "' 并读取 " + result.loadedFiles + " 个JSON流程文件\n\n🎉 现在新建和保存的文件将直接写入此目录!");
// 重新渲染树状导航
self.renderTreeNavigation();
} else {
self.currentDirDisplay.textContent = "当前目录: " + self.fileManager.getCurrentDirectory();
alert("选择目录失败:" + result.error);
}
}).catch(function(error) {
self.selectDirBtn.disabled = false;
self.selectDirBtn.textContent = "📂 选择本地项目文件夹";
self.currentDirDisplay.textContent = "当前目录: " + self.fileManager.getCurrentDirectory();
// 如果用户取消选择,不显示错误
if (error.name === 'AbortError') {
console.log("用户取消了目录选择");
return;
}
alert("选择目录时发生错误:" + error.message);
});
return;
}
// 降级到传统文件选择方式
console.log("降级到传统文件选择方式");
this.directoryInput.click();
};
// 处理传统的目录选择(通过 file input
SceneStepEditor.prototype.handleLegacyDirectorySelection = function(event) {
var self = this;
var files = event.target.files;
if (!files || files.length === 0) {
return;
}
// 显示加载提示
this.currentDirDisplay.textContent = "正在读取本地文件夹...";
this.selectDirBtn.disabled = true;
this.selectDirBtn.textContent = "📂 正在读取文件夹...";
this.fileManager.loadFilesFromDirectory(files).then(function(result) {
self.selectDirBtn.disabled = false;
self.selectDirBtn.textContent = "📂 选择本地项目文件夹";
if (result.success) {
// 更新界面
self.updateFileDropdown();
// 如果有文件,加载第一个
var fileNames = self.fileManager.getFilesInCurrentDirectory();
if (fileNames.length > 0) {
var firstFile = fileNames[0];
self.fileManager.setCurrentFile(firstFile);
self.loadProcessFile(firstFile);
}
self.showAlert("本地文件夹读取成功",
"已从本地 " + result.directory + " 文件夹读取 " + result.loadedFiles + " 个JSON流程文件\n\n⚠ 由于浏览器限制,新建和保存的文件将下载到默认下载文件夹,请手动移动到项目文件夹中");
// 重新渲染树状导航
self.renderTreeNavigation();
} else {
self.currentDirDisplay.textContent = "当前目录: " + self.fileManager.getCurrentDirectory();
alert("读取失败:" + result.error);
}
}).catch(function(error) {
self.selectDirBtn.disabled = false;
self.selectDirBtn.textContent = "📂 选择本地项目文件夹";
self.currentDirDisplay.textContent = "当前目录: " + self.fileManager.getCurrentDirectory();
alert("读取本地文件夹时发生错误:" + error.message);
});
};
SceneStepEditor.prototype.showConfirmModal = function(title, message, callback) {
this.confirmTitle.textContent = title;
this.confirmMessage.textContent = message;
this.confirmCallback = callback;
this.confirmModal.style.display = 'block';
};
SceneStepEditor.prototype.hideConfirmModal = function() {
this.confirmModal.style.display = 'none';
this.confirmCallback = null;
};
SceneStepEditor.prototype.showCodeModal = function(code) {
this.generatedCode.value = code;
this.codeModal.style.display = 'block';
};
SceneStepEditor.prototype.hideCodeModal = function() {
this.codeModal.style.display = 'none';
};
SceneStepEditor.prototype.copyGeneratedCode = function() {
this.generatedCode.select();
document.execCommand('copy');
this.showAlert("复制成功", "代码已复制到剪贴板");
};
SceneStepEditor.prototype.showAlert = function(title, message) {
alert(title + "\n" + message);
};
// 下载流程文件到本地
SceneStepEditor.prototype.downloadProcessFile = function(fileName) {
try {
var steps = this.fileManager.loadProcessFile(fileName);
var jsonString = JSON.stringify(steps, null, 2);
// 创建Blob对象
var blob = new Blob([jsonString], { type: 'application/json' });
// 创建下载链接
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = fileName;
// 隐藏链接并触发点击
a.style.display = 'none';
document.body.appendChild(a);
a.click();
// 清理
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
console.log("文件下载触发: " + fileName);
} catch (error) {
console.error("下载文件失败: ", error);
alert("下载文件失败:" + error.message);
}
};
SceneStepEditor.prototype.saveConfiguration = function() {
var self = this;
var currentFileName = this.fileManager.getCurrentFileName();
var currentDir = this.fileManager.getCurrentDirectory();
var confirmMessage = "是否要保存当前配置到 " + currentFileName + "";
if (this.fileManager.supportsFileSystemAccess && this.fileManager.directoryHandle) {
confirmMessage += "\n\n🎉 文件将直接保存到您选择的项目文件夹 '" + currentDir + "' 中。";
} else {
confirmMessage += "\n\n💡 文件将自动下载到您的下载文件夹,请手动移动到项目文件夹中。";
}
this.showConfirmModal("确认保存", confirmMessage, function() {
// 尝试直接写入到选择的目录
if (self.fileManager.supportsFileSystemAccess && self.fileManager.directoryHandle) {
self.fileManager.writeFileToDirectory(currentFileName, self.currentSteps).then(function(result) {
self.showAlert("保存成功",
"配置已直接保存到目录 '" + result.directory + "' 中的文件:" + result.fileName +
"\n\n🎉 文件已直接更新,无需手动移动文件!");
}).catch(function(error) {
console.error("直接写入失败,降级到下载模式:", error);
self.saveConfigurationFallback(currentFileName, currentDir);
});
return;
}
// 降级到传统保存方式
self.saveConfigurationFallback(currentFileName, currentDir);
});
};
// 降级的保存配置方法
SceneStepEditor.prototype.saveConfigurationFallback = function(currentFileName, currentDir) {
var result = this.fileManager.saveProcessFile(currentFileName, this.currentSteps);
if (result.success) {
// 触发下载
this.downloadProcessFile(currentFileName);
var message = "配置已保存并下载:" + currentFileName;
if (this.fileManager.directoryHandle) {
message += "\n\n💡 请将下载的文件放入您的项目文件夹 '" + currentDir + "' 中,然后重新选择该文件夹以刷新文件列表。";
} else {
message += "\n\n💡 文件已下载到默认下载文件夹。建议先选择项目文件夹以启用直接保存功能。";
}
this.showAlert("保存成功", message);
} else {
alert("保存失败:" + result.error);
}
};
SceneStepEditor.prototype.generateProcessEventsCode = function() {
try {
var currentFileName = this.fileManager.getCurrentFileName();
if (!this.currentSteps || this.currentSteps.length === 0) {
alert("当前流程文件为空,无法生成代码");
return;
}
var code = this.codeGenerator.generateProcessEventsCode(currentFileName, this.currentSteps);
this.showCodeModal(code);
} catch (error) {
alert("生成代码失败:" + error.message);
}
};
// 全局编辑器实例
var editor;
// 渲染判断题列表
SceneStepEditor.prototype.renderJudgmentQuestions = function(stepIndex, actionIndex) {
var container = document.getElementById('judgmentQuestionsContainer_' + stepIndex + '_' + actionIndex);
if (!container) return;
var action = this.currentSteps[stepIndex].Actions[actionIndex];
var content = '<h4>判断题配置</h4>';
if (action.JudgmentQuestions.length === 0) {
content +=
'<div class="empty-state">' +
'<p>暂无判断题</p>' +
'<div class="add-button-container">' +
'<button class="btn-add-large" onclick="editor.addJudgmentQuestion(' + stepIndex + ', ' + actionIndex + ')">添加判断题</button>' +
'</div>' +
'</div>';
} else {
for (var i = 0; i < action.JudgmentQuestions.length; i++) {
var question = action.JudgmentQuestions[i];
content +=
'<div class="question-item">' +
'<div class="question-header">' +
'<strong>题目 ' + (i + 1) + '</strong>' +
'<button class="btn btn-small btn-remove" onclick="editor.removeJudgmentQuestion(' + stepIndex + ', ' + actionIndex + ', ' + i + ')" title="删除">-</button>' +
'</div>' +
'<div class="form-group">' +
'<label>题目内容</label>' +
'<input type="text" class="form-control" value="' + this.escapeHtml(question.Question) + '" ' +
'onchange="editor.updateJudgmentQuestionProperty(' + stepIndex + ', ' + actionIndex + ', ' + i + ', \'Question\', this.value)">' +
'</div>' +
'<div class="form-group">' +
'<label>正确答案</label>' +
'<input type="text" class="form-control" value="' + this.escapeHtml(question.CorrectAnswer) + '" ' +
'onchange="editor.updateJudgmentQuestionProperty(' + stepIndex + ', ' + actionIndex + ', ' + i + ', \'CorrectAnswer\', this.value)">' +
'</div>' +
'<div class="form-group">' +
'<label>分数</label>' +
'<input type="number" class="form-control" value="' + question.Score + '" step="0.1"' +
' onchange="editor.updateJudgmentQuestionProperty(' + stepIndex + ', ' + actionIndex + ', ' + i + ', \'Score\', parseFloat(this.value) || 0)">' +
'</div>' +
'</div>';
}
content +=
'<div class="add-button-container">' +
'<button class="btn-add-large" onclick="editor.addJudgmentQuestion(' + stepIndex + ', ' + actionIndex + ')">添加判断题</button>' +
'</div>';
}
container.innerHTML = content;
};
// 添加判断题
SceneStepEditor.prototype.addJudgmentQuestion = function(stepIndex, actionIndex) {
var newQuestion = DataUtils.createNewJudgmentQuestion();
this.currentSteps[stepIndex].Actions[actionIndex].JudgmentQuestions.push(newQuestion);
this.renderJudgmentQuestions(stepIndex, actionIndex);
};
// 删除判断题
SceneStepEditor.prototype.removeJudgmentQuestion = function(stepIndex, actionIndex, questionIndex) {
var self = this;
this.showConfirmModal(
"确认删除",
"确定要删除这个判断题吗?",
function() {
self.currentSteps[stepIndex].Actions[actionIndex].JudgmentQuestions.splice(questionIndex, 1);
self.renderJudgmentQuestions(stepIndex, actionIndex);
}
);
};
// 更新判断题属性
SceneStepEditor.prototype.updateJudgmentQuestionProperty = function(stepIndex, actionIndex, questionIndex, property, value) {
var question = this.currentSteps[stepIndex].Actions[actionIndex].JudgmentQuestions[questionIndex];
if (question) {
question[property] = value;
}
};
// 渲染多选题列表
SceneStepEditor.prototype.renderMultipleChoiceQuestions = function(stepIndex, actionIndex) {
var container = document.getElementById('multipleChoiceContainer_' + stepIndex + '_' + actionIndex);
if (!container) return;
var action = this.currentSteps[stepIndex].Actions[actionIndex];
var content = '<h4>多选题配置</h4>';
if (action.MultipleChoiceQuestions.length === 0) {
content +=
'<div class="empty-state">' +
'<p>暂无多选题</p>' +
'<div class="add-button-container">' +
'<button class="btn-add-large" onclick="editor.addMultipleChoiceQuestion(' + stepIndex + ', ' + actionIndex + ')">添加多选题</button>' +
'</div>' +
'</div>';
} else {
for (var i = 0; i < action.MultipleChoiceQuestions.length; i++) {
var question = action.MultipleChoiceQuestions[i];
content +=
'<div class="question-item">' +
'<div class="question-header">' +
'<strong>题目 ' + (i + 1) + '</strong>' +
'<button class="btn btn-small btn-remove" onclick="editor.removeMultipleChoiceQuestion(' + stepIndex + ', ' + actionIndex + ', ' + i + ')" title="删除">-</button>' +
'</div>' +
'<div class="form-group">' +
'<label>题目内容</label>' +
'<input type="text" class="form-control" value="' + this.escapeHtml(question.Question) + '" ' +
'onchange="editor.updateMultipleChoiceQuestionProperty(' + stepIndex + ', ' + actionIndex + ', ' + i + ', \'Question\', this.value)">' +
'</div>' +
'<div class="form-group">' +
'<label>分数</label>' +
'<input type="number" class="form-control" value="' + question.Score + '" step="0.1"' +
' onchange="editor.updateMultipleChoiceQuestionProperty(' + stepIndex + ', ' + actionIndex + ', ' + i + ', \'Score\', parseFloat(this.value) || 0)">' +
'</div>' +
'<div class="form-group">' +
'<label>选项列表</label>' +
'<div id="optionsContainer_' + stepIndex + '_' + actionIndex + '_' + i + '">' +
'<!-- 选项将在这里渲染 -->' +
'</div>' +
'</div>' +
'</div>';
}
content +=
'<div class="add-button-container">' +
'<button class="btn-add-large" onclick="editor.addMultipleChoiceQuestion(' + stepIndex + ', ' + actionIndex + ')">添加多选题</button>' +
'</div>';
}
container.innerHTML = content;
// 渲染每个题目的选项
for (var i = 0; i < action.MultipleChoiceQuestions.length; i++) {
this.renderMultipleChoiceOptions(stepIndex, actionIndex, i);
}
};
// 渲染多选题选项
SceneStepEditor.prototype.renderMultipleChoiceOptions = function(stepIndex, actionIndex, questionIndex) {
var container = document.getElementById('optionsContainer_' + stepIndex + '_' + actionIndex + '_' + questionIndex);
if (!container) return;
var question = this.currentSteps[stepIndex].Actions[actionIndex].MultipleChoiceQuestions[questionIndex];
var content = '';
for (var i = 0; i < question.Options.length; i++) {
var option = question.Options[i];
var isCorrect = question.CorrectAnswers.indexOf(option) !== -1;
content +=
'<div class="option-item">' +
'<input type="text" class="form-control" value="' + this.escapeHtml(option) + '" ' +
'onchange="editor.updateMultipleChoiceOption(' + stepIndex + ', ' + actionIndex + ', ' + questionIndex + ', ' + i + ', this.value)" ' +
'placeholder="选项 ' + (i + 1) + '">' +
'<label>' +
'<input type="checkbox" class="checkbox"' + (isCorrect ? ' checked' : '') + ' ' +
'onchange="editor.toggleCorrectAnswer(' + stepIndex + ', ' + actionIndex + ', ' + questionIndex + ', ' + i + ', this.checked)">' +
' 是正确答案' +
'</label>' +
'<button class="btn btn-small btn-remove" onclick="editor.removeMultipleChoiceOption(' + stepIndex + ', ' + actionIndex + ', ' + questionIndex + ', ' + i + ')" title="删除选项">-</button>' +
'</div>';
}
content +=
'<div class="add-button-container">' +
'<button class="btn btn-small btn-add" onclick="editor.addMultipleChoiceOption(' + stepIndex + ', ' + actionIndex + ', ' + questionIndex + ')">添加选项</button>' +
'</div>';
container.innerHTML = content;
};
// 添加多选题
SceneStepEditor.prototype.addMultipleChoiceQuestion = function(stepIndex, actionIndex) {
var newQuestion = DataUtils.createNewMultipleChoice();
this.currentSteps[stepIndex].Actions[actionIndex].MultipleChoiceQuestions.push(newQuestion);
this.renderMultipleChoiceQuestions(stepIndex, actionIndex);
};
// 删除多选题
SceneStepEditor.prototype.removeMultipleChoiceQuestion = function(stepIndex, actionIndex, questionIndex) {
var self = this;
this.showConfirmModal(
"确认删除",
"确定要删除这个多选题吗?",
function() {
self.currentSteps[stepIndex].Actions[actionIndex].MultipleChoiceQuestions.splice(questionIndex, 1);
self.renderMultipleChoiceQuestions(stepIndex, actionIndex);
}
);
};
// 更新多选题属性
SceneStepEditor.prototype.updateMultipleChoiceQuestionProperty = function(stepIndex, actionIndex, questionIndex, property, value) {
var question = this.currentSteps[stepIndex].Actions[actionIndex].MultipleChoiceQuestions[questionIndex];
if (question) {
question[property] = value;
}
};
// 添加多选题选项
SceneStepEditor.prototype.addMultipleChoiceOption = function(stepIndex, actionIndex, questionIndex) {
var question = this.currentSteps[stepIndex].Actions[actionIndex].MultipleChoiceQuestions[questionIndex];
question.Options.push("");
this.renderMultipleChoiceOptions(stepIndex, actionIndex, questionIndex);
};
// 删除多选题选项
SceneStepEditor.prototype.removeMultipleChoiceOption = function(stepIndex, actionIndex, questionIndex, optionIndex) {
var question = this.currentSteps[stepIndex].Actions[actionIndex].MultipleChoiceQuestions[questionIndex];
var option = question.Options[optionIndex];
// 从选项列表中删除
question.Options.splice(optionIndex, 1);
// 从正确答案列表中删除(如果存在)
var correctIndex = question.CorrectAnswers.indexOf(option);
if (correctIndex > -1) {
question.CorrectAnswers.splice(correctIndex, 1);
}
this.renderMultipleChoiceOptions(stepIndex, actionIndex, questionIndex);
};
// 更新多选题选项
SceneStepEditor.prototype.updateMultipleChoiceOption = function(stepIndex, actionIndex, questionIndex, optionIndex, newValue) {
var question = this.currentSteps[stepIndex].Actions[actionIndex].MultipleChoiceQuestions[questionIndex];
var oldValue = question.Options[optionIndex];
// 更新选项值
question.Options[optionIndex] = newValue;
// 如果旧值在正确答案中,替换为新值
var correctIndex = question.CorrectAnswers.indexOf(oldValue);
if (correctIndex > -1) {
question.CorrectAnswers[correctIndex] = newValue;
}
};
// 切换正确答案状态
SceneStepEditor.prototype.toggleCorrectAnswer = function(stepIndex, actionIndex, questionIndex, optionIndex, isCorrect) {
var question = this.currentSteps[stepIndex].Actions[actionIndex].MultipleChoiceQuestions[questionIndex];
var option = question.Options[optionIndex];
if (isCorrect) {
// 添加到正确答案列表
if (question.CorrectAnswers.indexOf(option) === -1) {
question.CorrectAnswers.push(option);
}
} else {
// 从正确答案列表中删除
var index = question.CorrectAnswers.indexOf(option);
if (index > -1) {
question.CorrectAnswers.splice(index, 1);
}
}
};
// 树状导航相关方法
// 切换侧边栏显示/隐藏
SceneStepEditor.prototype.toggleSidebar = function() {
this.sidebar.classList.toggle('collapsed');
};
// 渲染树状导航
SceneStepEditor.prototype.renderTreeNavigation = function() {
this.treeContainer.innerHTML = '';
if (this.currentSteps.length === 0) {
var emptyDiv = document.createElement('div');
emptyDiv.className = 'tree-empty-state';
emptyDiv.innerHTML = '<p style="color: #888; text-align: center; padding: 20px; font-size: 11px;">暂无步骤</p>';
this.treeContainer.appendChild(emptyDiv);
return;
}
for (var i = 0; i < this.currentSteps.length; i++) {
this.renderTreeNode(this.currentSteps[i], i);
}
};
// 渲染单个树节点(步骤)
SceneStepEditor.prototype.renderTreeNode = function(step, stepIndex) {
var self = this;
var isExpanded = this.stepFoldouts[stepIndex];
// 创建步骤节点
var stepNode = document.createElement('div');
stepNode.className = 'tree-node tree-node-step';
stepNode.id = 'tree-step-' + stepIndex;
// 步骤头部
var stepHeader = document.createElement('div');
stepHeader.className = 'tree-node-header';
stepHeader.innerHTML =
'<div class="tree-node-icon">' + (isExpanded ? '📂' : '📁') + '</div>' +
'<div class="tree-node-label" title="' + this.escapeHtml(step.StepDescription) + '">' +
this.escapeHtml(step.StepDescription) + ' (' + stepIndex + ')' +
'</div>' +
'<div class="tree-node-actions">' +
'<button class="btn-tree-action btn-tree-jump" title="跳转到步骤">→</button>' +
'</div>';
stepNode.appendChild(stepHeader);
// 添加动作子节点容器
if (step.Actions && step.Actions.length > 0) {
var childrenContainer = document.createElement('div');
childrenContainer.className = 'tree-children' + (isExpanded ? '' : ' collapsed');
childrenContainer.id = 'tree-children-' + stepIndex;
for (var j = 0; j < step.Actions.length; j++) {
this.renderTreeActionNode(childrenContainer, step.Actions[j], stepIndex, j);
}
stepNode.appendChild(childrenContainer);
}
// 绑定事件 - 整个标题区域可点击折叠/展开
stepHeader.addEventListener('click', function(e) {
// 如果点击的是跳转按钮,则不触发折叠
if (e.target.closest('.btn-tree-jump')) {
return;
}
self.toggleStepFromTree(stepIndex);
});
var jumpBtn = stepHeader.querySelector('.btn-tree-jump');
if (jumpBtn) {
jumpBtn.addEventListener('click', function(e) {
e.stopPropagation();
self.jumpToStep(stepIndex);
});
}
this.treeContainer.appendChild(stepNode);
};
// 渲染动作子节点
SceneStepEditor.prototype.renderTreeActionNode = function(container, action, stepIndex, actionIndex) {
var self = this;
var actionNode = document.createElement('div');
actionNode.className = 'tree-node tree-node-action';
actionNode.id = 'tree-action-' + stepIndex + '-' + actionIndex;
var actionHeader = document.createElement('div');
actionHeader.className = 'tree-node-header';
actionHeader.innerHTML =
'<div class="tree-node-icon">⚡</div>' +
'<div class="tree-node-label" title="' + this.escapeHtml(action.Title) + '">' +
this.escapeHtml(action.Title) + ' (' + stepIndex + '-' + actionIndex + ')' +
'</div>';
actionNode.appendChild(actionHeader);
// 绑定跳转事件 - 整个动作头部都可以点击跳转
actionHeader.addEventListener('click', function(e) {
e.stopPropagation();
self.jumpToAction(stepIndex, actionIndex);
});
// 添加鼠标悬停效果,让用户知道可以点击
actionHeader.addEventListener('mouseenter', function() {
actionHeader.style.cursor = 'pointer';
actionHeader.style.backgroundColor = 'rgba(74, 144, 226, 0.1)';
});
actionHeader.addEventListener('mouseleave', function() {
actionHeader.style.cursor = '';
actionHeader.style.backgroundColor = '';
});
container.appendChild(actionNode);
};
// 从树状导航切换步骤折叠状态
SceneStepEditor.prototype.toggleStepFromTree = function(stepIndex) {
// 切换主界面的折叠状态
this.stepFoldouts[stepIndex] = !this.stepFoldouts[stepIndex];
// 重新渲染主界面和树状导航
this.renderSteps();
};
// 跳转到指定步骤
SceneStepEditor.prototype.jumpToStep = function(stepIndex) {
var stepElement = this.stepsContainer.children[stepIndex];
if (stepElement) {
// 找到步骤的标题元素
var stepHeader = stepElement.querySelector('.step-header');
if (stepHeader) {
stepHeader.scrollIntoView({
behavior: 'smooth',
block: 'start',
inline: 'nearest'
});
// 高亮整个步骤
this.highlightElement(stepElement);
}
}
};
// 跳转到指定动作
SceneStepEditor.prototype.jumpToAction = function(stepIndex, actionIndex) {
// 首先确保步骤是展开的
if (!this.stepFoldouts[stepIndex]) {
this.stepFoldouts[stepIndex] = true;
this.renderSteps();
}
// 等待DOM更新后再跳转
var self = this;
setTimeout(function() {
var actionsContainer = document.getElementById('actionsContainer_' + stepIndex);
if (actionsContainer && actionsContainer.children[actionIndex]) {
var actionElement = actionsContainer.children[actionIndex];
// 找到动作的标题元素
var actionHeader = actionElement.querySelector('.action-header');
if (actionHeader) {
actionHeader.scrollIntoView({
behavior: 'smooth',
block: 'start',
inline: 'nearest'
});
// 高亮整个动作
self.highlightElement(actionElement);
} else {
// 如果没有找到标题,则滚动到动作元素顶部
actionElement.scrollIntoView({
behavior: 'smooth',
block: 'start',
inline: 'nearest'
});
self.highlightElement(actionElement);
}
}
}, 100);
};
// 高亮元素
SceneStepEditor.prototype.highlightElement = function(element) {
element.style.transition = 'all 0.3s ease';
element.style.backgroundColor = 'rgba(74, 144, 226, 0.2)';
element.style.transform = 'scale(1.01)';
setTimeout(function() {
element.style.backgroundColor = '';
element.style.transform = '';
}, 1500);
};
// 页面加载完成后初始化编辑器
document.addEventListener('DOMContentLoaded', function() {
// 初始化全局编辑器实例
window.editor = new SceneStepEditor();
editor = window.editor;
});