class RunEnvConfig extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.currentFile = null; this.xmlContent = ''; this.scenarioFiles = []; this.isEdited = false; // 是否已编辑 this.xmlDoc = null; // 当前XML文档对象 } async connectedCallback() { // 先渲染UI this.render(); this.setupEventListeners(); // 然后加载文件列表 await this.loadScenarioFiles(); // 检查是否有renderComplete方法,如果有则调用 setTimeout(() => { if (typeof this.renderComplete === 'function') { this.renderComplete(); } }, 100); } async loadScenarioFiles() { // 设置按钮为刷新中状态 const refreshButton = this.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(); this.scenarioFiles = files; // 更新文件选择器 this.updateFileSelector(); 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) { // 只有在用户明确点击刷新按钮时才清空当前文件 this.currentFile = null; this.xmlContent = ''; this.updateFileContent(); this.resetEditState(); // 更新文件选择器,确保没有文件被选中 const fileSelector = this.shadowRoot.getElementById('scenarioFile'); if (fileSelector) { fileSelector.value = ''; } } return success; } updateFileSelector() { const fileSelector = this.shadowRoot.getElementById('scenarioFile'); if (fileSelector) { // 清除现有选项,保留第一个 while (fileSelector.options.length > 1) { fileSelector.remove(1); } // 添加文件选项 this.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('未找到文件选择器元素'); } } async loadFileContent(filePath) { try { const response = await fetch(`/api/file-content?path=${encodeURIComponent(filePath)}`); if (!response.ok) { throw new Error(`加载文件内容失败: ${response.status} ${response.statusText}`); } this.xmlContent = await response.text(); this.currentFile = filePath; // 检查内容是否为空 if (!this.xmlContent.trim()) { // 如果是.sce文件且内容为空,则报文件内容为空的错误 if (filePath.toLowerCase().endsWith('.sce')) { throw new Error('文件内容为空'); } } this.updateFileContent(); // 重置编辑状态,因为刚刚加载了新文件 this.resetEditState(); } catch (error) { console.error('加载文件内容失败:', error); this.xmlContent = ''; this.updateFileContent(); } } // 更新文件内容显示 updateFileContent() { const contentElement = this.shadowRoot.getElementById('fileContent'); if (!contentElement) { return; } if (!this.xmlContent) { contentElement.innerHTML = '
请选择一个运行环境配置文件查看内容
'; return; } try { // 尝试解析XML const parser = new DOMParser(); this.xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); // 检查解析错误 const parseError = this.xmlDoc.querySelector('parsererror'); if (parseError) { contentElement.innerHTML = `
${this.escapeHtml(this.xmlContent)}
`; return; } // 创建可视化编辑器 contentElement.innerHTML = ''; this.renderVisualEditor(contentElement, this.xmlDoc); } catch (error) { contentElement.innerHTML = `
${this.escapeHtml(this.xmlContent)}
`; } } // 渲染可视化编辑界面 renderVisualEditor(container, xmlDoc) { // 清空容器 container.innerHTML = ''; // 创建编辑器样式 const style = document.createElement('style'); style.textContent = ` .visual-editor { font-family: Arial, sans-serif; color: #333; } .editor-section { margin-bottom: 20px; border: 1px solid #e0e0e0; border-radius: 4px; padding: 16px; background-color: #fff; } .section-title { font-weight: bold; font-size: 16px; margin-bottom: 16px; color: #444; border-bottom: 1px solid #eee; padding-bottom: 8px; } .property-group { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; margin-bottom: 16px; } .property-item { margin-bottom: 12px; background-color: #f9f9f9; padding: 10px; border-radius: 4px; border: 1px solid #eee; } .property-label { display: block; font-size: 14px; margin-bottom: 6px; color: #555; font-weight: bold; } .property-input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; box-sizing: border-box; } .property-input:focus { border-color: #7986E7; outline: none; box-shadow: 0 0 0 2px rgba(121, 134, 231, 0.2); } .add-button { background-color: #7986E7; color: white; border: none; border-radius: 4px; padding: 8px 12px; cursor: pointer; font-size: 14px; margin-top: 12px; } .add-button:hover { background-color: #6875D6; } .list-item { border: 1px solid #eee; border-radius: 4px; padding: 12px; margin-bottom: 12px; background-color: #f9f9f9; } .list-item-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } .list-item-title { font-weight: bold; color: #555; font-size: 15px; } .list-item-actions { display: flex; gap: 8px; } .action-icon { cursor: pointer; color: #7986E7; font-size: 18px; width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; } .action-icon:hover { color: #6875D6; background-color: #f0f0f0; } /* 模态对话框样式 */ .modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); z-index: 1000; align-items: center; justify-content: center; } .modal-content { background-color: white; border-radius: 6px; padding: 24px; width: 500px; max-width: 90%; max-height: 90vh; overflow-y: auto; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } .modal-title { font-size: 18px; font-weight: bold; margin-bottom: 16px; color: #333; } .modal-form { margin-bottom: 20px; } .form-row { margin-bottom: 12px; } .form-label { display: block; margin-bottom: 6px; font-weight: bold; color: #555; } .form-input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } .form-actions { display: flex; justify-content: flex-end; gap: 12px; margin-top: 24px; } .modal-button { padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 14px; border: none; } .cancel-button { background-color: #f0f0f0; color: #333; } .submit-button { background-color: #7986E7; color: white; } .submit-button:hover { background-color: #6875D6; } `; const visualEditor = document.createElement('div'); visualEditor.className = 'visual-editor'; // 获取根元素 const rootElement = xmlDoc.documentElement; // 只处理Scenario根元素 if (rootElement.nodeName === 'Scenario') { // 创建模态对话框容器 const modalContainer = document.createElement('div'); modalContainer.className = 'modal'; modalContainer.id = 'propertyModal'; container.appendChild(modalContainer); // 渲染Environment部分 this.renderEnvironmentSection(visualEditor, rootElement); // 渲染Console输出和日志部分 this.renderConsoleAndLogSection(visualEditor, rootElement); // 渲染ModelGroup部分 this.renderModelGroupsSection(visualEditor, rootElement); // 渲染Services部分 this.renderServicesSection(visualEditor, rootElement); } else { // 不是Scenario根元素,显示错误信息 visualEditor.innerHTML = `
无法编辑:XML文档的根元素不是Scenario。 请确保XML文档的根元素是Scenario。
`; } 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(); }); }); } // 创建模态对话框 createModal(title, content, confirmCallback) { // 创建模态框容器 const modalOverlay = document.createElement('div'); modalOverlay.className = 'modal-overlay active'; modalOverlay.style.position = 'fixed'; modalOverlay.style.top = '0'; modalOverlay.style.left = '0'; modalOverlay.style.right = '0'; modalOverlay.style.bottom = '0'; modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; modalOverlay.style.display = 'flex'; modalOverlay.style.alignItems = 'center'; modalOverlay.style.justifyContent = 'center'; modalOverlay.style.zIndex = '1000'; // 创建模态框 const modal = document.createElement('div'); modal.className = 'modal-content'; modal.style.backgroundColor = 'white'; modal.style.borderRadius = '12px'; modal.style.padding = '24px'; modal.style.width = '500px'; modal.style.maxWidth = '90%'; modal.style.maxHeight = '90vh'; modal.style.overflowY = 'auto'; modal.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)'; // 创建模态框头部 const modalHeader = document.createElement('div'); modalHeader.className = 'modal-header'; modalHeader.style.display = 'flex'; modalHeader.style.justifyContent = 'space-between'; modalHeader.style.alignItems = 'center'; modalHeader.style.marginBottom = '16px'; // 创建标题 const modalTitle = document.createElement('h2'); modalTitle.className = 'modal-title'; modalTitle.textContent = title; modalTitle.style.fontSize = '20px'; modalTitle.style.fontWeight = 'bold'; modalTitle.style.color = '#2c3e50'; modalTitle.style.margin = '0'; // 关闭按钮 const closeBtn = document.createElement('button'); closeBtn.className = 'close-btn'; closeBtn.innerHTML = '×'; closeBtn.style.background = 'none'; closeBtn.style.border = 'none'; closeBtn.style.fontSize = '24px'; closeBtn.style.cursor = 'pointer'; closeBtn.style.color = '#718096'; closeBtn.style.lineHeight = '1'; closeBtn.style.padding = '0'; // 添加关闭按钮事件 closeBtn.onclick = () => { modalOverlay.remove(); }; // 组装头部 modalHeader.appendChild(modalTitle); modalHeader.appendChild(closeBtn); // 创建表单容器 const formContainer = document.createElement('form'); formContainer.style.marginBottom = '0'; // 添加内容 if (content) { // 如果内容已经是DOM元素,直接使用 if (content instanceof HTMLElement) { // 为内容添加样式 content.style.margin = '0 0 20px 0'; formContainer.appendChild(content); } else { // 否则创建新的内容容器 const contentDiv = document.createElement('div'); contentDiv.innerHTML = content; contentDiv.style.margin = '0 0 20px 0'; formContainer.appendChild(contentDiv); } } // 创建按钮容器 const modalFooter = document.createElement('div'); modalFooter.className = 'modal-footer'; modalFooter.style.display = 'flex'; modalFooter.style.justifyContent = 'flex-end'; modalFooter.style.gap = '12px'; modalFooter.style.marginTop = '24px'; // 取消按钮 const cancelButton = document.createElement('button'); cancelButton.className = 'btn btn-secondary'; cancelButton.type = 'button'; cancelButton.textContent = '取消'; cancelButton.style.padding = '10px 16px'; cancelButton.style.borderRadius = '6px'; cancelButton.style.fontSize = '15px'; cancelButton.style.cursor = 'pointer'; cancelButton.style.transition = 'all 0.3s'; cancelButton.style.backgroundColor = '#f8f9fa'; cancelButton.style.border = '1px solid #ddd'; cancelButton.style.color = '#718096'; // 添加取消按钮事件 cancelButton.onclick = () => { modalOverlay.remove(); }; // 确定按钮 const confirmButton = document.createElement('button'); confirmButton.className = 'btn btn-primary'; confirmButton.type = 'button'; confirmButton.textContent = '确定'; confirmButton.style.padding = '10px 16px'; confirmButton.style.borderRadius = '6px'; confirmButton.style.fontSize = '15px'; confirmButton.style.cursor = 'pointer'; confirmButton.style.transition = 'all 0.3s'; confirmButton.style.background = 'linear-gradient(135deg, #667eea, #764ba2)'; confirmButton.style.border = 'none'; confirmButton.style.color = 'white'; // 添加确定按钮事件 confirmButton.onclick = () => { if (confirmCallback) { confirmCallback(); modalOverlay.remove(); // 确保回调执行后关闭对话框 } }; // 点击背景关闭 modalOverlay.onclick = (e) => { if (e.target === modalOverlay) { modalOverlay.remove(); } }; // 组装底部按钮 modalFooter.appendChild(cancelButton); modalFooter.appendChild(confirmButton); // 组装表单 formContainer.appendChild(modalFooter); // 阻止表单提交默认行为 formContainer.onsubmit = (e) => { e.preventDefault(); if (confirmCallback) { confirmCallback(); modalOverlay.remove(); // 确保回调执行后关闭对话框 } return false; }; // 组装整个模态框 modal.appendChild(modalHeader); modal.appendChild(formContainer); modalOverlay.appendChild(modal); // 添加到DOM this.shadowRoot.appendChild(modalOverlay); // 返回引用 return { modal: modalOverlay, confirmButton: confirmButton }; } // 渲染Environment部分 renderEnvironmentSection(container, rootElement) { const envElement = rootElement.querySelector('Environment'); if (!envElement) { return; // 如果找不到Environment元素,直接返回 } const section = document.createElement('div'); section.className = 'editor-section'; const title = document.createElement('div'); title.className = 'section-title'; title.textContent = '环境配置'; section.appendChild(title); const propertyGroup = document.createElement('div'); propertyGroup.className = 'property-group'; // 获取并显示所有Environment属性 Array.from(envElement.attributes).forEach(attr => { const propertyItem = document.createElement('div'); propertyItem.className = 'property-item'; const label = document.createElement('label'); label.className = 'property-label'; label.textContent = attr.name; // 对CPUAffinity属性使用特殊处理 if (attr.name === 'CPUAffinity') { const inputContainer = document.createElement('div'); inputContainer.style.display = 'flex'; inputContainer.style.position = 'relative'; const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = attr.value; input.dataset.path = `Environment@${attr.name}`; input.style.flexGrow = '1'; input.title = 'CPU亲和性'; input.addEventListener('change', () => this.markEdited()); const dropdownButton = document.createElement('button'); dropdownButton.className = 'dropdown-button'; dropdownButton.type = 'button'; // 确保按钮类型为button dropdownButton.innerHTML = '▼'; dropdownButton.style.width = '30px'; dropdownButton.style.border = '1px solid #ddd'; dropdownButton.style.borderLeft = 'none'; dropdownButton.style.borderRadius = '0 4px 4px 0'; dropdownButton.style.backgroundColor = '#f5f5f5'; dropdownButton.style.cursor = 'pointer'; // 创建下拉框容器 const dropdown = document.createElement('div'); dropdown.className = 'cpu-dropdown'; dropdown.style.position = 'absolute'; dropdown.style.top = '100%'; dropdown.style.left = '0'; dropdown.style.right = '0'; dropdown.style.backgroundColor = 'white'; dropdown.style.border = '1px solid #ddd'; dropdown.style.borderRadius = '4px'; dropdown.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)'; dropdown.style.zIndex = '50'; // 提高z-index确保在最上层 dropdown.style.padding = '10px'; dropdown.style.display = 'none'; // 生成CPU核心选项 const cpuCount = 16; // 假设最多16个核心 const selectedCores = attr.value.split(',').map(core => parseInt(core.trim())).filter(core => !isNaN(core)); for (let i = 0; i < cpuCount; i++) { const checkboxContainer = document.createElement('div'); checkboxContainer.style.marginBottom = '6px'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = `cpu-${i}`; checkbox.value = i; checkbox.checked = selectedCores.includes(i); checkbox.style.marginRight = '6px'; const checkboxLabel = document.createElement('label'); checkboxLabel.htmlFor = `cpu-${i}`; checkboxLabel.textContent = `CPU ${i}`; checkbox.addEventListener('change', () => { // 获取所有选中的核心 const checkedCores = Array.from(dropdown.querySelectorAll('input[type="checkbox"]:checked')) .map(cb => cb.value) .sort((a, b) => a - b); // 更新输入框的值 input.value = checkedCores.join(','); input.dispatchEvent(new Event('change')); }); checkboxContainer.appendChild(checkbox); checkboxContainer.appendChild(checkboxLabel); dropdown.appendChild(checkboxContainer); } // 添加应用和取消按钮 const buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.justifyContent = 'flex-end'; buttonContainer.style.marginTop = '10px'; buttonContainer.style.gap = '8px'; const closeButton = document.createElement('button'); closeButton.textContent = '关闭'; closeButton.style.padding = '6px 12px'; closeButton.style.backgroundColor = '#f0f0f0'; closeButton.style.border = '1px solid #ddd'; closeButton.style.borderRadius = '4px'; closeButton.style.cursor = 'pointer'; closeButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); dropdown.style.display = 'none'; }); buttonContainer.appendChild(closeButton); dropdown.appendChild(buttonContainer); // 使用更可靠的方法显示下拉菜单 const toggleDropdown = (e) => { e.preventDefault(); e.stopPropagation(); if (dropdown.style.display === 'none' || !dropdown.style.display) { dropdown.style.display = 'block'; // 添加全局一次性点击事件,用于关闭下拉框 setTimeout(() => { const closeDropdown = (event) => { if (!dropdown.contains(event.target) && event.target !== dropdownButton) { dropdown.style.display = 'none'; document.removeEventListener('click', closeDropdown); } }; document.addEventListener('click', closeDropdown); }, 0); } else { dropdown.style.display = 'none'; } }; // 点击下拉按钮时显示或隐藏下拉框 dropdownButton.addEventListener('click', toggleDropdown); inputContainer.appendChild(input); inputContainer.appendChild(dropdownButton); inputContainer.appendChild(dropdown); propertyItem.appendChild(label); propertyItem.appendChild(inputContainer); } else if (attr.name === 'BaseFrequency') { // 对数字类型的属性使用number类型输入框 const input = document.createElement('input'); input.className = 'property-input'; input.type = 'number'; input.min = '0'; input.value = attr.value; input.dataset.path = `Environment@${attr.name}`; input.title = '仿真运行基础频率,单位Hz'; input.addEventListener('change', () => this.markEdited()); propertyItem.appendChild(label); propertyItem.appendChild(input); } else if (attr.name === 'OSName') { const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = attr.value; input.dataset.path = `Environment@${attr.name}`; input.title = '操作系统名称'; input.addEventListener('change', () => this.markEdited()); propertyItem.appendChild(label); propertyItem.appendChild(input); } else if (attr.name === 'Version') { const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = attr.value; input.dataset.path = `Environment@${attr.name}`; input.title = '操作系统版本'; input.addEventListener('change', () => this.markEdited()); propertyItem.appendChild(label); propertyItem.appendChild(input); } else if (attr.name === 'RTXVersion') { const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = attr.value; input.dataset.path = `Environment@${attr.name}`; input.title = '实时内核版本'; input.addEventListener('change', () => this.markEdited()); propertyItem.appendChild(label); propertyItem.appendChild(input); } else if (attr.name === 'WorkPath') { const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = attr.value; input.dataset.path = `Environment@${attr.name}`; input.title = '仿真工作路径,XNCore环境变量所在路径'; input.addEventListener('change', () => this.markEdited()); propertyItem.appendChild(label); propertyItem.appendChild(input); } else if (attr.name === 'ModelsPath') { const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = attr.value; input.dataset.path = `Environment@${attr.name}`; input.title = '模型相对路径,相对于仿真工作路径'; input.addEventListener('change', () => this.markEdited()); propertyItem.appendChild(label); propertyItem.appendChild(input); } else if (attr.name === 'ServicesPath') { const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = attr.value; input.dataset.path = `Environment@${attr.name}`; input.title = '服务相对路径,相对于仿真工作路径'; input.addEventListener('change', () => this.markEdited()); propertyItem.appendChild(label); propertyItem.appendChild(input); } else if (attr.name === 'DomainID') { // 对数字类型的属性使用number类型输入框 const input = document.createElement('input'); input.className = 'property-input'; input.type = 'number'; input.min = '0'; input.value = attr.value; input.dataset.path = `Environment@${attr.name}`; input.title = 'DDS通信域ID'; input.addEventListener('change', () => this.markEdited()); propertyItem.appendChild(label); propertyItem.appendChild(input); } else { const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = attr.value; input.dataset.path = `Environment@${attr.name}`; input.addEventListener('change', () => this.markEdited()); propertyItem.appendChild(label); propertyItem.appendChild(input); } propertyGroup.appendChild(propertyItem); }); section.appendChild(propertyGroup); // 添加"添加新属性"按钮 const addButton = document.createElement('button'); addButton.className = 'add-button'; addButton.textContent = '添加新属性'; addButton.title = '添加新的运行环境属性'; addButton.addEventListener('click', () => this.showAddPropertyDialog('Environment')); section.appendChild(addButton); container.appendChild(section); } // 添加新属性对话框 showAddPropertyDialog(elementName) { // 创建表单内容 const formContent = document.createElement('div'); formContent.className = 'modal-form'; // 属性名输入 const nameRow = document.createElement('div'); nameRow.className = 'form-row'; const nameLabel = document.createElement('label'); nameLabel.className = 'form-label'; nameLabel.textContent = '属性名称'; const nameInput = document.createElement('input'); nameInput.className = 'form-input'; nameInput.type = 'text'; nameInput.placeholder = '请输入属性名称'; nameRow.appendChild(nameLabel); nameRow.appendChild(nameInput); formContent.appendChild(nameRow); // 属性值输入 const valueRow = document.createElement('div'); valueRow.className = 'form-row'; const valueLabel = document.createElement('label'); valueLabel.className = 'form-label'; valueLabel.textContent = '属性值'; const valueInput = document.createElement('input'); valueInput.className = 'form-input'; valueInput.type = 'text'; valueInput.placeholder = '请输入属性值'; valueRow.appendChild(valueLabel); valueRow.appendChild(valueInput); formContent.appendChild(valueRow); // 显示对话框 this.createModal('添加新属性', formContent, () => { const propertyName = nameInput.value.trim(); const propertyValue = valueInput.value; if (propertyName) { this.addNewProperty(elementName, propertyName, propertyValue); } }); } // 添加新属性 addNewProperty(elementName, propertyName, propertyValue) { // 更新UI const section = Array.from(this.shadowRoot.querySelectorAll('.section-title')).find(el => { if (elementName === 'Environment' && el.textContent === '环境配置') return true; return false; })?.parentElement; if (section) { const propertyGroup = section.querySelector('.property-group'); if (propertyGroup) { const propertyItem = document.createElement('div'); propertyItem.className = 'property-item'; const label = document.createElement('label'); label.className = 'property-label'; label.textContent = propertyName; const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = propertyValue; input.dataset.path = `${elementName}@${propertyName}`; input.dataset.isNew = 'true'; input.addEventListener('change', () => this.markEdited()); propertyItem.appendChild(label); propertyItem.appendChild(input); propertyGroup.appendChild(propertyItem); this.markEdited(); this.updateXmlFromVisualEditor(true); // 静默更新XML } } } // 全局变量,用于存储CPU亲和性设置 updateCPUAffinityOptions() { // 从环境配置中获取CPUAffinity的值 const envElement = this.xmlDoc.querySelector('Environment'); if (!envElement) return []; const cpuAffinity = envElement.getAttribute('CPUAffinity') || '0'; const availableCores = cpuAffinity.split(',').map(core => parseInt(core.trim())).filter(core => !isNaN(core)); return availableCores; } // 渲染模型组部分 renderModelGroupsSection(container, rootElement) { const section = document.createElement('div'); section.className = 'editor-section'; const title = document.createElement('div'); title.className = 'section-title'; title.textContent = '模型组配置'; section.appendChild(title); // 获取所有ModelGroup元素 const modelGroups = rootElement.querySelectorAll('ModelGroup'); if (modelGroups.length === 0) { const emptyMsg = document.createElement('div'); emptyMsg.textContent = '没有找到模型组配置'; emptyMsg.style.color = '#888'; emptyMsg.style.fontStyle = 'italic'; emptyMsg.style.marginBottom = '16px'; section.appendChild(emptyMsg); } else { // 为每个ModelGroup创建一个列表项 modelGroups.forEach((modelGroup, index) => { const groupItem = document.createElement('div'); groupItem.className = 'list-item'; groupItem.dataset.index = index; // 创建组标题和操作按钮 const header = document.createElement('div'); header.className = 'list-item-header'; const groupTitle = document.createElement('div'); groupTitle.className = 'list-item-title'; groupTitle.textContent = modelGroup.getAttribute('Name') || `模型组 ${index + 1}`; const actions = document.createElement('div'); actions.className = 'list-item-actions'; const deleteButton = document.createElement('button'); deleteButton.className = 'action-icon'; deleteButton.title = '删除模型组'; deleteButton.style.border = 'none'; deleteButton.style.background = 'none'; deleteButton.style.cursor = 'pointer'; deleteButton.style.padding = '4px'; deleteButton.style.display = 'flex'; deleteButton.style.alignItems = 'center'; deleteButton.style.justifyContent = 'center'; const deleteImg = document.createElement('img'); deleteImg.src = 'assets/icons/png/delete_b.png'; deleteImg.alt = '删除'; deleteImg.style.width = '16px'; deleteImg.style.height = '16px'; deleteButton.appendChild(deleteImg); deleteButton.addEventListener('click', () => this.deleteModelGroup(index)); actions.appendChild(deleteButton); header.appendChild(groupTitle); header.appendChild(actions); groupItem.appendChild(header); // 显示模型组属性 const propertiesContainer = document.createElement('div'); propertiesContainer.className = 'property-group'; propertiesContainer.style.marginTop = '12px'; // 获取可用的CPU核心选项 const availableCores = this.updateCPUAffinityOptions(); Array.from(modelGroup.attributes).forEach(attr => { const propertyItem = document.createElement('div'); propertyItem.className = 'property-item'; const label = document.createElement('label'); label.className = 'property-label'; label.textContent = attr.name; // 根据属性名使用不同的输入控件 if (attr.name === 'FreqGroup') { // FreqGroup使用限制范围的数字输入框 const input = document.createElement('input'); input.className = 'property-input'; input.type = 'number'; input.min = '0'; input.max = '6'; input.value = attr.value; input.dataset.path = `ModelGroup[${index}]@${attr.name}`; input.title = '频率组,取值范围0-6'; input.addEventListener('change', () => { // 确保值在有效范围内 if (parseInt(input.value) > 6) { input.value = '6'; } this.markEdited(); }); propertyItem.appendChild(label); propertyItem.appendChild(input); } else if (attr.name === 'Priority') { // Priority使用限制范围的数字输入框 const input = document.createElement('input'); input.className = 'property-input'; input.type = 'number'; input.min = '0'; input.max = '99'; input.value = attr.value; input.dataset.path = `ModelGroup[${index}]@${attr.name}`; input.title = '线程优先级,取值范围0-99'; input.addEventListener('change', () => { // 确保值在有效范围内 if (parseInt(input.value) > 99) { input.value = '99'; } this.markEdited(); }); propertyItem.appendChild(label); propertyItem.appendChild(input); } else if (attr.name === 'CPUAff') { // CPUAff使用下拉框 const select = document.createElement('select'); select.className = 'property-input'; select.dataset.path = `ModelGroup[${index}]@${attr.name}`; select.title = '选择一个可用的亲和CPU核心'; // 添加可用的CPU核心选项 availableCores.forEach(core => { const option = document.createElement('option'); option.value = core; option.textContent = `CPU ${core}`; // 如果当前值匹配,设为选中 if (parseInt(attr.value) === core) { option.selected = true; } select.appendChild(option); }); // 如果没有可用选项,添加一个默认选项 if (availableCores.length === 0) { const option = document.createElement('option'); option.value = attr.value; option.textContent = `CPU ${attr.value}`; option.selected = true; select.appendChild(option); } select.addEventListener('change', () => { this.markEdited(); }); propertyItem.appendChild(label); propertyItem.appendChild(select); } else if (attr.name === 'Name') { // 模型组名称 const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = attr.value; input.dataset.path = `ModelGroup[${index}]@${attr.name}`; input.title = '模型组名称'; input.addEventListener('change', () => { this.markEdited(); groupTitle.textContent = input.value || `模型组 ${index + 1}`; }); propertyItem.appendChild(label); propertyItem.appendChild(input); } else { // 其他属性使用文本输入框 const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = attr.value; input.dataset.path = `ModelGroup[${index}]@${attr.name}`; input.addEventListener('change', () => { this.markEdited(); }); propertyItem.appendChild(label); propertyItem.appendChild(input); } propertiesContainer.appendChild(propertyItem); }); groupItem.appendChild(propertiesContainer); // 显示模型列表 const modelsTitle = document.createElement('div'); modelsTitle.textContent = '模型列表'; modelsTitle.style.fontWeight = 'bold'; modelsTitle.style.margin = '16px 0 12px'; groupItem.appendChild(modelsTitle); const models = modelGroup.querySelectorAll('Model'); const modelsContainer = document.createElement('div'); modelsContainer.style.marginLeft = '16px'; if (models.length === 0) { const emptyModelMsg = document.createElement('div'); emptyModelMsg.textContent = '没有找到模型'; emptyModelMsg.style.color = '#888'; emptyModelMsg.style.fontStyle = 'italic'; emptyModelMsg.style.marginBottom = '12px'; modelsContainer.appendChild(emptyModelMsg); } else { models.forEach((model, modelIndex) => { const modelItem = document.createElement('div'); modelItem.className = 'list-item'; modelItem.style.marginBottom = '12px'; const modelHeader = document.createElement('div'); modelHeader.className = 'list-item-header'; const modelTitle = document.createElement('div'); modelTitle.className = 'list-item-title'; modelTitle.textContent = model.getAttribute('Name') || `模型 ${modelIndex + 1}`; const modelActions = document.createElement('div'); modelActions.className = 'list-item-actions'; const deleteModelButton = document.createElement('button'); deleteModelButton.className = 'action-icon'; deleteModelButton.title = '删除模型'; deleteModelButton.style.border = 'none'; deleteModelButton.style.background = 'none'; deleteModelButton.style.cursor = 'pointer'; deleteModelButton.style.padding = '4px'; deleteModelButton.style.display = 'flex'; deleteModelButton.style.alignItems = 'center'; deleteModelButton.style.justifyContent = 'center'; const deleteModelImg = document.createElement('img'); deleteModelImg.src = 'assets/icons/png/delete_b.png'; deleteModelImg.alt = '删除'; deleteModelImg.style.width = '16px'; deleteModelImg.style.height = '16px'; deleteModelButton.appendChild(deleteModelImg); deleteModelButton.addEventListener('click', () => this.deleteModel(index, modelIndex)); modelActions.appendChild(deleteModelButton); modelHeader.appendChild(modelTitle); modelHeader.appendChild(modelActions); modelItem.appendChild(modelHeader); // 显示模型属性 const modelProperties = document.createElement('div'); modelProperties.className = 'property-group'; modelProperties.style.marginTop = '12px'; Array.from(model.attributes).forEach(attr => { if (attr.name === 'Name') { // 如果是Name属性,我们需要查看是否有ClassName属性 const classNameAttr = model.getAttribute('ClassName'); // 创建一个行容器,直接放在标题下方 const rowContainer = document.createElement('div'); rowContainer.style.display = 'flex'; rowContainer.style.width = '100%'; rowContainer.style.gap = '10px'; rowContainer.style.marginTop = '8px'; rowContainer.style.marginBottom = '8px'; // 名称标签和输入框组 const nameGroup = document.createElement('div'); nameGroup.style.display = 'flex'; nameGroup.style.alignItems = 'center'; nameGroup.style.flex = '1'; const nameLabel = document.createElement('label'); nameLabel.className = 'property-label'; nameLabel.textContent = 'Name'; nameLabel.style.marginRight = '5px'; nameLabel.style.minWidth = '60px'; const nameInput = document.createElement('input'); nameInput.className = 'property-input'; nameInput.type = 'text'; nameInput.value = attr.value; nameInput.dataset.path = `ModelGroup[${index}]/Model[${modelIndex}]@${attr.name}`; nameInput.title = '模型名称'; nameInput.addEventListener('change', () => { this.markEdited(); modelTitle.textContent = nameInput.value || `模型 ${modelIndex + 1}`; }); nameGroup.appendChild(nameLabel); nameGroup.appendChild(nameInput); // 类名标签和输入框组 const classGroup = document.createElement('div'); classGroup.style.display = 'flex'; classGroup.style.alignItems = 'center'; classGroup.style.flex = '1'; const classLabel = document.createElement('label'); classLabel.className = 'property-label'; classLabel.textContent = 'ClassName'; classLabel.style.marginRight = '5px'; classLabel.style.minWidth = '80px'; const classInput = document.createElement('input'); classInput.className = 'property-input'; classInput.type = 'text'; classInput.value = classNameAttr || ''; classInput.dataset.path = `ModelGroup[${index}]/Model[${modelIndex}]@ClassName`; classInput.title = '模型C++类名'; classInput.addEventListener('change', () => { this.markEdited(); }); classGroup.appendChild(classLabel); classGroup.appendChild(classInput); // 将名称组和类名组添加到行容器 rowContainer.appendChild(nameGroup); rowContainer.appendChild(classGroup); // 直接添加到modelItem,而不是添加到modelProperties modelItem.appendChild(rowContainer); // 将ClassName标记为已处理,后面不需要再处理 model.setAttribute('ClassName_processed', 'true'); } else if (attr.name === 'ClassName') { // 如果是ClassName且已处理,则跳过 if (model.getAttribute('ClassName_processed') === 'true') { return; } // 如果Name还没被处理,我们需要创建完整的行 const nameAttr = model.getAttribute('Name'); // 创建一个行容器 const rowContainer = document.createElement('div'); rowContainer.style.display = 'flex'; rowContainer.style.width = '100%'; rowContainer.style.gap = '10px'; rowContainer.style.marginTop = '8px'; rowContainer.style.marginBottom = '8px'; // 名称标签和输入框组 const nameGroup = document.createElement('div'); nameGroup.style.display = 'flex'; nameGroup.style.alignItems = 'center'; nameGroup.style.flex = '1'; const nameLabel = document.createElement('label'); nameLabel.className = 'property-label'; nameLabel.textContent = 'Name'; nameLabel.style.marginRight = '5px'; nameLabel.style.minWidth = '60px'; const nameInput = document.createElement('input'); nameInput.className = 'property-input'; nameInput.type = 'text'; nameInput.value = nameAttr || ''; nameInput.dataset.path = `ModelGroup[${index}]/Model[${modelIndex}]@Name`; nameInput.title = '模型名称'; nameInput.addEventListener('change', () => { this.markEdited(); modelTitle.textContent = nameInput.value || `模型 ${modelIndex + 1}`; }); nameGroup.appendChild(nameLabel); nameGroup.appendChild(nameInput); // 类名标签和输入框组 const classGroup = document.createElement('div'); classGroup.style.display = 'flex'; classGroup.style.alignItems = 'center'; classGroup.style.flex = '1'; const classLabel = document.createElement('label'); classLabel.className = 'property-label'; classLabel.textContent = 'ClassName'; classLabel.style.marginRight = '5px'; classLabel.style.minWidth = '80px'; const classInput = document.createElement('input'); classInput.className = 'property-input'; classInput.type = 'text'; classInput.value = attr.value; classInput.dataset.path = `ModelGroup[${index}]/Model[${modelIndex}]@ClassName`; classInput.title = '模型C++类名'; classInput.addEventListener('change', () => { this.markEdited(); }); classGroup.appendChild(classLabel); classGroup.appendChild(classInput); // 将名称组和类名组添加到行容器 rowContainer.appendChild(nameGroup); rowContainer.appendChild(classGroup); // 直接添加到modelItem,而不是添加到modelProperties modelItem.appendChild(rowContainer); // 将Name标记为已处理 model.setAttribute('Name_processed', 'true'); } else { // 其他属性加入属性组 const propertyItem = document.createElement('div'); propertyItem.className = 'property-item'; const label = document.createElement('label'); label.className = 'property-label'; label.textContent = attr.name; const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = attr.value; input.dataset.path = `ModelGroup[${index}]/Model[${modelIndex}]@${attr.name}`; input.addEventListener('change', () => { this.markEdited(); }); propertyItem.appendChild(label); propertyItem.appendChild(input); modelProperties.appendChild(propertyItem); } }); // 只有当有其他属性时才添加属性组 if (modelProperties.children.length > 0) { // 添加一个标题来分隔基本属性和其他属性 const otherPropsTitle = document.createElement('div'); otherPropsTitle.textContent = '其他属性'; otherPropsTitle.style.fontWeight = 'bold'; otherPropsTitle.style.fontSize = '0.9em'; otherPropsTitle.style.margin = '8px 0'; modelItem.appendChild(otherPropsTitle); modelItem.appendChild(modelProperties); } // 清除处理标记 Array.from(model.attributes).forEach(attr => { if (attr.name === 'Name_processed' || attr.name === 'ClassName_processed') { model.removeAttribute(attr.name); } }); modelItem.appendChild(modelProperties); modelsContainer.appendChild(modelItem); }); } groupItem.appendChild(modelsContainer); // 添加模型按钮 const addModelButton = document.createElement('button'); addModelButton.className = 'add-button'; addModelButton.style.marginTop = '12px'; addModelButton.textContent = '添加模型'; addModelButton.title = '添加新模型到当前模型组'; addModelButton.addEventListener('click', () => this.showAddModelDialog(index)); groupItem.appendChild(addModelButton); section.appendChild(groupItem); }); } // 添加新模型组按钮 const addButton = document.createElement('button'); addButton.className = 'add-button'; addButton.style.marginTop = '16px'; addButton.textContent = '添加模型组'; addButton.title = '添加新的模型组'; addButton.addEventListener('click', () => this.showAddModelGroupDialog()); section.appendChild(addButton); container.appendChild(section); } // 渲染服务部分 renderServicesSection(container, rootElement) { const section = document.createElement('div'); section.className = 'editor-section'; const title = document.createElement('div'); title.className = 'section-title'; title.textContent = '服务配置'; section.appendChild(title); // 获取ServicesList元素 const servicesList = rootElement.querySelector('ServicesList'); if (!servicesList) { const emptyMsg = document.createElement('div'); emptyMsg.textContent = '没有找到服务列表配置'; emptyMsg.style.color = '#888'; emptyMsg.style.fontStyle = 'italic'; emptyMsg.style.marginBottom = '16px'; section.appendChild(emptyMsg); } else { // 获取所有Service元素 const services = servicesList.querySelectorAll('Service'); if (services.length === 0) { const emptyMsg = document.createElement('div'); emptyMsg.textContent = '没有找到服务配置'; emptyMsg.style.color = '#888'; emptyMsg.style.fontStyle = 'italic'; emptyMsg.style.marginBottom = '16px'; section.appendChild(emptyMsg); } else { // 为每个Service创建一个列表项 services.forEach((service, index) => { const serviceItem = document.createElement('div'); serviceItem.className = 'list-item'; // 创建服务标题和操作按钮 const header = document.createElement('div'); header.className = 'list-item-header'; const serviceTitle = document.createElement('div'); serviceTitle.className = 'list-item-title'; serviceTitle.textContent = service.getAttribute('Name') || `服务 ${index + 1}`; const actions = document.createElement('div'); actions.className = 'list-item-actions'; const deleteButton = document.createElement('button'); deleteButton.className = 'action-icon'; deleteButton.title = '删除服务'; deleteButton.style.border = 'none'; deleteButton.style.background = 'none'; deleteButton.style.cursor = 'pointer'; deleteButton.style.padding = '4px'; deleteButton.style.display = 'flex'; deleteButton.style.alignItems = 'center'; deleteButton.style.justifyContent = 'center'; const deleteImg = document.createElement('img'); deleteImg.src = 'assets/icons/png/delete_b.png'; deleteImg.alt = '删除'; deleteImg.style.width = '16px'; deleteImg.style.height = '16px'; deleteButton.appendChild(deleteImg); deleteButton.addEventListener('click', () => this.deleteService(index)); actions.appendChild(deleteButton); header.appendChild(serviceTitle); header.appendChild(actions); serviceItem.appendChild(header); // 显示服务属性 const propertiesContainer = document.createElement('div'); propertiesContainer.className = 'property-group'; propertiesContainer.style.marginTop = '12px'; Array.from(service.attributes).forEach(attr => { const propertyItem = document.createElement('div'); propertyItem.className = 'property-item'; const label = document.createElement('label'); label.className = 'property-label'; label.textContent = attr.name; if (attr.name === 'Name') { // 如果是Name属性,我们需要查看是否有ClassName属性 const classNameAttr = service.getAttribute('ClassName'); // 创建一个行容器,直接放在标题下方 const rowContainer = document.createElement('div'); rowContainer.style.display = 'flex'; rowContainer.style.width = '100%'; rowContainer.style.gap = '10px'; rowContainer.style.marginTop = '8px'; rowContainer.style.marginBottom = '8px'; // 名称标签和输入框组 const nameGroup = document.createElement('div'); nameGroup.style.display = 'flex'; nameGroup.style.alignItems = 'center'; nameGroup.style.flex = '1'; const nameLabel = document.createElement('label'); nameLabel.className = 'property-label'; nameLabel.textContent = 'Name'; nameLabel.style.marginRight = '5px'; nameLabel.style.minWidth = '60px'; const nameInput = document.createElement('input'); nameInput.className = 'property-input'; nameInput.type = 'text'; nameInput.value = attr.value; nameInput.dataset.path = `ServicesList/Service[${index}]@${attr.name}`; nameInput.title = '服务名称'; nameInput.addEventListener('change', () => { this.markEdited(); serviceTitle.textContent = nameInput.value || `服务 ${index + 1}`; }); nameGroup.appendChild(nameLabel); nameGroup.appendChild(nameInput); // 类名标签和输入框组 const classGroup = document.createElement('div'); classGroup.style.display = 'flex'; classGroup.style.alignItems = 'center'; classGroup.style.flex = '1'; const classLabel = document.createElement('label'); classLabel.className = 'property-label'; classLabel.textContent = 'ClassName'; classLabel.style.marginRight = '5px'; classLabel.style.minWidth = '80px'; const classInput = document.createElement('input'); classInput.className = 'property-input'; classInput.type = 'text'; classInput.value = classNameAttr || ''; classInput.dataset.path = `ServicesList/Service[${index}]@ClassName`; classInput.title = '服务C++类名'; classInput.addEventListener('change', () => { this.markEdited(); }); classGroup.appendChild(classLabel); classGroup.appendChild(classInput); // 将名称组和类名组添加到行容器 rowContainer.appendChild(nameGroup); rowContainer.appendChild(classGroup); // 直接添加到serviceItem,而不是添加到propertiesContainer serviceItem.appendChild(rowContainer); // 将ClassName标记为已处理,后面不需要再处理 service.setAttribute('ClassName_processed', 'true'); } else if (attr.name === 'ClassName') { // 如果是ClassName且已处理,则跳过 if (service.getAttribute('ClassName_processed') === 'true') { return; } // 如果Name还没被处理,我们需要创建完整的行 const nameAttr = service.getAttribute('Name'); // 创建一个行容器 const rowContainer = document.createElement('div'); rowContainer.style.display = 'flex'; rowContainer.style.width = '100%'; rowContainer.style.gap = '10px'; rowContainer.style.marginTop = '8px'; rowContainer.style.marginBottom = '8px'; // 名称标签和输入框组 const nameGroup = document.createElement('div'); nameGroup.style.display = 'flex'; nameGroup.style.alignItems = 'center'; nameGroup.style.flex = '1'; const nameLabel = document.createElement('label'); nameLabel.className = 'property-label'; nameLabel.textContent = 'Name'; nameLabel.style.marginRight = '5px'; nameLabel.style.minWidth = '60px'; const nameInput = document.createElement('input'); nameInput.className = 'property-input'; nameInput.type = 'text'; nameInput.value = nameAttr || ''; nameInput.dataset.path = `ServicesList/Service[${index}]@Name`; nameInput.title = '服务名称'; nameInput.addEventListener('change', () => { this.markEdited(); serviceTitle.textContent = nameInput.value || `服务 ${index + 1}`; }); nameGroup.appendChild(nameLabel); nameGroup.appendChild(nameInput); // 类名标签和输入框组 const classGroup = document.createElement('div'); classGroup.style.display = 'flex'; classGroup.style.alignItems = 'center'; classGroup.style.flex = '1'; const classLabel = document.createElement('label'); classLabel.className = 'property-label'; classLabel.textContent = 'ClassName'; classLabel.style.marginRight = '5px'; classLabel.style.minWidth = '80px'; const classInput = document.createElement('input'); classInput.className = 'property-input'; classInput.type = 'text'; classInput.value = attr.value; classInput.dataset.path = `ServicesList/Service[${index}]@ClassName`; classInput.title = '服务C++类名'; classInput.addEventListener('change', () => { this.markEdited(); }); classGroup.appendChild(classLabel); classGroup.appendChild(classInput); // 将名称组和类名组添加到行容器 rowContainer.appendChild(nameGroup); rowContainer.appendChild(classGroup); propertyItem.appendChild(rowContainer); propertiesContainer.appendChild(propertyItem); // 将Name标记为已处理 service.setAttribute('Name_processed', 'true'); } else { // 其他属性 const input = document.createElement('input'); input.className = 'property-input'; input.type = 'text'; input.value = attr.value; input.dataset.path = `ServicesList/Service[${index}]@${attr.name}`; input.addEventListener('change', () => { this.markEdited(); }); propertyItem.appendChild(label); propertyItem.appendChild(input); propertiesContainer.appendChild(propertyItem); } }); // 只有当有其他属性时才添加属性组 if (propertiesContainer.children.length > 0) { // 添加一个标题来分隔基本属性和其他属性 const otherPropsTitle = document.createElement('div'); otherPropsTitle.textContent = '其他属性'; otherPropsTitle.style.fontWeight = 'bold'; otherPropsTitle.style.fontSize = '0.9em'; otherPropsTitle.style.margin = '8px 0'; serviceItem.appendChild(otherPropsTitle); serviceItem.appendChild(propertiesContainer); } // 清除处理标记 Array.from(service.attributes).forEach(attr => { if (attr.name === 'Name_processed' || attr.name === 'ClassName_processed') { service.removeAttribute(attr.name); } }); serviceItem.appendChild(propertiesContainer); section.appendChild(serviceItem); }); } // 添加新服务按钮 const addButton = document.createElement('button'); addButton.className = 'add-button'; addButton.style.marginTop = '16px'; addButton.textContent = '添加服务'; addButton.title = '添加新的服务'; addButton.addEventListener('click', () => this.showAddServiceDialog()); section.appendChild(addButton); } container.appendChild(section); } // 标记编辑状态 markEdited() { if (!this.isEdited) { this.isEdited = true; // 更新保存按钮样式 const saveButton = this.shadowRoot.getElementById('saveConfig'); if (saveButton) { saveButton.classList.add('modified'); saveButton.title = '文件已修改,请保存'; } } } // 重置编辑状态 resetEditState() { this.isEdited = false; // 更新保存按钮样式 const saveButton = this.shadowRoot.getElementById('saveConfig'); if (saveButton) { saveButton.classList.remove('modified'); saveButton.title = ''; } } // 渲染Console输出和日志部分 renderConsoleAndLogSection(container, rootElement) { const section = document.createElement('div'); section.className = 'editor-section'; const title = document.createElement('div'); title.className = 'section-title'; title.textContent = '控制台和日志设置'; section.appendChild(title); const propertyGroup = document.createElement('div'); propertyGroup.className = 'property-group'; // 处理ConsoleOutput元素 const consoleElement = rootElement.querySelector('ConsoleOutput'); if (consoleElement) { const consoleTitle = document.createElement('div'); consoleTitle.style.gridColumn = '1 / -1'; consoleTitle.style.fontWeight = 'bold'; consoleTitle.textContent = '控制台输出'; propertyGroup.appendChild(consoleTitle); Array.from(consoleElement.attributes).forEach(attr => { const propertyItem = document.createElement('div'); propertyItem.className = 'property-item'; propertyItem.style.display = 'flex'; propertyItem.style.alignItems = 'center'; const checkboxContainer = document.createElement('div'); checkboxContainer.style.display = 'flex'; checkboxContainer.style.alignItems = 'center'; const checkbox = document.createElement('input'); checkbox.className = 'property-checkbox'; checkbox.type = 'checkbox'; checkbox.checked = attr.value === '1'; checkbox.dataset.path = `ConsoleOutput@${attr.name}`; checkbox.id = `console-${attr.name}`; checkbox.style.marginRight = '8px'; // 添加提示 if (attr.name === 'Debug') { checkbox.title = '是否输出调试信息'; } else if (attr.name === 'Info') { checkbox.title = '是否输出提示信息'; } else if (attr.name === 'Error') { checkbox.title = '是否输出错误信息'; } else if (attr.name === 'Warning') { checkbox.title = '是否输出警告信息'; } else { checkbox.title = `是否启用${attr.name}`; } checkbox.addEventListener('change', () => { // 更新XML数据 this.updateConsoleOrLogCheckbox(checkbox, 'ConsoleOutput', attr.name); this.markEdited(); }); const label = document.createElement('label'); label.className = 'property-label'; label.textContent = attr.name; label.htmlFor = `console-${attr.name}`; label.style.cursor = 'pointer'; checkboxContainer.appendChild(checkbox); checkboxContainer.appendChild(label); propertyItem.appendChild(checkboxContainer); propertyGroup.appendChild(propertyItem); }); } // 处理Log元素 const logElement = rootElement.querySelector('Log'); if (logElement) { const logTitle = document.createElement('div'); logTitle.style.gridColumn = '1 / -1'; logTitle.style.fontWeight = 'bold'; logTitle.style.marginTop = '12px'; logTitle.textContent = '日志设置'; propertyGroup.appendChild(logTitle); Array.from(logElement.attributes).forEach(attr => { const propertyItem = document.createElement('div'); propertyItem.className = 'property-item'; propertyItem.style.display = 'flex'; propertyItem.style.alignItems = 'center'; const checkboxContainer = document.createElement('div'); checkboxContainer.style.display = 'flex'; checkboxContainer.style.alignItems = 'center'; const checkbox = document.createElement('input'); checkbox.className = 'property-checkbox'; checkbox.type = 'checkbox'; checkbox.checked = attr.value === '1'; checkbox.dataset.path = `Log@${attr.name}`; checkbox.id = `log-${attr.name}`; checkbox.style.marginRight = '8px'; // 添加提示 if (attr.name === 'Debug') { checkbox.title = '是否记录调试信息'; } else if (attr.name === 'Info') { checkbox.title = '是否记录提示信息'; } else if (attr.name === 'Error') { checkbox.title = '是否记录错误信息'; } else if (attr.name === 'Warning') { checkbox.title = '是否记录警告信息'; } else { checkbox.title = `是否启用${attr.name}日志`; } checkbox.addEventListener('change', () => { // 更新XML数据 this.updateConsoleOrLogCheckbox(checkbox, 'Log', attr.name); this.markEdited(); }); const label = document.createElement('label'); label.className = 'property-label'; label.textContent = attr.name; label.htmlFor = `log-${attr.name}`; label.style.cursor = 'pointer'; checkboxContainer.appendChild(checkbox); checkboxContainer.appendChild(label); propertyItem.appendChild(checkboxContainer); propertyGroup.appendChild(propertyItem); }); } section.appendChild(propertyGroup); container.appendChild(section); } // 更新控制台或日志复选框值到XML updateConsoleOrLogCheckbox(checkbox, elementName, attrName) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); const element = xmlDoc.querySelector(elementName); if (element) { // 设置属性值为0或1 element.setAttribute(attrName, checkbox.checked ? '1' : '0'); // 更新XML内容 const serializer = new XMLSerializer(); this.xmlContent = serializer.serializeToString(xmlDoc); this.markEdited(); } } // 显示添加模型组对话框 showAddModelGroupDialog() { // 创建表单内容 const formContent = document.createElement('div'); formContent.className = 'modal-form'; // 获取可用的CPU核心选项 const availableCores = this.updateCPUAffinityOptions(); // 常用属性输入字段 const nameRow = document.createElement('div'); nameRow.className = 'form-row'; const nameLabel = document.createElement('label'); nameLabel.className = 'form-label'; nameLabel.textContent = '名称'; const nameInput = document.createElement('input'); nameInput.className = 'form-input'; nameInput.name = 'Name'; nameInput.type = 'text'; nameInput.value = '新模型组'; nameRow.appendChild(nameLabel); nameRow.appendChild(nameInput); formContent.appendChild(nameRow); // FreqGroup输入字段 const freqGroupRow = document.createElement('div'); freqGroupRow.className = 'form-row'; const freqGroupLabel = document.createElement('label'); freqGroupLabel.className = 'form-label'; freqGroupLabel.textContent = '频率组'; const freqGroupInput = document.createElement('input'); freqGroupInput.className = 'form-input'; freqGroupInput.name = 'FreqGroup'; freqGroupInput.type = 'number'; freqGroupInput.min = '0'; freqGroupInput.max = '6'; freqGroupInput.value = '0'; freqGroupInput.title = '频率组,取值范围0-6'; freqGroupRow.appendChild(freqGroupLabel); freqGroupRow.appendChild(freqGroupInput); formContent.appendChild(freqGroupRow); // Priority输入字段 const priorityRow = document.createElement('div'); priorityRow.className = 'form-row'; const priorityLabel = document.createElement('label'); priorityLabel.className = 'form-label'; priorityLabel.textContent = '优先级'; const priorityInput = document.createElement('input'); priorityInput.className = 'form-input'; priorityInput.name = 'Priority'; priorityInput.type = 'number'; priorityInput.min = '0'; priorityInput.max = '99'; priorityInput.value = '99'; priorityInput.title = '线程优先级,取值范围0-99'; priorityRow.appendChild(priorityLabel); priorityRow.appendChild(priorityInput); formContent.appendChild(priorityRow); // CPUAff下拉字段 const cpuAffRow = document.createElement('div'); cpuAffRow.className = 'form-row'; const cpuAffLabel = document.createElement('label'); cpuAffLabel.className = 'form-label'; cpuAffLabel.textContent = 'CPU亲和性'; const cpuAffSelect = document.createElement('select'); cpuAffSelect.className = 'form-input'; cpuAffSelect.name = 'CPUAff'; cpuAffSelect.title = '选择一个可用的CPU核心'; // 添加可用的CPU核心选项 if (availableCores.length > 0) { availableCores.forEach(core => { const option = document.createElement('option'); option.value = core; option.textContent = `CPU ${core}`; cpuAffSelect.appendChild(option); }); } else { // 如果没有可用选项,添加一个默认选项 const option = document.createElement('option'); option.value = '0'; option.textContent = `CPU 0`; cpuAffSelect.appendChild(option); } cpuAffRow.appendChild(cpuAffLabel); cpuAffRow.appendChild(cpuAffSelect); formContent.appendChild(cpuAffRow); // 显示对话框 this.createModal('添加新模型组', formContent, () => { const props = { Name: nameInput.value, FreqGroup: freqGroupInput.value, Priority: priorityInput.value, CPUAff: cpuAffSelect.value }; this.addModelGroup(props); }); } // 显示添加模型对话框 showAddModelDialog(groupIndex) { // 创建表单内容 const formContent = document.createElement('div'); formContent.className = 'modal-form'; // 创建一个行容器来放置Name和ClassName const rowContainer = document.createElement('div'); rowContainer.style.display = 'flex'; rowContainer.style.width = '100%'; rowContainer.style.gap = '10px'; rowContainer.style.marginBottom = '10px'; // 名称部分 const nameGroup = document.createElement('div'); nameGroup.style.display = 'flex'; nameGroup.style.flexDirection = 'column'; nameGroup.style.flex = '1'; const nameLabel = document.createElement('label'); nameLabel.className = 'form-label'; nameLabel.textContent = '名称'; nameLabel.style.marginBottom = '5px'; const nameInput = document.createElement('input'); nameInput.className = 'form-input'; nameInput.name = 'Name'; nameInput.type = 'text'; nameInput.value = '新模型'; nameGroup.appendChild(nameLabel); nameGroup.appendChild(nameInput); // 类名部分 const classGroup = document.createElement('div'); classGroup.style.display = 'flex'; classGroup.style.flexDirection = 'column'; classGroup.style.flex = '1'; const classLabel = document.createElement('label'); classLabel.className = 'form-label'; classLabel.textContent = '类名'; classLabel.style.marginBottom = '5px'; const classInput = document.createElement('input'); classInput.className = 'form-input'; classInput.name = 'ClassName'; classInput.type = 'text'; classInput.value = 'XNModel'; classGroup.appendChild(classLabel); classGroup.appendChild(classInput); // 将两个组添加到容器中 rowContainer.appendChild(nameGroup); rowContainer.appendChild(classGroup); // 将容器添加到表单中 formContent.appendChild(rowContainer); // 显示对话框 this.createModal('添加新模型', formContent, () => { const props = {}; formContent.querySelectorAll('.form-input').forEach(input => { props[input.name] = input.value; }); this.addModel(groupIndex, props); }); } // 显示添加服务对话框 showAddServiceDialog() { // 创建表单内容 const formContent = document.createElement('div'); formContent.className = 'modal-form'; // 创建一个行容器来放置Name和ClassName const rowContainer = document.createElement('div'); rowContainer.style.display = 'flex'; rowContainer.style.width = '100%'; rowContainer.style.gap = '10px'; rowContainer.style.marginBottom = '10px'; // 名称部分 const nameGroup = document.createElement('div'); nameGroup.style.display = 'flex'; nameGroup.style.flexDirection = 'column'; nameGroup.style.flex = '1'; const nameLabel = document.createElement('label'); nameLabel.className = 'form-label'; nameLabel.textContent = '名称'; nameLabel.style.marginBottom = '5px'; const nameInput = document.createElement('input'); nameInput.className = 'form-input'; nameInput.name = 'Name'; nameInput.type = 'text'; nameInput.value = '新服务'; nameGroup.appendChild(nameLabel); nameGroup.appendChild(nameInput); // 类名部分 const classGroup = document.createElement('div'); classGroup.style.display = 'flex'; classGroup.style.flexDirection = 'column'; classGroup.style.flex = '1'; const classLabel = document.createElement('label'); classLabel.className = 'form-label'; classLabel.textContent = '类名'; classLabel.style.marginBottom = '5px'; const classInput = document.createElement('input'); classInput.className = 'form-input'; classInput.name = 'ClassName'; classInput.type = 'text'; classInput.value = 'XNService'; classGroup.appendChild(classLabel); classGroup.appendChild(classInput); // 将两个组添加到容器中 rowContainer.appendChild(nameGroup); rowContainer.appendChild(classGroup); // 将容器添加到表单中 formContent.appendChild(rowContainer); // 显示对话框 this.createModal('添加新服务', formContent, () => { const props = {}; formContent.querySelectorAll('.form-input').forEach(input => { props[input.name] = input.value; }); this.addService(props); }); } // 格式化XML字符串 formatXml(xml) { if (!xml || !xml.trim()) return ''; try { let formatted = ''; const parser = new DOMParser(); const doc = parser.parseFromString(xml, 'text/xml'); // 检查是否解析错误 const parseError = doc.querySelector('parsererror'); if (parseError) { console.warn('XML解析错误,返回原始文本'); return xml; // 如果解析错误,返回原始文本 } // 使用XMLSerializer序列化为字符串 const serializer = new XMLSerializer(); formatted = serializer.serializeToString(doc); // 简单格式化:替换'><'为'>\n<'添加换行 formatted = formatted.replace(/><(?!\/)/g, '>\n<'); // 添加缩进 const lines = formatted.split('\n'); let indent = 0; formatted = lines.map(line => { if (line.match(/<\/[^>]+>/)) { // 关闭标签,减少缩进 indent--; } const result = ' '.repeat(Math.max(0, indent * 2)) + line; if (line.match(/<[^\/][^>]*[^\/]>/) && !line.match(/<.*\/>/) && !line.match(/<[^>]+><\/[^>]+>/)) { // 开放标签,增加缩进 indent++; } return result; }).join('\n'); return formatted; } catch (e) { console.error('XML格式化失败:', e); return xml; } } // 转义HTML字符 escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } render() { this.shadowRoot.innerHTML = `
运行环境配置文件:
请选择一个运行环境配置文件查看内容
`; } // 在完成渲染后同时处理状态恢复 renderComplete() { // 在DOM渲染完成后更新文件选择器 setTimeout(() => { this.updateFileSelector(); // 如果有当前文件,尝试选择它 if (this.currentFile && this.xmlContent) { const fileSelector = this.shadowRoot.getElementById('scenarioFile'); if (fileSelector) { // 确认文件在列表中 const fileExists = Array.from(fileSelector.options).some(opt => opt.value === this.currentFile); if (fileExists) { // 设置选中的文件 fileSelector.value = this.currentFile; // 解析XML内容 try { const parser = new DOMParser(); this.xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); // 更新内容显示 const contentElement = this.shadowRoot.getElementById('fileContent'); if (contentElement) { contentElement.innerHTML = ''; this.renderVisualEditor(contentElement, this.xmlDoc); } // 如果有编辑状态,恢复它 if (this.isEdited) { const saveButton = this.shadowRoot.getElementById('saveConfig'); if (saveButton) { saveButton.classList.add('modified'); saveButton.title = '文件已修改,请保存'; } } } catch (error) { console.error('恢复XML内容失败:', error); } } else { // 文件不存在,清除状态 this.currentFile = null; this.xmlContent = ''; this.isEdited = false; } } } }, 50); // 增加延迟确保DOM已经完全加载 } setupEventListeners() { // 新建按钮 const newButton = this.shadowRoot.getElementById('newConfig'); newButton.addEventListener('click', () => { this.showNewConfigDialog(); }); // 保存按钮 const saveButton = this.shadowRoot.getElementById('saveConfig'); saveButton.addEventListener('click', () => { if (this.currentFile && this.xmlContent) { this.saveFileContent(this.currentFile, this.xmlContent); } else { // 如果没有选择文件,则提示另存为 if (this.xmlContent) { this.showSaveAsDialog(); } } }); // 另存为按钮 const saveAsButton = this.shadowRoot.getElementById('saveAsConfig'); saveAsButton.addEventListener('click', () => { this.showSaveAsDialog(); }); // 刷新按钮 const refreshButton = this.shadowRoot.getElementById('refreshButton'); refreshButton.addEventListener('click', () => { // 使用更可靠的方法检查是否有未保存的修改 - 通过保存按钮的class const saveButton = this.shadowRoot.getElementById('saveConfig'); const hasUnsavedChanges = saveButton && saveButton.classList.contains('modified'); // 如果有未保存的修改,先确认是否要放弃修改 if (hasUnsavedChanges) { const confirmResult = confirm('当前文件有未保存的修改,刷新将丢失这些修改。是否继续?'); if (!confirmResult) { return; } } this.loadScenarioFiles(); }); // 文件选择下拉框 const fileSelector = this.shadowRoot.getElementById('scenarioFile'); fileSelector.addEventListener('change', (e) => { // 使用更可靠的方法检查是否有未保存的修改 - 通过保存按钮的class const saveButton = this.shadowRoot.getElementById('saveConfig'); const hasUnsavedChanges = saveButton && saveButton.classList.contains('modified'); // 如果当前文件有修改,提示用户保存 if (hasUnsavedChanges) { const confirmResult = confirm('当前文件已修改但未保存,是否放弃修改?'); if (!confirmResult) { // 恢复之前的选择 fileSelector.value = this.currentFile || ''; return; } } const selectedFile = e.target.value; if (selectedFile) { this.loadFileContent(selectedFile); } else { this.currentFile = null; this.xmlContent = ''; const contentElement = this.shadowRoot.getElementById('fileContent'); contentElement.innerHTML = '
请选择一个运行环境配置文件查看内容
'; this.resetEditState(); } }); } // 显示新建配置对话框 showNewConfigDialog() { // 使用更可靠的方法检查是否有未保存的修改 - 通过保存按钮的class const saveButton = this.shadowRoot.getElementById('saveConfig'); const hasUnsavedChanges = saveButton && saveButton.classList.contains('modified'); // 如果当前文件有修改,提示用户保存 if (hasUnsavedChanges) { const confirmResult = confirm('当前文件已修改但未保存,是否放弃修改?'); if (!confirmResult) { return; } } // 创建表单容器 const formContent = document.createElement('div'); // 创建文件名输入组 const formGroup = document.createElement('div'); formGroup.className = 'form-group'; formGroup.style.marginBottom = '16px'; // 创建标签 const label = document.createElement('label'); label.htmlFor = 'fileName'; label.textContent = '文件名'; label.style.display = 'block'; label.style.marginBottom = '8px'; label.style.fontWeight = '500'; label.style.color = '#2d3748'; // 创建输入框 const input = document.createElement('input'); input.id = 'fileName'; input.className = 'form-control'; input.type = 'text'; input.value = 'NewConfig.xml'; input.placeholder = '请输入文件名(以.xml或.sce结尾)'; input.style.width = '100%'; input.style.padding = '10px 12px'; input.style.border = '1px solid #ddd'; input.style.borderRadius = '6px'; input.style.fontSize = '15px'; input.style.boxSizing = 'border-box'; // 组装表单组 formGroup.appendChild(label); formGroup.appendChild(input); formContent.appendChild(formGroup); // 显示对话框 const modal = this.createModal('新建配置文件', formContent, () => { const fileName = input.value.trim(); if (!fileName) { alert('文件名不能为空'); return; } // 验证文件名 if (!fileName.endsWith('.xml') && !fileName.endsWith('.sce')) { alert('文件名必须以.xml或.sce结尾'); return; } // 创建新文件 this.createNewConfig(fileName); // 关闭模态框 modal.modal.remove(); }); } // 显示另存为对话框 showSaveAsDialog() { if (!this.xmlContent) { alert('没有内容可保存'); return; } // 创建表单容器 const formContent = document.createElement('div'); // 创建文件名输入组 const formGroup = document.createElement('div'); formGroup.className = 'form-group'; formGroup.style.marginBottom = '16px'; // 创建标签 const label = document.createElement('label'); label.htmlFor = 'fileName'; label.textContent = '文件名'; label.style.display = 'block'; label.style.marginBottom = '8px'; label.style.fontWeight = '500'; label.style.color = '#2d3748'; // 创建输入框 const input = document.createElement('input'); input.id = 'fileName'; input.className = 'form-control'; input.type = 'text'; input.value = 'NewConfig.xml'; input.placeholder = '请输入文件名(以.xml或.sce结尾)'; input.style.width = '100%'; input.style.padding = '10px 12px'; input.style.border = '1px solid #ddd'; input.style.borderRadius = '6px'; input.style.fontSize = '15px'; input.style.boxSizing = 'border-box'; // 如果当前文件存在,提取文件名作为默认值 if (this.currentFile) { const pathParts = this.currentFile.split('/'); const currentFileName = pathParts[pathParts.length - 1]; input.value = currentFileName; } // 组装表单组 formGroup.appendChild(label); formGroup.appendChild(input); formContent.appendChild(formGroup); // 显示对话框 const modal = this.createModal('另存为', formContent, async () => { const fileName = input.value.trim(); if (!fileName) { alert('文件名不能为空'); return; } // 验证文件名 if (!fileName.endsWith('.xml') && !fileName.endsWith('.sce')) { alert('文件名必须以.xml或.sce结尾'); return; } try { // 调用API保存文件 const response = await fetch('/api/save-as-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName: fileName, content: this.xmlContent, currentFile: this.currentFile || '' }) }); if (!response.ok) { const errorData = await response.json(); // 如果是文件已存在错误,询问是否覆盖 if (errorData.code === 'FILE_EXISTS') { if (!confirm('文件已存在,是否覆盖?')) { return; } // 用户确认覆盖,再次发送请求并添加覆盖标记 const overwriteResponse = await fetch('/api/save-as-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName: fileName, content: this.xmlContent, currentFile: this.currentFile || '', overwrite: true }) }); if (!overwriteResponse.ok) { const overwriteError = await overwriteResponse.json(); throw new Error(overwriteError.error || '保存文件失败'); } const overwriteData = await overwriteResponse.json(); // 更新当前文件路径 this.currentFile = overwriteData.path; } else { throw new Error(errorData.error || '保存文件失败'); } } else { const data = await response.json(); // 更新当前文件路径 this.currentFile = data.path; } // 重新加载文件列表 await this.loadScenarioFiles(); // 在文件列表中选择新保存的文件 const fileSelector = this.shadowRoot.getElementById('scenarioFile'); fileSelector.value = this.currentFile; // 重置编辑状态 this.resetEditState(); // 关闭模态框 modal.modal.remove(); } catch (error) { console.error('另存为失败:', error); alert(`另存为失败: ${error.message}`); } }); } async saveFileContent(filePath, content) { try { // 更新XML内容 if (this.autoSaveToXml) { this.updateXmlFromVisualEditor(); } // 格式化XML内容 const formattedContent = this.formatXml(this.xmlContent); const response = await fetch('/api/save-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: filePath, content: formattedContent }) }); if (!response.ok) { throw new Error(`保存文件失败: ${response.status} ${response.statusText}`); } // 重置编辑状态 this.resetEditState(); // 更新当前文件 this.currentFile = filePath; // 刷新文件列表 await this.loadScenarioFiles(); // 选中当前文件 const fileSelector = this.shadowRoot.getElementById('scenarioFile'); if (fileSelector && this.currentFile) { fileSelector.value = this.currentFile; } alert('文件保存成功'); } catch (error) { console.error('保存文件失败:', error); alert(`保存文件失败: ${error.message}`); } } // 编辑模型组 editModelGroup(groupIndex) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); const modelGroups = xmlDoc.querySelectorAll('ModelGroup'); if (groupIndex >= 0 && groupIndex < modelGroups.length) { const group = modelGroups[groupIndex]; let properties = {}; Array.from(group.attributes).forEach(attr => { properties[attr.name] = attr.value; }); // 显示一个简单的属性编辑对话框 const propertyNames = Object.keys(properties).join(', '); const message = `请编辑模型组属性,格式为:\n属性名1=值1, 属性名2=值2, ...\n\n当前属性: ${propertyNames}`; const input = prompt(message, Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(', ')); if (input !== null) { // 解析输入的属性值 const pairs = input.split(',').map(pair => pair.trim()); const newProps = {}; pairs.forEach(pair => { const [key, value] = pair.split('=').map(item => item.trim()); if (key && value !== undefined) { newProps[key] = value; } }); // 更新属性 for (const [name, value] of Object.entries(newProps)) { group.setAttribute(name, value); } // 重新生成XML内容 const serializer = new XMLSerializer(); this.xmlContent = serializer.serializeToString(xmlDoc); // 更新编辑器 this.updateFileContent(); this.markEdited(); } } } // 删除模型组 deleteModelGroup(groupIndex) { if (confirm('确定要删除此模型组吗?此操作不可撤销。')) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); const modelGroups = xmlDoc.querySelectorAll('ModelGroup'); if (groupIndex >= 0 && groupIndex < modelGroups.length) { const group = modelGroups[groupIndex]; group.parentNode.removeChild(group); // 重新生成XML内容 const serializer = new XMLSerializer(); this.xmlContent = serializer.serializeToString(xmlDoc); // 更新编辑器 this.updateFileContent(); this.markEdited(); } } } // 添加模型组 addModelGroup(properties = {}) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); // 创建新的模型组 const newGroup = xmlDoc.createElement('ModelGroup'); // 设置默认属性 const defaultProps = { Name: '新模型组', FreqGroup: '0', Priority: '99', CPUAff: '0' }; // 合并默认属性和传入的属性 const finalProps = { ...defaultProps, ...properties }; // 设置属性 for (const [name, value] of Object.entries(finalProps)) { newGroup.setAttribute(name, value); } // 添加到根元素 xmlDoc.documentElement.appendChild(newGroup); // 重新生成XML内容 const serializer = new XMLSerializer(); this.xmlContent = serializer.serializeToString(xmlDoc); // 更新编辑器 this.updateFileContent(); this.markEdited(); } // 编辑模型 editModel(groupIndex, modelIndex) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); const modelGroups = xmlDoc.querySelectorAll('ModelGroup'); if (groupIndex >= 0 && groupIndex < modelGroups.length) { const group = modelGroups[groupIndex]; const models = group.querySelectorAll('Model'); if (modelIndex >= 0 && modelIndex < models.length) { const model = models[modelIndex]; let properties = {}; Array.from(model.attributes).forEach(attr => { properties[attr.name] = attr.value; }); // 显示一个简单的属性编辑对话框 const propertyNames = Object.keys(properties).join(', '); const message = `请编辑模型属性,格式为:\n属性名1=值1, 属性名2=值2, ...\n\n当前属性: ${propertyNames}`; const input = prompt(message, Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(', ')); if (input !== null) { // 解析输入的属性值 const pairs = input.split(',').map(pair => pair.trim()); const newProps = {}; pairs.forEach(pair => { const [key, value] = pair.split('=').map(item => item.trim()); if (key && value !== undefined) { newProps[key] = value; } }); // 更新属性 for (const [name, value] of Object.entries(newProps)) { model.setAttribute(name, value); } // 重新生成XML内容 const serializer = new XMLSerializer(); this.xmlContent = serializer.serializeToString(xmlDoc); // 更新编辑器 this.updateFileContent(); this.markEdited(); } } } } // 删除模型 deleteModel(groupIndex, modelIndex) { if (confirm('确定要删除此模型吗?此操作不可撤销。')) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); const modelGroups = xmlDoc.querySelectorAll('ModelGroup'); if (groupIndex >= 0 && groupIndex < modelGroups.length) { const group = modelGroups[groupIndex]; const models = group.querySelectorAll('Model'); if (modelIndex >= 0 && modelIndex < models.length) { const model = models[modelIndex]; model.parentNode.removeChild(model); // 重新生成XML内容 const serializer = new XMLSerializer(); this.xmlContent = serializer.serializeToString(xmlDoc); // 更新编辑器 this.updateFileContent(); this.markEdited(); } } } } // 添加模型 addModel(groupIndex, properties = {}) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); const modelGroups = xmlDoc.querySelectorAll('ModelGroup'); if (groupIndex >= 0 && groupIndex < modelGroups.length) { const group = modelGroups[groupIndex]; // 创建新的模型元素 const newModel = xmlDoc.createElement('Model'); // 设置默认属性 const defaultProps = { Name: '新模型', ClassName: 'XNModel' }; // 合并默认属性和传入的属性 const finalProps = { ...defaultProps, ...properties }; // 设置属性 for (const [name, value] of Object.entries(finalProps)) { if (value.trim()) { newModel.setAttribute(name, value); } } // 添加到模型组 group.appendChild(newModel); // 重新生成XML内容 const serializer = new XMLSerializer(); this.xmlContent = serializer.serializeToString(xmlDoc); // 更新编辑器 this.updateFileContent(); this.markEdited(); } } // 编辑服务 editService(serviceIndex) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); const servicesList = xmlDoc.querySelector('ServicesList'); if (servicesList) { const services = servicesList.querySelectorAll('Service'); if (serviceIndex >= 0 && serviceIndex < services.length) { const service = services[serviceIndex]; let properties = {}; Array.from(service.attributes).forEach(attr => { properties[attr.name] = attr.value; }); // 显示一个简单的属性编辑对话框 const propertyNames = Object.keys(properties).join(', '); const message = `请编辑服务属性,格式为:\n属性名1=值1, 属性名2=值2, ...\n\n当前属性: ${propertyNames}`; const input = prompt(message, Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(', ')); if (input !== null) { // 解析输入的属性值 const pairs = input.split(',').map(pair => pair.trim()); const newProps = {}; pairs.forEach(pair => { const [key, value] = pair.split('=').map(item => item.trim()); if (key && value !== undefined) { newProps[key] = value; } }); // 更新属性 for (const [name, value] of Object.entries(newProps)) { service.setAttribute(name, value); } // 重新生成XML内容 const serializer = new XMLSerializer(); this.xmlContent = serializer.serializeToString(xmlDoc); // 更新编辑器 this.updateFileContent(); this.markEdited(); } } } } // 删除服务 deleteService(serviceIndex) { if (confirm('确定要删除此服务吗?此操作不可撤销。')) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); const servicesList = xmlDoc.querySelector('ServicesList'); if (servicesList) { const services = servicesList.querySelectorAll('Service'); if (serviceIndex >= 0 && serviceIndex < services.length) { const service = services[serviceIndex]; service.parentNode.removeChild(service); // 重新生成XML内容 const serializer = new XMLSerializer(); this.xmlContent = serializer.serializeToString(xmlDoc); // 更新编辑器 this.updateFileContent(); this.markEdited(); } } } } // 添加服务 addService(properties = {}) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); let servicesList = xmlDoc.querySelector('ServicesList'); if (!servicesList) { // 如果没有ServicesList,创建一个 servicesList = xmlDoc.createElement('ServicesList'); xmlDoc.documentElement.appendChild(servicesList); } // 创建新的服务元素 const newService = xmlDoc.createElement('Service'); // 设置默认属性 const defaultProps = { Name: '新服务', ClassName: 'XNService' }; // 合并默认属性和传入的属性 const finalProps = { ...defaultProps, ...properties }; // 设置属性 for (const [name, value] of Object.entries(finalProps)) { if (value.trim()) { newService.setAttribute(name, value); } } // 添加到服务列表 servicesList.appendChild(newService); // 重新生成XML内容 const serializer = new XMLSerializer(); this.xmlContent = serializer.serializeToString(xmlDoc); // 更新编辑器 this.updateFileContent(); this.markEdited(); } // 更新XML内容 updateXmlFromVisualEditor(silent = true) { // 获取所有输入控件 const inputs = Array.from(this.shadowRoot.querySelectorAll('.property-input')); // 解析当前XML const parser = new DOMParser(); const xmlDoc = parser.parseFromString(this.xmlContent, 'text/xml'); // 更新XML值 inputs.forEach(input => { const path = input.dataset.path; if (!path) return; // 解析路径 if (path.includes('@')) { const [elementPath, attrName] = path.split('@'); let element = null; // 处理不同类型的元素路径 if (elementPath.includes('[') && elementPath.includes(']')) { // 复杂路径(如 ModelGroup[0]/Model[1]) const parts = elementPath.split('/'); let currentNode = xmlDoc.documentElement; for (const part of parts) { const match = part.match(/^(\w+)(?:\[(\d+)\])?$/); if (match) { const [, tagName, indexStr] = match; const targetNodes = currentNode.getElementsByTagName(tagName); if (indexStr !== undefined) { const index = parseInt(indexStr, 10); if (index < targetNodes.length) { currentNode = targetNodes[index]; } else { console.error(`Index out of range: ${part}`); return; } } else if (targetNodes.length > 0) { currentNode = targetNodes[0]; } else { console.error(`Element not found: ${part}`); return; } } } element = currentNode; } else { // 简单路径(如 Environment) element = xmlDoc.getElementsByTagName(elementPath)[0]; } if (element) { // 检查是否为新添加的属性 if (input.dataset.isNew === 'true') { element.setAttribute(attrName, input.value); } else { // 更新现有属性 if (element.hasAttribute(attrName)) { element.setAttribute(attrName, input.value); } } } } }); // 更新XML内容 const serializer = new XMLSerializer(); this.xmlContent = serializer.serializeToString(xmlDoc); // 不再提示更新成功 this.isEdited = false; } // 创建新配置文件 async createNewConfig(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 || '创建文件失败'); } const data = await response.json(); // 更新当前文件 this.currentFile = data.path; this.xmlContent = data.content; // 更新内容区域 this.updateFileContent(); // 重新加载文件列表 await this.loadScenarioFiles(); // 选择新文件 const fileSelector = this.shadowRoot.getElementById('scenarioFile'); fileSelector.value = this.currentFile; // 重置编辑状态 this.resetEditState(); return true; } catch (error) { console.error('创建新配置文件失败:', error); alert(`创建新配置文件失败: ${error.message}`); return false; } } // 组件销毁时移除事件监听器 disconnectedCallback() { // 移除可能的事件监听器 const fileSelector = this.shadowRoot.getElementById('scenarioFile'); if (fileSelector) { fileSelector.removeEventListener('change', this.handleFileSelectorChange); } } } customElements.define('run-env-config', RunEnvConfig);