1441 lines
56 KiB
JavaScript
1441 lines
56 KiB
JavaScript
// 主脚本文件 - 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;
|
||
});
|