/** * 模型配置组件 * @type {module} */ import { formatXml, escapeHtml, isValidElementName } from './model-config/utils.js'; import { loadModelFiles as apiLoadModelFiles, loadFileContent as apiLoadFileContent, saveFileContent as apiSaveFileContent, createNewConfig as apiCreateNewConfig, saveFileAs as apiSaveFileAs } from './model-config/api.js'; import { parseXmlContent, updateXmlFromVisualEditor, ensureCommandListFormat, updateElementByPath, updateElementValue, addElement, deleteElement } from './model-config/xml-handler.js'; import { renderBasicInfoSection, renderAdvancedSection, renderElements, renderOtherSettingsSection } from './model-config/ui-elements.js'; import { renderCommandListTable, renderCommandList } from './model-config/command-handler.js'; import { showDateTimeDialog } from './model-config/datetime.js'; import { showAddElementDialog, showEditElementDialog, showNewConfigDialog, showSaveAsDialog, showAddCommandDialog, showEditCommandDialog } from './model-config/dialogs.js'; import { getStyles } from './model-config/styles.js'; class ModelConfig extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.selectedFile = null; this.xmlContent = ''; this.xmlDoc = null; this.isEdited = false; this.modelFiles = []; this.isActive = true; // 添加活动状态标记 } async connectedCallback() { this.isActive = true; this.render(); await this.loadModelFiles(); this.setupEventListeners(); // 延迟调用renderComplete,确保DOM已更新 setTimeout(() => { this.renderComplete(); }, 100); } disconnectedCallback() { this.isActive = false; } // 在组件完成渲染后恢复状态(与run-env-config组件一致) renderComplete() { // 在DOM渲染完成后更新文件选择器 setTimeout(() => { // 如果有选中的文件,尝试恢复它 if (this.selectedFile && (this.xmlContent || this.xmlDoc)) { const fileSelector = this.shadowRoot.getElementById('fileSelector'); if (fileSelector) { // 确认文件在列表中 const fileExists = Array.from(fileSelector.options).some(opt => opt.value === this.selectedFile); if (fileExists) { // 设置选中的文件 fileSelector.value = this.selectedFile; // 更新内容显示 const contentArea = this.shadowRoot.querySelector('#contentArea'); if (contentArea && this.xmlDoc) { contentArea.innerHTML = `
`; const visualEditor = this.shadowRoot.querySelector('#visualEditor'); if (visualEditor) { this.renderVisualEditor(visualEditor, this.xmlDoc); // 恢复编辑状态标记 if (this.isEdited) { this.markEdited(); } } } else { console.log('ModelConfig: 无法找到内容区域或XML文档不存在'); } } else { // 文件不存在,清除状态 this.selectedFile = null; this.xmlContent = ''; this.xmlDoc = null; this.isEdited = false; } } else { console.log('ModelConfig: 未找到文件选择器'); } } }, 50); // 增加延迟确保DOM已经完全加载 } // 重新激活组件的方法(当标签页重新被选中时调用) async reactivate() { if (this.isActive) { return; } this.isActive = true; try { // 先重新渲染一次UI以确保Shadow DOM结构完整 this.render(); // 加载文件列表 await this.loadModelFiles(); // 设置事件监听器 this.setupEventListeners(); // 调用renderComplete来恢复状态(这是核心改进) this.renderComplete(); } catch (error) { console.error('ModelConfig: 重新激活组件时出错:', error); } } async loadModelFiles() { try { this.modelFiles = await apiLoadModelFiles(); this.updateFileSelector(); } catch (error) { console.error('加载模型文件失败:', error); // 如果请求失败,可能是API不存在,等待一段时间后重试 const retryLoadFiles = async () => { try { console.log('尝试重新加载模型文件列表...'); this.modelFiles = await apiLoadModelFiles(); this.updateFileSelector(); } catch (retryError) { console.error('重试加载模型文件失败:', retryError); } }; // 延迟3秒后重试 setTimeout(retryLoadFiles, 3000); } } updateFileSelector() { const fileSelector = this.shadowRoot.getElementById('fileSelector'); if (!fileSelector) return; // 清空现有选项 fileSelector.innerHTML = ''; // 按修改时间排序,最新的在前面 const sortedFiles = [...this.modelFiles].sort((a, b) => new Date(b.mtime) - new Date(a.mtime) ); // 添加文件到选择器 sortedFiles.forEach(file => { const option = document.createElement('option'); option.value = file.path; option.textContent = file.name; fileSelector.appendChild(option); }); } async loadFileContent(filePath) { try { const content = await apiLoadFileContent(filePath); this.selectedFile = filePath; this.xmlContent = content; // 解析XML内容 const { xmlDoc, error, basicXml } = parseXmlContent(content); if (error && !basicXml) { // 显示错误信息 const configContent = this.shadowRoot.querySelector('.config-content'); configContent.innerHTML = `

