/** * 文件操作模块 * @type {module} */ import { formatXml, markEdited, resetEditState } from './utils.js'; // 获取Scenario目录路径前缀 let scenarioDirPrefix = null; /** * 获取Scenario目录路径 * @returns {string} Scenario目录的绝对路径 */ async function getScenarioDir() { if (scenarioDirPrefix) return scenarioDirPrefix; try { // 从文件列表API获取Scenario目录路径 const response = await fetch('/api/scenario-files'); if (response.ok) { const files = await response.json(); if (files && files.length > 0) { // 提取第一个文件的目录作为Scenario目录 const filePath = files[0].path; scenarioDirPrefix = filePath.substring(0, filePath.lastIndexOf('/')); return scenarioDirPrefix; } } return ''; // 如果无法获取,返回空字符串 } catch (error) { console.error('获取Scenario目录失败:', error); return ''; } } /** * 加载场景文件列表 * @param {HTMLElement} component - 组件实例 * @returns {Promise} 是否成功加载 */ export async function loadScenarioFiles(component) { // 设置按钮为刷新中状态 const refreshButton = component.shadowRoot.getElementById('refreshButton'); if (refreshButton) { refreshButton.classList.add('refreshing'); } let retryCount = 0; const maxRetries = 3; // 最多重试3次 const tryLoadFiles = async () => { try { const response = await fetch('/api/scenario-files'); if (!response.ok) { throw new Error(`服务器返回错误: ${response.status} ${response.statusText}`); } const files = await response.json(); component.scenarioFiles = files; // 更新文件选择器 updateFileSelector(component); return true; } catch (error) { if (retryCount < maxRetries) { retryCount++; // 指数级退避重试 const retryDelay = Math.pow(2, retryCount) * 500; await new Promise(resolve => setTimeout(resolve, retryDelay)); return tryLoadFiles(); } alert(`加载文件列表失败: ${error.message}`); return false; } finally { // 无论成功失败,移除刷新中状态 if (refreshButton) { refreshButton.classList.remove('refreshing'); } } }; const success = await tryLoadFiles(); // 检查是否是用户通过刷新按钮手动触发的刷新 const isManualRefresh = document.activeElement === refreshButton; if (success && isManualRefresh) { // 只有在用户明确点击刷新按钮时才清空当前文件 component.currentFile = null; component.xmlContent = ''; component.updateFileContent(); component.isEdited = resetEditState(component.shadowRoot); // 更新文件选择器,确保没有文件被选中 const fileSelector = component.shadowRoot.getElementById('scenarioFile'); if (fileSelector) { fileSelector.value = ''; } } return success; } /** * 更新文件选择器 * @param {HTMLElement} component - 组件实例 */ export function updateFileSelector(component) { const fileSelector = component.shadowRoot.getElementById('scenarioFile'); if (fileSelector) { // 清除现有选项,保留第一个 while (fileSelector.options.length > 1) { fileSelector.remove(1); } // 添加文件选项 component.scenarioFiles.forEach(file => { const option = document.createElement('option'); option.value = file.path; option.textContent = file.name; fileSelector.appendChild(option); }); // 验证文件选择器选项 const options = Array.from(fileSelector.options); options.forEach((opt, index) => { if (index === 0) return; // 跳过第一个空选项 }); } else { console.log('未找到文件选择器元素'); } } /** * 加载文件内容 * @param {HTMLElement} component - 组件实例 * @param {string} filePath - 文件路径 * @returns {Promise} */ export async function loadFileContent(component, filePath) { try { const response = await fetch(`/api/file-content?path=${encodeURIComponent(filePath)}`); if (!response.ok) { throw new Error(`加载文件内容失败: ${response.status} ${response.statusText}`); } component.xmlContent = await response.text(); component.currentFile = filePath; // 检查内容是否为空 if (!component.xmlContent.trim()) { // 如果是.sce文件且内容为空,则报文件内容为空的错误 if (filePath.toLowerCase().endsWith('.sce')) { throw new Error('文件内容为空'); } } component.updateFileContent(); // 重置编辑状态,因为刚刚加载了新文件 component.isEdited = resetEditState(component.shadowRoot); } catch (error) { console.error('加载文件内容失败:', error); component.xmlContent = ''; component.updateFileContent(); } } /** * 保存文件内容 * @param {HTMLElement} component - 组件实例 * @param {string} filePath - 文件路径 * @param {string} content - 文件内容 * @returns {Promise} 是否成功保存 */ export async function saveFileContent(component, filePath, content) { try { // 更新XML内容 if (component.autoSaveToXml) { component.updateXmlFromVisualEditor(); } // 格式化XML内容 const formattedContent = formatXml(component.xmlContent); // 确保使用绝对路径 let absolutePath = filePath; if (!filePath.startsWith('/')) { const scenarioDir = await getScenarioDir(); // 如果filePath是相对路径但带有./,则移除它 const cleanPath = filePath.startsWith('./') ? filePath.substring(2) : filePath; absolutePath = `${scenarioDir}/${cleanPath}`; } const response = await fetch('/api/save-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: absolutePath, content: formattedContent }) }); if (!response.ok) { throw new Error(`保存文件失败: ${response.status} ${response.statusText}`); } // 重置编辑状态 component.isEdited = resetEditState(component.shadowRoot); // 更新当前文件 component.currentFile = absolutePath; // 刷新文件列表 await loadScenarioFiles(component); // 选中当前文件 const fileSelector = component.shadowRoot.getElementById('scenarioFile'); if (fileSelector && component.currentFile) { fileSelector.value = component.currentFile; } alert('文件保存成功'); return true; } catch (error) { console.error('保存文件失败:', error); alert(`保存文件失败: ${error.message}`); return false; } } /** * 创建新配置 * @param {HTMLElement} component - 组件实例 * @param {string} fileName - 文件名 * @returns {Promise} 是否成功创建 */ export async function createNewConfig(component, fileName) { try { // 使用服务器API创建新文件 const response = await fetch('/api/create-config-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName: fileName }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(`创建文件失败: ${errorData.error || response.statusText}`); } const data = await response.json(); // 更新文件列表 await loadScenarioFiles(component); // 加载新创建的文件内容 await loadFileContent(component, data.path); // 选中新创建的文件 const fileSelector = component.shadowRoot.getElementById('scenarioFile'); if (fileSelector) { fileSelector.value = data.path; } return true; } catch (error) { console.error('创建新配置文件失败:', error); alert(`创建新配置文件失败: ${error.message}`); return false; } }