XML解析错误

文件内容不是有效的XML格式。

${escapeHtml(content)}
`; return; } this.xmlDoc = xmlDoc; if (basicXml) { this.xmlContent = basicXml; } // 特殊处理CommandList元素,确保命令以属性方式存储 ensureCommandListFormat(this.xmlDoc); this.updateFileContent(); this.resetEditState(); } catch (error) { console.error('加载文件内容失败:', error); const configContent = this.shadowRoot.querySelector('.config-content'); configContent.innerHTML = `

加载失败

${error.message}

`; } } updateFileContent() { const contentArea = this.shadowRoot.querySelector('#contentArea'); if (!this.xmlDoc || !this.selectedFile) { contentArea.innerHTML = `
请选择一个模型配置文件查看内容
`; return; } contentArea.innerHTML = `
`; const visualEditor = this.shadowRoot.querySelector('#visualEditor'); this.renderVisualEditor(visualEditor, this.xmlDoc); } render() { this.shadowRoot.innerHTML = `
模型配置文件:
请选择一个模型配置文件查看内容
`; } setupEventListeners() { // 文件选择器 const fileSelector = this.shadowRoot.getElementById('fileSelector'); fileSelector.addEventListener('change', async (e) => { const filePath = e.target.value; if (filePath) { // 检查是否需要保存当前文件 if (await this.checkSaveNeeded()) { this.loadFileContent(filePath); } else { // 如果用户取消或保存失败,恢复选择器的值 fileSelector.value = this.selectedFile || ''; } } }); // 刷新文件列表 const refreshButton = this.shadowRoot.getElementById('refreshFiles'); refreshButton.addEventListener('click', async () => { // 检查是否需要保存当前文件 if (await this.checkSaveNeeded()) { refreshButton.classList.add('refreshing'); try { // 重新加载文件列表 await this.loadModelFiles(); // 清除编辑状态和内容 this.resetEditState(); // 清空内容区域 this.xmlContent = ''; this.xmlDoc = null; const contentArea = this.shadowRoot.querySelector('#contentArea'); if (contentArea) { contentArea.innerHTML = `
请选择一个模型配置文件查看内容
`; } // 清空文件选择 const fileSelector = this.shadowRoot.getElementById('fileSelector'); if (fileSelector) { fileSelector.value = ''; } // 重置选中的文件 this.selectedFile = null; } catch (error) { console.error('刷新文件列表失败:', error); alert('刷新文件列表失败: ' + error.message); } finally { // 无论成功失败,都移除刷新动画 setTimeout(() => { refreshButton.classList.remove('refreshing'); }, 500); } } // 如果用户点击取消,什么也不做,保留当前内容 }); // 新建配置 const newButton = this.shadowRoot.getElementById('newConfig'); newButton.addEventListener('click', async () => { // 检查是否需要保存当前文件 if (await this.checkSaveNeeded()) { showNewConfigDialog(async (result) => { await this.loadModelFiles(); await this.loadFileContent(result.path); }, this); } }); // 保存配置 const saveButton = this.shadowRoot.getElementById('saveConfig'); saveButton.addEventListener('click', async () => { if (!this.selectedFile) { alert('请先选择一个文件或创建新文件'); return; } await this.saveCurrentFile(); }); // 另存为 const saveAsButton = this.shadowRoot.getElementById('saveAsConfig'); saveAsButton.addEventListener('click', () => { if (!this.xmlContent) { alert('没有内容可保存'); return; } showSaveAsDialog(this.xmlContent, this.selectedFile, async (result) => { this.selectedFile = result.path; this.resetEditState(); await this.loadModelFiles(); }, this); }); } renderVisualEditor(container, xmlDoc) { if (!xmlDoc || !container) return; // 添加样式 const style = document.createElement('style'); style.textContent = ` .visual-editor { padding: 10px; } .section-title { font-size: 18px; font-weight: bold; margin-bottom: 10px; color: #333; border-bottom: 1px solid #e0e0e0; padding-bottom: 8px; } .section { margin-bottom: 20px; background-color: white; border: 1px solid #e0e0e0; border-radius: 4px; padding: 16px; } .property-row { display: flex; margin-bottom: 12px; align-items: center; } .property-label { width: 150px; font-weight: 500; color: #555; } .property-input { flex: 1; padding: 8px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; } .property-input:focus { border-color: #7986E7; outline: none; box-shadow: 0 0 0 2px rgba(121, 134, 231, 0.2); } .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .section-title-text { font-size: 18px; font-weight: bold; color: #333; } .section-buttons { display: flex; gap: 8px; } .section-button { background-color: #7986E7; color: white; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; font-size: 14px; transition: background-color 0.3s; } .section-button:hover { background-color: #6875D6; } .error-message { color: #d32f2f; background-color: #ffebee; padding: 10px; border-radius: 4px; margin-bottom: 15px; } `; const visualEditor = document.createElement('div'); visualEditor.className = 'visual-editor'; // 获取根元素 const rootElement = xmlDoc.documentElement; // 只处理Model根元素 if (rootElement.nodeName === 'Model') { // 创建模态对话框容器 const modalContainer = document.createElement('div'); modalContainer.className = 'modal'; modalContainer.id = 'propertyModal'; container.appendChild(modalContainer); // 渲染基本信息部分 renderBasicInfoSection( visualEditor, rootElement, xmlDoc, (inputElement) => this.showDateTimeDialogWrapper(inputElement), () => this.markEdited() ); // 渲染高级设置部分 renderAdvancedSection( visualEditor, rootElement, xmlDoc, (container, rootElement) => this.renderCommandListTableWrapper(container, rootElement), () => this.markEdited() ); // 渲染其他设置部分 renderOtherSettingsSection( visualEditor, rootElement, (container, parentElement, parentPath, level = 0) => this.renderElementsWrapper( container, parentElement, parentPath, level ) ); } else { // 不是Model根元素,显示错误信息 visualEditor.innerHTML = `
无法编辑:XML文档的根元素不是Model。 请确保XML文档的根元素是Model。
`; } container.appendChild(style); container.appendChild(visualEditor); // 自动保存配置到XML this.autoSaveToXml(); } // 自动保存表单内容到XML autoSaveToXml() { // 为所有输入框添加change事件 const inputs = this.shadowRoot.querySelectorAll('.property-input'); inputs.forEach(input => { input.addEventListener('change', () => { this.updateXmlFromVisualEditor(); // 更新XML this.markEdited(); // 标记已编辑 }); }); } // 显示日期时间对话框包装方法 showDateTimeDialogWrapper(inputElement) { showDateTimeDialog(inputElement, this.xmlDoc, () => this.markEdited(), this); } // 渲染命令列表表格包装方法 renderCommandListTableWrapper(container, rootElement) { renderCommandListTable( container, rootElement, (rootElement) => this.showAddCommandDialogWrapper(rootElement), (command, index) => this.showEditCommandDialogWrapper(command, index), (command, index) => this.deleteCommandWrapper(command, index) ); } // 渲染元素包装方法 renderElementsWrapper(container, parentElement, parentPath, level = 0) { renderElements( container, parentElement, parentPath, level, isValidElementName, (path, value) => this.updateElementByPathWrapper(path, value), (element, elementPath) => this.deleteElementWrapper(element, elementPath), (parentElement, name, value) => this.addElementWrapper(parentElement, name, value), (parentElement, parentPath) => this.showAddElementDialogWrapper(parentElement, parentPath), (element, elementPath) => this.showEditElementDialogWrapper(element, elementPath) ); } // 更新元素路径包装方法 updateElementByPathWrapper(path, value) { if (updateElementByPath(this.xmlDoc, path, value)) { this.updateXmlFromVisualEditor(); this.markEdited(); return true; } return false; } // 删除元素包装方法 deleteElementWrapper(element, elementPath) { // 确认删除 if (!confirm(`确定要删除元素 ${element.nodeName} 吗?此操作不可撤销。`)) { return; } if (deleteElement(element)) { this.updateXmlFromVisualEditor(); this.markEdited(); // 重新渲染元素列表 const container = this.shadowRoot.querySelector('#otherSettingsContainer'); if (container) { container.innerHTML = ''; this.renderElementsWrapper(container, this.xmlDoc.documentElement, '/' + this.xmlDoc.documentElement.nodeName); } return true; } return false; } // 添加元素包装方法 addElementWrapper(parentElement, name, value) { const newElement = addElement(this.xmlDoc, parentElement, name, value); if (newElement) { this.updateXmlFromVisualEditor(); this.markEdited(); return true; } return false; } // 显示添加元素对话框包装方法 showAddElementDialogWrapper(parentElement, parentPath) { showAddElementDialog(parentElement, parentPath, this.xmlDoc, () => { this.updateXmlFromVisualEditor(); this.markEdited(); // 重新渲染元素列表 const container = this.shadowRoot.querySelector('#otherSettingsContainer'); if (container) { container.innerHTML = ''; this.renderElementsWrapper(container, this.xmlDoc.documentElement, '/' + this.xmlDoc.documentElement.nodeName); } }, this); } // 显示编辑元素对话框包装方法 showEditElementDialogWrapper(element, elementPath) { showEditElementDialog(element, elementPath, this.xmlDoc, () => { this.updateXmlFromVisualEditor(); this.markEdited(); // 更新UI中的值 const inputElement = this.shadowRoot.querySelector(`input[data-path="${elementPath}"]`); if (inputElement) { inputElement.value = element.textContent; } }, this); } // 显示添加命令对话框包装方法 showAddCommandDialogWrapper(rootElement) { showAddCommandDialog(rootElement, this.xmlDoc, () => { this.updateXmlFromVisualEditor(); this.markEdited(); // 重新渲染命令列表 const commandListContainer = this.shadowRoot.querySelector('#commandListContainer'); const visualEditor = this.shadowRoot.querySelector('#visualEditor'); if (commandListContainer && visualEditor) { // 清空当前渲染 visualEditor.innerHTML = ''; // 重新渲染整个编辑器 this.renderVisualEditor(visualEditor, this.xmlDoc); } }, this); } // 显示编辑命令对话框包装方法 showEditCommandDialogWrapper(commandElement, index) { showEditCommandDialog(commandElement, index, () => { this.updateXmlFromVisualEditor(); this.markEdited(); // 更新表格行 const row = this.shadowRoot.querySelector(`.command-row[data-index="${index}"]`); if (row) { const name = commandElement.getAttribute('Name') || ''; const call = commandElement.getAttribute('Call') || ''; const description = commandElement.getAttribute('Description') || ''; row.querySelectorAll('.command-cell')[0].textContent = name; row.querySelectorAll('.command-cell')[1].textContent = call; row.querySelectorAll('.command-cell')[2].textContent = description || `${name}描述`; } else { // 如果找不到行,重新渲染整个编辑器 const visualEditor = this.shadowRoot.querySelector('#visualEditor'); if (visualEditor) { visualEditor.innerHTML = ''; this.renderVisualEditor(visualEditor, this.xmlDoc); } } }, this); } // 删除命令包装方法 deleteCommandWrapper(commandElement, index) { if (!confirm('确定要删除此命令吗?此操作不可撤销。')) { return; } if (deleteElement(commandElement)) { this.updateXmlFromVisualEditor(); this.markEdited(); // 从表格中移除行 const row = this.shadowRoot.querySelector(`.command-row[data-index="${index}"]`); if (row) { row.parentNode.removeChild(row); // 更新索引 const rows = this.shadowRoot.querySelectorAll('.command-row'); rows.forEach((r, i) => { r.dataset.index = i; }); } else { // 如果找不到行,重新渲染整个编辑器 const visualEditor = this.shadowRoot.querySelector('#visualEditor'); if (visualEditor) { visualEditor.innerHTML = ''; this.renderVisualEditor(visualEditor, this.xmlDoc); } } return true; } return false; } // 从可视化编辑器更新XML updateXmlFromVisualEditor() { if (!this.xmlDoc) return; this.xmlContent = updateXmlFromVisualEditor(this.xmlDoc); } // 标记为已编辑 markEdited() { this.isEdited = true; // 更新保存按钮样式 const saveButton = this.shadowRoot.getElementById('saveConfig'); if (saveButton) { saveButton.classList.add('modified'); } } // 重置编辑状态 resetEditState() { this.isEdited = false; // 更新保存按钮样式 const saveButton = this.shadowRoot.getElementById('saveConfig'); if (saveButton) { saveButton.classList.remove('modified'); } } // 更新命令列表部分 updateCommandListSection() { if (!this.xmlDoc) return; const visualEditorContainer = document.getElementById('visualEditorContainer'); if (!visualEditorContainer) return; // 移除已有的命令列表容器 const existingContainer = document.getElementById('commandListContainer'); if (existingContainer) { existingContainer.remove(); } // 获取根元素和命令列表元素 const rootElement = this.xmlDoc.documentElement; const commandListElement = ensureCommandListFormat(this.xmlDoc); // 渲染命令列表 renderCommandList( visualEditorContainer, commandListElement, rootElement, (rootElement) => this.showAddCommandDialogWrapper(rootElement), (command, index) => this.showEditCommandDialogWrapper(command, index), (command, index) => this.deleteCommandWrapper(command, index) ); } // 添加检查未保存更改的方法 async checkSaveNeeded() { if (this.isEdited) { // 使用自定义对话框替代简单的confirm const dialogResult = await new Promise(resolve => { // 创建模态框 const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'saveConfirmModal'; modal.innerHTML = ` `; document.body.appendChild(modal); // 添加事件监听 const saveBtn = modal.querySelector('#saveBtn'); const dontSaveBtn = modal.querySelector('#dontSaveBtn'); const cancelBtn = modal.querySelector('#cancelBtn'); const closeModal = () => { document.body.removeChild(modal); }; saveBtn.addEventListener('click', () => { closeModal(); resolve('save'); }); dontSaveBtn.addEventListener('click', () => { closeModal(); resolve('dont-save'); }); cancelBtn.addEventListener('click', () => { closeModal(); resolve('cancel'); }); // 点击模态窗口外部也取消 modal.addEventListener('click', (event) => { if (event.target === modal) { closeModal(); resolve('cancel'); } }); }); // 根据对话框结果执行相应操作 if (dialogResult === 'save') { try { await this.saveCurrentFile(); return true; // 继续执行后续操作 } catch (error) { console.error('保存出错:', error); return false; // 保存失败,不继续执行 } } else if (dialogResult === 'dont-save') { // 不保存,但继续执行后续操作 return true; } else { // 用户取消,不执行后续操作 return false; } } // 没有编辑状态,直接返回true允许继续操作 return true; } // 检查未保存更改 (兼容旧版的使用) checkUnsavedChanges() { // 由于无法同步返回Promise结果,这个方法保留为返回布尔值 // 但为了向前兼容,仍保留这个方法 return this.isEdited; } // 保存当前文件 async saveCurrentFile() { if (!this.selectedFile || !this.isEdited) return false; try { await apiSaveFileContent(this.selectedFile, this.xmlContent); this.resetEditState(); return true; } catch (error) { alert('保存失败: ' + error.message); return false; } } } customElements.define('model-config', ModelConfig);