diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index 3b232e4..1f099b0 100644 Binary files a/Release/database/XNSim.db and b/Release/database/XNSim.db differ diff --git a/XNSimHtml/components/configuration-config.js b/XNSimHtml/components/configuration-config.js new file mode 100644 index 0000000..9772f79 --- /dev/null +++ b/XNSimHtml/components/configuration-config.js @@ -0,0 +1,1046 @@ +/** + * @class ConfigurationConfig + * @classdesc 构型配置开发组件,采用卡片风格,展示机型信息和构型信息。 + * @extends HTMLElement + */ +class ConfigurationConfig extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.currentView = 'planes'; // 'planes' 或 'configurations' 或 'editor' + this.selectedPlane = null; + this.selectedConfiguration = null; + this.userAccessLevel = 0; // 添加用户权限级别属性 + + /** + * 初始化DOM结构,采用卡片风格,展示机型信息和构型信息。 + */ + this.shadowRoot.innerHTML = ` + +
+ +
+
加载中...
+
+ +
+ `; + + // 绑定事件 + this.shadowRoot.getElementById('backButton').addEventListener('click', (e) => { + e.preventDefault(); + if (this.currentView === 'editor') { + this.showConfigurations(this.selectedPlane); + } else { + this.showPlanes(); + } + }); + + // 绑定编辑器按钮事件 + this.shadowRoot.getElementById('saveButton').addEventListener('click', () => this.saveConfiguration()); + this.shadowRoot.getElementById('cancelButton').addEventListener('click', () => { + this.showConfigurations(this.selectedPlane); + }); + + // 添加输入验证 + this.shadowRoot.getElementById('cpuAffinity').addEventListener('input', (e) => { + // 只允许数字和英文逗号 + e.target.value = e.target.value.replace(/[^0-9,]/g, ''); + }); + + this.shadowRoot.getElementById('domainId').addEventListener('input', (e) => { + // 只允许数字 + e.target.value = e.target.value.replace(/[^0-9]/g, ''); + // 限制范围在0-232之间 + const value = parseInt(e.target.value); + if (value > 232) { + e.target.value = '232'; + } + }); + + // 获取机型数据 + this.loadPlanes(); + } + + /** + * @method loadPlanes + * @description 从服务器获取机型数据并更新UI + */ + async loadPlanes() { + try { + const response = await fetch('/api/planes'); + if (!response.ok) { + throw new Error('获取机型数据失败'); + } + const planes = await response.json(); + this.renderPlanes(planes); + } catch (error) { + console.error('加载机型数据失败:', error); + this.showError('加载机型数据失败,请稍后重试'); + } + } + + /** + * @method loadConfigurations + * @description 从服务器获取构型数据并更新UI + * @param {string} planeName - 机型名称 + */ + async loadConfigurations(planeName) { + try { + const response = await fetch(`/api/configurations?plane=${encodeURIComponent(planeName)}`); + if (!response.ok) { + throw new Error('获取构型数据失败'); + } + const configs = await response.json(); + this.renderConfigurations(configs, planeName); + } catch (error) { + console.error('加载构型数据失败:', error); + this.showError('加载构型数据失败,请稍后重试'); + } + } + + /** + * @method renderPlanes + * @description 渲染机型卡片 + * @param {Array} planes - 机型数据数组 + */ + renderPlanes(planes) { + const container = this.shadowRoot.getElementById('contentContainer'); + if (!planes || planes.length === 0) { + container.innerHTML = '
暂无机型数据
'; + return; + } + + container.innerHTML = planes.map(plane => ` +
+
+ ${plane.Icon ? `${plane.PlaneName}` : ''} +
+
+
${plane.PlaneName || '未知机型'}
+
${plane.Description || '暂无描述'}
+
+
+ `).join(''); + + // 添加点击事件 + container.querySelectorAll('.config-card').forEach(card => { + card.addEventListener('click', () => { + const planeName = card.dataset.plane; + this.showConfigurations(planeName); + }); + }); + } + + /** + * @method showBaseConfigDialog + * @description 显示基准构型选择对话框 + * @param {Array} configs - 构型数据数组 + * @param {string} planeName - 机型名称 + * @returns {Promise} 返回选择的基准构型或undefined(表示不使用基准构型) + */ + showBaseConfigDialog(configs, planeName) { + return new Promise((resolve) => { + const dialog = document.createElement('div'); + dialog.className = 'base-config-dialog'; + dialog.innerHTML = ` + +
+
选择基准构型
+
+ + +
+
+ + +
+
+ `; + + const select = dialog.querySelector('#baseConfigSelect'); + + dialog.querySelector('#cancelButton').addEventListener('click', () => { + document.body.removeChild(dialog); + resolve(null); // 取消时返回null + }); + + dialog.querySelector('#confirmButton').addEventListener('click', () => { + const selectedValue = select.value; + const selectedConfig = selectedValue === 'none' ? + undefined : // 选择"不使用基准构型"时返回undefined + configs.find(c => c.ConfID === parseInt(selectedValue)); + document.body.removeChild(dialog); + resolve(selectedConfig); + }); + + document.body.appendChild(dialog); + }); + } + + /** + * @method checkUserAccessLevel + * @description 检查用户访问权限级别 + */ + async checkUserAccessLevel() { + try { + const response = await fetch('/api/check-auth', { + credentials: 'include' + }); + const result = await response.json(); + + if (result.success) { + this.userAccessLevel = parseInt(result.user.access_level); + // 重新渲染构型列表以更新删除按钮的显示状态 + if (this.currentView === 'configurations' && this.selectedPlane) { + this.loadConfigurations(this.selectedPlane); + } + } + } catch (error) { + console.error('获取用户权限失败:', error); + } + } + + /** + * @method renderConfigurations + * @description 渲染构型卡片 + * @param {Array} configs - 构型数据数组 + * @param {string} planeName - 机型名称 + */ + renderConfigurations(configs, planeName) { + const container = this.shadowRoot.getElementById('contentContainer'); + if (!configs || configs.length === 0) { + container.innerHTML = '
暂无构型数据
'; + return; + } + + // 添加新建构型卡片(根据权限显示) + container.innerHTML = ` + ${this.userAccessLevel >= 2 ? ` +
+
+ 新建构型
+
+ ` : ''} + ${configs.map(config => ` +
+
${config.ConfName || '未知构型'}
+ ${this.userAccessLevel >= 2 ? ` + + ` : ''} +
+ `).join('')} + `; + + // 添加新建构型卡片点击事件(仅在权限足够时添加) + if (this.userAccessLevel >= 2) { + container.querySelector('#newConfigCard')?.addEventListener('click', async () => { + const baseConfig = await this.showBaseConfigDialog(configs, planeName); + if (baseConfig !== null) { + const defaultConfig = { + ConfName: '', + OSName: baseConfig?.OSName || '', + OSVersion: baseConfig?.OSVersion || '', + RTXVersion: baseConfig?.RTXVersion || '', + CPUAffinity: baseConfig?.CPUAffinity || '', + DomainID: baseConfig?.DomainID || '', + WorkPath: baseConfig?.WorkPath || '', + ModelsPath: baseConfig?.ModelsPath || '', + ServicesPath: baseConfig?.ServicesPath || '', + ConsoleDebug: baseConfig?.ConsoleDebug ?? 1, + ConsoleInfo: baseConfig?.ConsoleInfo ?? 1, + ConsoleWarning: baseConfig?.ConsoleWarning ?? 1, + ConsoleError: baseConfig?.ConsoleError ?? 1, + LogDebug: baseConfig?.LogDebug ?? 0, + LogInfo: baseConfig?.LogInfo ?? 1, + LogWarning: baseConfig?.LogWarning ?? 1, + LogError: baseConfig?.LogError ?? 1 + }; + this.showEditor(defaultConfig, planeName, true); + } + }); + } + + // 添加构型卡片点击事件 + container.querySelectorAll('.config-card:not(.new-config)').forEach(card => { + card.addEventListener('click', (e) => { + // 如果点击的是删除按钮,不触发编辑 + if (e.target.closest('.delete-button')) { + return; + } + const configId = parseInt(card.dataset.configId, 10); + const config = configs.find(c => c.ConfID === configId); + if (config) { + this.showEditor(config, planeName, false); + } + }); + }); + + // 添加删除按钮点击事件 + container.querySelectorAll('.delete-button').forEach(button => { + button.addEventListener('click', async (e) => { + e.stopPropagation(); // 阻止事件冒泡 + + // 再次检查权限 + if (this.userAccessLevel < 2) { + alert('您没有删除构型的权限'); + return; + } + + const configId = parseInt(button.dataset.configId, 10); + const config = configs.find(c => c.ConfID === configId); + + if (config && confirm(`确定要删除构型"${config.ConfName}"吗?`)) { + try { + const response = await fetch(`/api/configurations/${configId}`, { + method: 'DELETE', + credentials: 'include' + }); + + if (!response.ok) { + throw new Error('删除构型失败'); + } + + // 重新加载构型列表 + this.loadConfigurations(planeName); + } catch (error) { + console.error('删除构型失败:', error); + alert('删除构型失败,请稍后重试'); + } + } + }); + }); + } + + /** + * @method showEditor + * @description 显示构型编辑器 + * @param {Object} configuration - 构型数据 + * @param {string} planeName - 机型名称 + * @param {boolean} isNew - 是否是新建构型 + */ + showEditor(configuration, planeName, isNew) { + this.currentView = 'editor'; + this.selectedConfiguration = configuration; + this.isNewConfiguration = isNew; + + // 更新UI + const pageTitle = this.shadowRoot.getElementById('pageTitle'); + const backSection = this.shadowRoot.getElementById('backSection'); + const backButtonText = this.shadowRoot.querySelector('#backButton span:last-child'); + const contentContainer = this.shadowRoot.getElementById('contentContainer'); + const editorContainer = this.shadowRoot.getElementById('editorContainer'); + const confNameInput = this.shadowRoot.getElementById('confName'); + + if (!pageTitle || !backSection || !contentContainer || !editorContainer) { + return; + } + + pageTitle.textContent = isNew ? `${planeName} - 新建构型` : `${planeName} - ${configuration.ConfName} 配置`; + backSection.style.display = 'block'; + backButtonText.textContent = '返回构型列表'; + contentContainer.style.display = 'none'; + editorContainer.style.display = 'block'; + + // 设置构型名称输入框状态 + confNameInput.readOnly = !isNew; + if (isNew) { + confNameInput.value = ''; + } else { + confNameInput.value = configuration.ConfName || ''; + } + + // 填充其他表单数据 + this.shadowRoot.getElementById('osName').value = configuration.OSName || ''; + this.shadowRoot.getElementById('osVersion').value = configuration.OSVersion || ''; + this.shadowRoot.getElementById('rtxVersion').value = configuration.RTXVersion || ''; + this.shadowRoot.getElementById('cpuAffinity').value = configuration.CPUAffinity || ''; + this.shadowRoot.getElementById('domainId').value = configuration.DomainID !== undefined ? Math.floor(configuration.DomainID) : ''; + this.shadowRoot.getElementById('modelsPath').value = configuration.ModelsPath || ''; + this.shadowRoot.getElementById('servicesPath').value = configuration.ServicesPath || ''; + this.shadowRoot.getElementById('workPath').value = configuration.WorkPath || ''; + + // 设置复选框状态 + this.shadowRoot.getElementById('consoleDebug').checked = configuration.ConsoleDebug === 1; + this.shadowRoot.getElementById('consoleInfo').checked = configuration.ConsoleInfo === 1; + this.shadowRoot.getElementById('consoleWarning').checked = configuration.ConsoleWarning === 1; + this.shadowRoot.getElementById('consoleError').checked = configuration.ConsoleError === 1; + this.shadowRoot.getElementById('logDebug').checked = configuration.LogDebug === 1; + this.shadowRoot.getElementById('logInfo').checked = configuration.LogInfo === 1; + this.shadowRoot.getElementById('logWarning').checked = configuration.LogWarning === 1; + this.shadowRoot.getElementById('logError').checked = configuration.LogError === 1; + } + + /** + * @method ensurePathEndsWithSlash + * @description 确保路径以斜杠结尾 + * @param {string} path - 要处理的路径 + * @returns {string} 处理后的路径 + */ + ensurePathEndsWithSlash(path) { + if (!path) return path; + return path.endsWith('/') ? path : path + '/'; + } + + /** + * @method saveConfiguration + * @description 保存构型配置 + */ + async saveConfiguration() { + // 检查权限 + if (this.userAccessLevel < 2) { + alert('您没有保存构型的权限'); + return; + } + + // 验证域ID + const domainId = parseInt(this.shadowRoot.getElementById('domainId').value); + if (isNaN(domainId) || domainId < 0 || domainId > 232) { + alert('域ID必须在0-232之间'); + return; + } + + // 验证构型名称 + const confName = this.shadowRoot.getElementById('confName').value.trim(); + if (!confName) { + alert('构型名称不能为空'); + return; + } + + const updatedConfig = { + ...this.selectedConfiguration, + ConfName: confName, + OSName: this.shadowRoot.getElementById('osName').value, + OSVersion: this.shadowRoot.getElementById('osVersion').value, + RTXVersion: this.shadowRoot.getElementById('rtxVersion').value, + CPUAffinity: this.shadowRoot.getElementById('cpuAffinity').value, + DomainID: domainId, + ModelsPath: this.ensurePathEndsWithSlash(this.shadowRoot.getElementById('modelsPath').value), + ServicesPath: this.ensurePathEndsWithSlash(this.shadowRoot.getElementById('servicesPath').value), + WorkPath: this.ensurePathEndsWithSlash(this.shadowRoot.getElementById('workPath').value), + ConsoleDebug: this.shadowRoot.getElementById('consoleDebug').checked ? 1 : 0, + ConsoleInfo: this.shadowRoot.getElementById('consoleInfo').checked ? 1 : 0, + ConsoleWarning: this.shadowRoot.getElementById('consoleWarning').checked ? 1 : 0, + ConsoleError: this.shadowRoot.getElementById('consoleError').checked ? 1 : 0, + LogDebug: this.shadowRoot.getElementById('logDebug').checked ? 1 : 0, + LogInfo: this.shadowRoot.getElementById('logInfo').checked ? 1 : 0, + LogWarning: this.shadowRoot.getElementById('logWarning').checked ? 1 : 0, + LogError: this.shadowRoot.getElementById('logError').checked ? 1 : 0 + }; + + try { + const url = this.isNewConfiguration ? + '/api/configurations' : + `/api/configurations/${this.selectedConfiguration.ConfID}`; + + const method = this.isNewConfiguration ? 'POST' : 'PUT'; + + const response = await fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(updatedConfig), + credentials: 'include' + }); + + if (!response.ok) { + const errorData = await response.json(); + if (errorData.error === '路径验证失败') { + const errorMessages = errorData.details.map(detail => + `${detail.path === 'workPath' ? '工作路径' : + detail.path === 'modelsPath' ? '模型路径' : '服务路径'}: ${detail.error}` + ).join('\n'); + alert(`路径验证失败:\n${errorMessages}`); + } else { + throw new Error(errorData.error || '保存配置失败'); + } + return; + } + + this.selectedConfiguration = updatedConfig; + this.showConfigurations(this.selectedPlane); + } catch (error) { + console.error('保存配置失败:', error); + alert('保存配置失败,请稍后重试'); + } + } + + /** + * @method showConfigurations + * @description 显示指定机型的构型列表 + * @param {string} planeName - 机型名称 + */ + async showConfigurations(planeName) { + this.currentView = 'configurations'; + this.selectedPlane = planeName; + + // 更新UI + this.shadowRoot.getElementById('pageTitle').textContent = `${planeName} - 构型列表`; + this.shadowRoot.getElementById('backSection').style.display = 'block'; + this.shadowRoot.querySelector('#backButton span:last-child').textContent = '返回机型列表'; + this.shadowRoot.getElementById('contentContainer').classList.add('configurations'); + this.shadowRoot.getElementById('contentContainer').style.display = 'grid'; + this.shadowRoot.getElementById('editorContainer').style.display = 'none'; + + // 检查用户权限 + await this.checkUserAccessLevel(); + + // 加载构型数据 + this.loadConfigurations(planeName); + } + + /** + * @method showPlanes + * @description 显示机型列表 + */ + showPlanes() { + this.currentView = 'planes'; + this.selectedPlane = null; + this.selectedConfiguration = null; + + // 更新UI + this.shadowRoot.getElementById('pageTitle').textContent = '机型'; + this.shadowRoot.getElementById('backSection').style.display = 'none'; + this.shadowRoot.getElementById('contentContainer').classList.remove('configurations'); + this.shadowRoot.getElementById('contentContainer').style.display = 'grid'; + this.shadowRoot.getElementById('editorContainer').style.display = 'none'; + + // 重新加载机型数据 + this.loadPlanes(); + } + + /** + * @method showError + * @description 显示错误信息 + * @param {string} message - 错误信息 + */ + showError(message) { + const container = this.shadowRoot.getElementById('contentContainer'); + container.innerHTML = `
${message}
`; + } +} + +// 注册自定义元素 +customElements.define('configuration-config', ConfigurationConfig); \ No newline at end of file diff --git a/XNSimHtml/components/content-area.js b/XNSimHtml/components/content-area.js index 1557f83..479f05a 100644 --- a/XNSimHtml/components/content-area.js +++ b/XNSimHtml/components/content-area.js @@ -99,8 +99,8 @@ class ContentArea extends HTMLElement { case 'qa': contentElement = document.createElement('qa-component'); break; - case 'run-env-config': - contentElement = document.createElement('run-env-config'); + case 'configuration-config': + contentElement = document.createElement('configuration-config'); break; case 'model-config': contentElement = document.createElement('model-config'); diff --git a/XNSimHtml/components/run-env-config.js b/XNSimHtml/components/run-env-config.js deleted file mode 100644 index 6cb4d59..0000000 --- a/XNSimHtml/components/run-env-config.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 运行环境配置组件 - * @type {module} - */ -import { initializeComponent, connectedCallback, renderComplete, disconnectedCallback } from './run-env-config/core.js'; -import { updateFileContent } from './run-env-config/ui-renderer.js'; -import { updateXmlFromVisualEditor } from './run-env-config/xml-editor.js'; -import { renderVisualEditor } from './run-env-config/ui-renderer.js'; - -class RunEnvConfig extends HTMLElement { - constructor() { - super(); - initializeComponent(this); - } - - async connectedCallback() { - await connectedCallback(this); - } - - renderComplete() { - renderComplete(this); - } - - updateFileContent() { - updateFileContent(this); - } - - renderVisualEditor(container, xmlDoc) { - renderVisualEditor(this, container, xmlDoc); - } - - updateXmlFromVisualEditor(silent = true) { - return updateXmlFromVisualEditor(this, silent); - } - - disconnectedCallback() { - disconnectedCallback(this); - } -} - -customElements.define('run-env-config', RunEnvConfig); \ No newline at end of file diff --git a/XNSimHtml/components/run-env-config/console-log.js b/XNSimHtml/components/run-env-config/console-log.js deleted file mode 100644 index 7efcc3d..0000000 --- a/XNSimHtml/components/run-env-config/console-log.js +++ /dev/null @@ -1,170 +0,0 @@ -/** - * 控制台和日志模块 - * @type {module} - */ -import { markEdited } from './utils.js'; - -/** - * 渲染控制台和日志部分 - * @param {HTMLElement} component - 组件实例 - * @param {HTMLElement} container - 容器元素 - * @param {Element} rootElement - XML根元素 - */ -export function renderConsoleAndLogSection(component, 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数据 - updateConsoleOrLogCheckbox(component, checkbox, 'ConsoleOutput', attr.name); - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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数据 - updateConsoleOrLogCheckbox(component, checkbox, 'Log', attr.name); - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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 - * @param {HTMLElement} component - 组件实例 - * @param {HTMLInputElement} checkbox - 复选框元素 - * @param {string} elementName - 元素名称 - * @param {string} attrName - 属性名 - */ -export function updateConsoleOrLogCheckbox(component, checkbox, elementName, attrName) { - const parser = new DOMParser(); - const xmlDoc = parser.parseFromString(component.xmlContent, 'text/xml'); - const element = xmlDoc.querySelector(elementName); - - if (element) { - // 设置属性值为0或1 - element.setAttribute(attrName, checkbox.checked ? '1' : '0'); - - // 更新XML内容 - const serializer = new XMLSerializer(); - component.xmlContent = serializer.serializeToString(xmlDoc); - - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - } -} \ No newline at end of file diff --git a/XNSimHtml/components/run-env-config/core.js b/XNSimHtml/components/run-env-config/core.js deleted file mode 100644 index c187aa3..0000000 --- a/XNSimHtml/components/run-env-config/core.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * 核心模块 - 运行环境配置组件 - * @type {module} - */ -import { setupEventListeners } from './event-handlers.js'; -import { loadScenarioFiles } from './file-operations.js'; -import { renderBasicUI, updateFileContent } from './ui-renderer.js'; -import { resetEditState } from './utils.js'; - -/** - * 初始化组件 - * @param {HTMLElement} component - RunEnvConfig组件实例 - */ -export function initializeComponent(component) { - // 设置初始状态 - component.currentFile = null; - component.xmlContent = ''; - component.scenarioFiles = []; - component.isEdited = false; - component.xmlDoc = null; - - // 创建Shadow DOM - component.attachShadow({ mode: 'open' }); -} - -/** - * 组件连接回调 - * @param {HTMLElement} component - RunEnvConfig组件实例 - */ -export async function connectedCallback(component) { - // 先渲染UI - renderBasicUI(component); - setupEventListeners(component); - - // 然后加载文件列表 - await loadScenarioFiles(component); - - // 检查是否有renderComplete方法,如果有则调用 - setTimeout(() => { - if (typeof component.renderComplete === 'function') { - component.renderComplete(); - } - }, 100); -} - -/** - * 在完成渲染后同时处理状态恢复 - * @param {HTMLElement} component - RunEnvConfig组件实例 - */ -export function renderComplete(component) { - // 在DOM渲染完成后更新文件选择器 - setTimeout(() => { - // 如果有当前文件,尝试选择它 - if (component.currentFile && component.xmlContent) { - const fileSelector = component.shadowRoot.getElementById('scenarioFile'); - if (fileSelector) { - // 确认文件在列表中 - const fileExists = Array.from(fileSelector.options).some(opt => opt.value === component.currentFile); - - if (fileExists) { - // 设置选中的文件 - fileSelector.value = component.currentFile; - - // 解析XML内容 - try { - const parser = new DOMParser(); - component.xmlDoc = parser.parseFromString(component.xmlContent, 'text/xml'); - - // 更新内容显示 - const contentElement = component.shadowRoot.getElementById('fileContent'); - if (contentElement) { - contentElement.innerHTML = ''; - component.renderVisualEditor(contentElement, component.xmlDoc); - } - - // 如果有编辑状态,恢复它 - if (component.isEdited) { - const saveButton = component.shadowRoot.getElementById('saveConfig'); - if (saveButton) { - saveButton.classList.add('modified'); - saveButton.title = '文件已修改,请保存'; - } - } - } catch (error) { - console.error('恢复XML内容失败:', error); - } - } else { - // 文件不存在,清除状态 - component.currentFile = null; - component.xmlContent = ''; - component.isEdited = false; - } - } - } - }, 50); // 增加延迟确保DOM已经完全加载 -} - -/** - * 组件销毁回调 - * @param {HTMLElement} component - RunEnvConfig组件实例 - */ -export function disconnectedCallback(component) { - // 移除可能的事件监听器 - const fileSelector = component.shadowRoot.getElementById('scenarioFile'); - if (fileSelector) { - fileSelector.removeEventListener('change', component.handleFileSelectorChange); - } -} \ No newline at end of file diff --git a/XNSimHtml/components/run-env-config/environment-section.js b/XNSimHtml/components/run-env-config/environment-section.js deleted file mode 100644 index 1985883..0000000 --- a/XNSimHtml/components/run-env-config/environment-section.js +++ /dev/null @@ -1,427 +0,0 @@ -/** - * 环境配置模块 - * @type {module} - */ -import { createModal, markEdited } from './utils.js'; - -/** - * 渲染环境部分 - * @param {HTMLElement} component - 组件实例 - * @param {HTMLElement} container - 容器元素 - * @param {Element} rootElement - XML根元素 - */ -export function renderEnvironmentSection(component, 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => showAddPropertyDialog(component, 'Environment')); - section.appendChild(addButton); - - container.appendChild(section); -} - -/** - * 添加新属性对话框 - * @param {HTMLElement} component - 组件实例 - * @param {string} elementName - 元素名称 - */ -export function showAddPropertyDialog(component, 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); - - // 显示对话框 - createModal(component.shadowRoot, '添加新属性', formContent, () => { - const propertyName = nameInput.value.trim(); - const propertyValue = valueInput.value; - - if (propertyName) { - addNewProperty(component, elementName, propertyName, propertyValue); - } - }); -} - -/** - * 添加新属性 - * @param {HTMLElement} component - 组件实例 - * @param {string} elementName - 元素名称 - * @param {string} propertyName - 属性名 - * @param {string} propertyValue - 属性值 - */ -export function addNewProperty(component, elementName, propertyName, propertyValue) { - // 更新UI - const section = Array.from(component.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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - propertyItem.appendChild(label); - propertyItem.appendChild(input); - propertyGroup.appendChild(propertyItem); - - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - - // 静默更新XML - if (component.updateXmlFromVisualEditor) { - component.updateXmlFromVisualEditor(true); - } - } - } -} - -/** - * 获取CPU亲和性设置 - * @param {Document} xmlDoc - XML文档 - * @returns {number[]} 可用的CPU核心列表 - */ -export function getCPUAffinityOptions(xmlDoc) { - // 从环境配置中获取CPUAffinity的值 - const envElement = 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; -} \ No newline at end of file diff --git a/XNSimHtml/components/run-env-config/event-handlers.js b/XNSimHtml/components/run-env-config/event-handlers.js deleted file mode 100644 index 1e85bce..0000000 --- a/XNSimHtml/components/run-env-config/event-handlers.js +++ /dev/null @@ -1,271 +0,0 @@ -/** - * 事件处理模块 - * @type {module} - */ -import { loadScenarioFiles, loadFileContent, saveFileContent, createNewConfig } from './file-operations.js'; -import { createModal, resetEditState, checkSaveNeeded } from './utils.js'; - -/** - * 初始化事件监听器 - * @param {HTMLElement} component - 组件实例 - */ -export function setupEventListeners(component) { - // 新建按钮 - const newButton = component.shadowRoot.getElementById('newConfig'); - newButton.addEventListener('click', async () => { - // 检查是否需要保存当前文件 - if (await checkSaveNeeded(component)) { - showNewConfigDialog(component); - } - }); - - // 保存按钮 - const saveButton = component.shadowRoot.getElementById('saveConfig'); - saveButton.addEventListener('click', () => { - if (component.currentFile && component.xmlContent) { - saveFileContent(component, component.currentFile, component.xmlContent); - } else { - // 如果没有选择文件,则提示另存为 - if (component.xmlContent) { - showSaveAsDialog(component); - } - } - }); - - // 另存为按钮 - const saveAsButton = component.shadowRoot.getElementById('saveAsConfig'); - saveAsButton.addEventListener('click', () => { - showSaveAsDialog(component); - }); - - // 刷新按钮 - const refreshButton = component.shadowRoot.getElementById('refreshButton'); - refreshButton.addEventListener('click', async () => { - // 检查是否需要保存当前文件 - if (await checkSaveNeeded(component)) { - // 添加刷新中的样式 - refreshButton.classList.add('refreshing'); - - try { - // 重新加载文件列表 - await loadScenarioFiles(component); - - // 清除编辑状态和内容 - resetEditState(component.shadowRoot); - - // 清空内容区域 - component.xmlContent = ''; - component.xmlDoc = null; - component.currentFile = null; - - const contentElement = component.shadowRoot.getElementById('fileContent'); - if (contentElement) { - contentElement.innerHTML = '
请选择一个运行环境配置文件查看内容
'; - } - - // 清空文件选择 - const fileSelector = component.shadowRoot.getElementById('scenarioFile'); - if (fileSelector) { - fileSelector.value = ''; - } - } catch (error) { - console.error('刷新文件列表失败:', error); - alert('刷新文件列表失败: ' + error.message); - } finally { - // 移除刷新中的样式 - setTimeout(() => { - refreshButton.classList.remove('refreshing'); - }, 500); - } - } - // 如果用户点击取消,什么也不做,保留当前内容 - }); - - // 文件选择下拉框 - const fileSelector = component.shadowRoot.getElementById('scenarioFile'); - fileSelector.addEventListener('change', async (e) => { - // 检查是否需要保存当前文件 - if (await checkSaveNeeded(component)) { - const selectedFile = e.target.value; - if (selectedFile) { - loadFileContent(component, selectedFile); - } else { - component.currentFile = null; - component.xmlContent = ''; - const contentElement = component.shadowRoot.getElementById('fileContent'); - contentElement.innerHTML = '
请选择一个运行环境配置文件查看内容
'; - component.isEdited = resetEditState(component.shadowRoot); - } - } else { - // 恢复之前的选择 - fileSelector.value = component.currentFile || ''; - } - }); - - // 添加fileSelectorChange处理器供外部调用 - component.handleFileSelectorChange = async (e) => { - // 检查是否需要保存当前文件 - if (await checkSaveNeeded(component)) { - const selectedFile = e.target.value; - if (selectedFile) { - loadFileContent(component, selectedFile); - } - } else { - // 恢复之前的选择 - fileSelector.value = component.currentFile || ''; - } - }; -} - -/** - * 显示新建配置对话框 - * @param {HTMLElement} component - 组件实例 - */ -export function showNewConfigDialog(component) { - // 创建表单容器 - 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'; - input.placeholder = '请输入文件名'; - 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'; - - // 添加提示信息 - const helpText = document.createElement('div'); - helpText.textContent = '如未指定后缀,将自动添加.sce后缀'; - helpText.style.fontSize = '12px'; - helpText.style.color = '#718096'; - helpText.style.marginTop = '4px'; - - // 组装表单组 - formGroup.appendChild(label); - formGroup.appendChild(input); - formGroup.appendChild(helpText); - formContent.appendChild(formGroup); - - // 显示对话框 - const modal = createModal(component.shadowRoot, '新建配置文件', formContent, () => { - let fileName = input.value.trim(); - - if (!fileName) { - alert('文件名不能为空'); - return; - } - - // 自动添加后缀 - const ext = fileName.toLowerCase(); - if (!ext.endsWith('.xml') && !ext.endsWith('.sce')) { - fileName += '.sce'; - } - - // 创建新文件 - createNewConfig(component, fileName); - - // 关闭模态框 - modal.modal.remove(); - }); -} - -/** - * 显示另存为对话框 - * @param {HTMLElement} component - 组件实例 - */ -export function showSaveAsDialog(component) { - if (!component.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'; - - // 获取原文件名(不含路径和后缀) - let defaultFileName = 'NewConfig'; - if (component.currentFile) { - const fileName = component.currentFile.split('/').pop(); - defaultFileName = fileName.substring(0, fileName.lastIndexOf('.')) || fileName; - } - - // 创建输入框 - const input = document.createElement('input'); - input.id = 'fileName'; - input.className = 'form-control'; - input.type = 'text'; - input.value = defaultFileName; - input.placeholder = '请输入文件名'; - 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'; - - // 添加提示信息 - const helpText = document.createElement('div'); - helpText.textContent = '如未指定后缀,将自动添加.sce后缀'; - helpText.style.fontSize = '12px'; - helpText.style.color = '#718096'; - helpText.style.marginTop = '4px'; - - // 组装表单组 - formGroup.appendChild(label); - formGroup.appendChild(input); - formGroup.appendChild(helpText); - formContent.appendChild(formGroup); - - // 显示对话框 - createModal(component.shadowRoot, '另存为', formContent, () => { - let fileName = input.value.trim(); - - if (!fileName) { - alert('文件名不能为空'); - return; - } - - // 自动添加后缀 - const ext = fileName.toLowerCase(); - if (!ext.endsWith('.xml') && !ext.endsWith('.sce')) { - fileName += '.sce'; - } - - // 直接使用文件名,saveFileContent 函数会处理成绝对路径 - saveFileContent(component, fileName, component.xmlContent); - }); -} \ No newline at end of file diff --git a/XNSimHtml/components/run-env-config/file-operations.js b/XNSimHtml/components/run-env-config/file-operations.js deleted file mode 100644 index a8b2936..0000000 --- a/XNSimHtml/components/run-env-config/file-operations.js +++ /dev/null @@ -1,281 +0,0 @@ -/** - * 文件操作模块 - * @type {module} - */ -import { formatXml, markEdited, resetEditState } from './utils.js'; - -// 获取Scenario目录路径前缀 -let scenarioDirPrefix = null; - -/** - * 获取Scenario目录路径 - * @returns {string} Scenario目录的绝对路径 - */ -async function getScenarioDir() { - if (scenarioDirPrefix) return scenarioDirPrefix; - - try { - // 从文件列表API获取Scenario目录路径 - const response = await fetch('/api/scenario-files'); - if (response.ok) { - const files = await response.json(); - if (files && files.length > 0) { - // 提取第一个文件的目录作为Scenario目录 - const filePath = files[0].path; - scenarioDirPrefix = filePath.substring(0, filePath.lastIndexOf('/')); - return scenarioDirPrefix; - } - } - return ''; // 如果无法获取,返回空字符串 - } catch (error) { - console.error('获取Scenario目录失败:', error); - return ''; - } -} - -/** - * 加载场景文件列表 - * @param {HTMLElement} component - 组件实例 - * @returns {Promise} 是否成功加载 - */ -export async function loadScenarioFiles(component) { - // 设置按钮为刷新中状态 - const refreshButton = component.shadowRoot.getElementById('refreshButton'); - if (refreshButton) { - refreshButton.classList.add('refreshing'); - } - - let retryCount = 0; - const maxRetries = 3; // 最多重试3次 - - const tryLoadFiles = async () => { - try { - const response = await fetch('/api/scenario-files'); - - if (!response.ok) { - throw new Error(`服务器返回错误: ${response.status} ${response.statusText}`); - } - - const files = await response.json(); - component.scenarioFiles = files; - - // 更新文件选择器 - updateFileSelector(component); - - return true; - } catch (error) { - if (retryCount < maxRetries) { - retryCount++; - // 指数级退避重试 - const retryDelay = Math.pow(2, retryCount) * 500; - await new Promise(resolve => setTimeout(resolve, retryDelay)); - return tryLoadFiles(); - } - - alert(`加载文件列表失败: ${error.message}`); - return false; - } finally { - // 无论成功失败,移除刷新中状态 - if (refreshButton) { - refreshButton.classList.remove('refreshing'); - } - } - }; - - const success = await tryLoadFiles(); - - // 检查是否是用户通过刷新按钮手动触发的刷新 - const isManualRefresh = document.activeElement === refreshButton; - - if (success && isManualRefresh) { - // 只有在用户明确点击刷新按钮时才清空当前文件 - component.currentFile = null; - component.xmlContent = ''; - component.updateFileContent(); - component.isEdited = resetEditState(component.shadowRoot); - - // 更新文件选择器,确保没有文件被选中 - const fileSelector = component.shadowRoot.getElementById('scenarioFile'); - if (fileSelector) { - fileSelector.value = ''; - } - } - - return success; -} - -/** - * 更新文件选择器 - * @param {HTMLElement} component - 组件实例 - */ -export function updateFileSelector(component) { - const fileSelector = component.shadowRoot.getElementById('scenarioFile'); - if (fileSelector) { - // 清除现有选项,保留第一个 - while (fileSelector.options.length > 1) { - fileSelector.remove(1); - } - - // 添加文件选项 - component.scenarioFiles.forEach(file => { - const option = document.createElement('option'); - option.value = file.path; - option.textContent = file.name; - fileSelector.appendChild(option); - }); - - // 验证文件选择器选项 - const options = Array.from(fileSelector.options); - options.forEach((opt, index) => { - if (index === 0) return; // 跳过第一个空选项 - }); - } else { - console.log('未找到文件选择器元素'); - } -} - -/** - * 加载文件内容 - * @param {HTMLElement} component - 组件实例 - * @param {string} filePath - 文件路径 - * @returns {Promise} - */ -export async function loadFileContent(component, filePath) { - try { - const response = await fetch(`/api/file-content?path=${encodeURIComponent(filePath)}`); - if (!response.ok) { - throw new Error(`加载文件内容失败: ${response.status} ${response.statusText}`); - } - - component.xmlContent = await response.text(); - component.currentFile = filePath; - - // 检查内容是否为空 - if (!component.xmlContent.trim()) { - // 如果是.sce文件且内容为空,则报文件内容为空的错误 - if (filePath.toLowerCase().endsWith('.sce')) { - throw new Error('文件内容为空'); - } - } - - component.updateFileContent(); - - // 重置编辑状态,因为刚刚加载了新文件 - component.isEdited = resetEditState(component.shadowRoot); - } catch (error) { - console.error('加载文件内容失败:', error); - component.xmlContent = ''; - component.updateFileContent(); - } -} - -/** - * 保存文件内容 - * @param {HTMLElement} component - 组件实例 - * @param {string} filePath - 文件路径 - * @param {string} content - 文件内容 - * @returns {Promise} 是否成功保存 - */ -export async function saveFileContent(component, filePath, content) { - try { - // 更新XML内容 - if (component.autoSaveToXml) { - component.updateXmlFromVisualEditor(); - } - - // 格式化XML内容 - const formattedContent = formatXml(component.xmlContent); - - // 确保使用绝对路径 - let absolutePath = filePath; - if (!filePath.startsWith('/')) { - const scenarioDir = await getScenarioDir(); - // 如果filePath是相对路径但带有./,则移除它 - const cleanPath = filePath.startsWith('./') ? filePath.substring(2) : filePath; - absolutePath = `${scenarioDir}/${cleanPath}`; - } - - const response = await fetch('/api/save-file', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - path: absolutePath, - content: formattedContent - }) - }); - - if (!response.ok) { - throw new Error(`保存文件失败: ${response.status} ${response.statusText}`); - } - - // 重置编辑状态 - component.isEdited = resetEditState(component.shadowRoot); - - // 更新当前文件 - component.currentFile = absolutePath; - - // 刷新文件列表 - await loadScenarioFiles(component); - - // 选中当前文件 - const fileSelector = component.shadowRoot.getElementById('scenarioFile'); - if (fileSelector && component.currentFile) { - fileSelector.value = component.currentFile; - } - - alert('文件保存成功'); - return true; - } catch (error) { - console.error('保存文件失败:', error); - alert(`保存文件失败: ${error.message}`); - return false; - } -} - -/** - * 创建新配置 - * @param {HTMLElement} component - 组件实例 - * @param {string} fileName - 文件名 - * @returns {Promise} 是否成功创建 - */ -export async function createNewConfig(component, fileName) { - try { - // 使用服务器API创建新文件 - const response = await fetch('/api/create-config-file', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - fileName: fileName - }) - }); - - if (!response.ok) { - const errorData = await response.json(); - throw new Error(`创建文件失败: ${errorData.error || response.statusText}`); - } - - const data = await response.json(); - - // 更新文件列表 - await loadScenarioFiles(component); - - // 加载新创建的文件内容 - await loadFileContent(component, data.path); - - // 选中新创建的文件 - const fileSelector = component.shadowRoot.getElementById('scenarioFile'); - if (fileSelector) { - fileSelector.value = data.path; - } - - return true; - } catch (error) { - console.error('创建新配置文件失败:', error); - alert(`创建新配置文件失败: ${error.message}`); - return false; - } -} \ No newline at end of file diff --git a/XNSimHtml/components/run-env-config/model-groups.js b/XNSimHtml/components/run-env-config/model-groups.js deleted file mode 100644 index b374019..0000000 --- a/XNSimHtml/components/run-env-config/model-groups.js +++ /dev/null @@ -1,846 +0,0 @@ -/** - * 模型组模块 - */ -import { createModal, markEdited } from './utils.js'; -import { getCPUAffinityOptions } from './environment-section.js'; - -/** - * 渲染模型组部分 - * @param {HTMLElement} component - 组件实例 - * @param {HTMLElement} container - 容器元素 - * @param {Element} rootElement - XML根元素 - */ -export function renderModelGroupsSection(component, 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', () => deleteModelGroup(component, 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 = getCPUAffinityOptions(component.xmlDoc); - - 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'; - } - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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'; - } - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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) => { - renderModelItem(component, modelsContainer, model, modelIndex, index); - }); - } - - groupItem.appendChild(modelsContainer); - - // 添加模型按钮 - const addModelButton = document.createElement('button'); - addModelButton.className = 'add-button'; - addModelButton.style.marginTop = '12px'; - addModelButton.textContent = '添加模型'; - addModelButton.title = '添加新模型到当前模型组'; - addModelButton.addEventListener('click', () => showAddModelDialog(component, 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', () => showAddModelGroupDialog(component)); - section.appendChild(addButton); - - container.appendChild(section); -} - -/** - * 渲染模型项 - * @param {HTMLElement} component - 组件实例 - * @param {HTMLElement} container - 容器元素 - * @param {Element} model - 模型XML元素 - * @param {number} modelIndex - 模型索引 - * @param {number} groupIndex - 模型组索引 - */ -function renderModelItem(component, container, model, modelIndex, groupIndex) { - 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', () => deleteModel(component, groupIndex, 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[${groupIndex}]/Model[${modelIndex}]@${attr.name}`; - nameInput.title = '模型名称'; - nameInput.addEventListener('change', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - 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[${groupIndex}]/Model[${modelIndex}]@ClassName`; - classInput.title = '模型C++类名'; - classInput.addEventListener('change', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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[${groupIndex}]/Model[${modelIndex}]@Name`; - nameInput.title = '模型名称'; - nameInput.addEventListener('change', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - 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[${groupIndex}]/Model[${modelIndex}]@ClassName`; - classInput.title = '模型C++类名'; - classInput.addEventListener('change', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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[${groupIndex}]/Model[${modelIndex}]@${attr.name}`; - input.addEventListener('change', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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); - } - }); - - container.appendChild(modelItem); -} - -/** - * 显示添加模型组对话框 - * @param {HTMLElement} component - 组件实例 - */ -export function showAddModelGroupDialog(component) { - // 创建表单内容 - const formContent = document.createElement('div'); - formContent.className = 'modal-form'; - - // 获取可用的CPU核心选项 - const availableCores = getCPUAffinityOptions(component.xmlDoc); - - // 常用属性输入字段 - 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); - - // 显示对话框 - createModal(component.shadowRoot, '添加新模型组', formContent, () => { - const props = { - Name: nameInput.value, - FreqGroup: freqGroupInput.value, - Priority: priorityInput.value, - CPUAff: cpuAffSelect.value - }; - - addModelGroup(component, props); - }); -} - -/** - * 显示添加模型对话框 - * @param {HTMLElement} component - 组件实例 - * @param {number} groupIndex - 模型组索引 - */ -export function showAddModelDialog(component, 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); - - // 显示对话框 - createModal(component.shadowRoot, '添加新模型', formContent, () => { - const props = {}; - formContent.querySelectorAll('.form-input').forEach(input => { - props[input.name] = input.value; - }); - - addModel(component, groupIndex, props); - }); -} - -/** - * 添加模型组 - * @param {HTMLElement} component - 组件实例 - * @param {Object} properties - 属性对象 - */ -export function addModelGroup(component, properties = {}) { - const parser = new DOMParser(); - const xmlDoc = parser.parseFromString(component.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); - } - - // 查找或创建ModelGroupList容器 - let modelGroupList = xmlDoc.querySelector('ModelGroupList'); - if (!modelGroupList) { - modelGroupList = xmlDoc.createElement('ModelGroupList'); - xmlDoc.documentElement.appendChild(modelGroupList); - } - - // 添加到ModelGroupList - modelGroupList.appendChild(newGroup); - - // 重新生成XML内容 - const serializer = new XMLSerializer(); - component.xmlContent = serializer.serializeToString(xmlDoc); - - // 更新编辑器 - component.updateFileContent(); - component.isEdited = markEdited(component.shadowRoot, component.isEdited); -} - -/** - * 删除模型组 - * @param {HTMLElement} component - 组件实例 - * @param {number} groupIndex - 模型组索引 - */ -export function deleteModelGroup(component, groupIndex) { - if (confirm('确定要删除此模型组吗?此操作不可撤销。')) { - const parser = new DOMParser(); - const xmlDoc = parser.parseFromString(component.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(); - component.xmlContent = serializer.serializeToString(xmlDoc); - - // 更新编辑器 - component.updateFileContent(); - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - } - } -} - -/** - * 添加模型 - * @param {HTMLElement} component - 组件实例 - * @param {number} groupIndex - 模型组索引 - * @param {Object} properties - 属性对象 - */ -export function addModel(component, groupIndex, properties = {}) { - const parser = new DOMParser(); - const xmlDoc = parser.parseFromString(component.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)) { - newModel.setAttribute(name, value); - } - - // 添加到模型组 - group.appendChild(newModel); - - // 重新生成XML内容 - const serializer = new XMLSerializer(); - component.xmlContent = serializer.serializeToString(xmlDoc); - - // 更新编辑器 - component.updateFileContent(); - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - } -} - -/** - * 删除模型 - * @param {HTMLElement} component - 组件实例 - * @param {number} groupIndex - 模型组索引 - * @param {number} modelIndex - 模型索引 - */ -export function deleteModel(component, groupIndex, modelIndex) { - if (confirm('确定要删除此模型吗?此操作不可撤销。')) { - const parser = new DOMParser(); - const xmlDoc = parser.parseFromString(component.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(); - component.xmlContent = serializer.serializeToString(xmlDoc); - - // 更新编辑器 - component.updateFileContent(); - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - } - } - } -} \ No newline at end of file diff --git a/XNSimHtml/components/run-env-config/services.js b/XNSimHtml/components/run-env-config/services.js deleted file mode 100644 index a840369..0000000 --- a/XNSimHtml/components/run-env-config/services.js +++ /dev/null @@ -1,448 +0,0 @@ -/** - * 服务模块 - * @type {module} - */ -import { createModal, markEdited } from './utils.js'; - -/** - * 渲染服务部分 - * @param {HTMLElement} component - 组件实例 - * @param {HTMLElement} container - 容器元素 - * @param {Element} rootElement - XML根元素 - */ -export function renderServicesSection(component, 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', () => deleteService(component, 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - 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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - classGroup.appendChild(classLabel); - classGroup.appendChild(classInput); - - // 将名称组和类名组添加到行容器 - rowContainer.appendChild(nameGroup); - rowContainer.appendChild(classGroup); - - serviceItem.appendChild(rowContainer); - - // 将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', () => { - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - }); - - 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); - } - }); - - section.appendChild(serviceItem); - }); - } - - // 添加新服务按钮 - const addButton = document.createElement('button'); - addButton.className = 'add-button'; - addButton.style.marginTop = '16px'; - addButton.textContent = '添加服务'; - addButton.title = '添加新的服务'; - addButton.addEventListener('click', () => showAddServiceDialog(component)); - section.appendChild(addButton); - } - - container.appendChild(section); -} - -/** - * 显示添加服务对话框 - * @param {HTMLElement} component - 组件实例 - */ -export function showAddServiceDialog(component) { - // 创建表单内容 - 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); - - // 显示对话框 - createModal(component.shadowRoot, '添加新服务', formContent, () => { - const props = {}; - formContent.querySelectorAll('.form-input').forEach(input => { - props[input.name] = input.value; - }); - - addService(component, props); - }); -} - -/** - * 添加服务 - * @param {HTMLElement} component - 组件实例 - * @param {Object} properties - 属性对象 - */ -export function addService(component, properties = {}) { - const parser = new DOMParser(); - const xmlDoc = parser.parseFromString(component.xmlContent, 'text/xml'); - - // 查找或创建ServicesList容器 - let servicesList = xmlDoc.querySelector('ServicesList'); - if (!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)) { - newService.setAttribute(name, value); - } - - // 添加到服务列表 - servicesList.appendChild(newService); - - // 重新生成XML内容 - const serializer = new XMLSerializer(); - component.xmlContent = serializer.serializeToString(xmlDoc); - - // 更新编辑器 - component.updateFileContent(); - component.isEdited = markEdited(component.shadowRoot, component.isEdited); -} - -/** - * 删除服务 - * @param {HTMLElement} component - 组件实例 - * @param {number} serviceIndex - 服务索引 - */ -export function deleteService(component, serviceIndex) { - if (confirm('确定要删除此服务吗?此操作不可撤销。')) { - const parser = new DOMParser(); - const xmlDoc = parser.parseFromString(component.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(); - component.xmlContent = serializer.serializeToString(xmlDoc); - - // 更新编辑器 - component.updateFileContent(); - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - } - } - } -} \ No newline at end of file diff --git a/XNSimHtml/components/run-env-config/ui-renderer.js b/XNSimHtml/components/run-env-config/ui-renderer.js deleted file mode 100644 index 1ce6011..0000000 --- a/XNSimHtml/components/run-env-config/ui-renderer.js +++ /dev/null @@ -1,481 +0,0 @@ -/** - * UI渲染模块 - * @type {module} - */ -import { escapeHtml } from './utils.js'; -import { renderEnvironmentSection } from './environment-section.js'; -import { renderModelGroupsSection } from './model-groups.js'; -import { renderServicesSection } from './services.js'; -import { renderConsoleAndLogSection } from './console-log.js'; - -/** - * 渲染基本UI - * @param {HTMLElement} component - 组件实例 - */ -export function renderBasicUI(component) { - component.shadowRoot.innerHTML = ` - -
-
-
-
运行环境配置文件:
- - -
-
- - - -
-
-
-
-
请选择一个运行环境配置文件查看内容
-
-
-
- `; -} - -/** - * 更新文件内容显示 - * @param {HTMLElement} component - 组件实例 - */ -export function updateFileContent(component) { - const contentElement = component.shadowRoot.getElementById('fileContent'); - - if (!contentElement) { - return; - } - - if (!component.xmlContent) { - contentElement.innerHTML = '
请选择一个运行环境配置文件查看内容
'; - return; - } - - try { - // 尝试解析XML - const parser = new DOMParser(); - component.xmlDoc = parser.parseFromString(component.xmlContent, 'text/xml'); - - // 检查解析错误 - const parseError = component.xmlDoc.querySelector('parsererror'); - if (parseError) { - contentElement.innerHTML = `
${escapeHtml(component.xmlContent)}
`; - return; - } - - // 创建可视化编辑器 - contentElement.innerHTML = ''; - renderVisualEditor(component, contentElement, component.xmlDoc); - } catch (error) { - contentElement.innerHTML = `
${escapeHtml(component.xmlContent)}
`; - } -} - -/** - * 渲染可视化编辑界面 - * @param {HTMLElement} component - 组件实例 - * @param {HTMLElement} container - 容器元素 - * @param {Document} xmlDoc - XML文档 - */ -export function renderVisualEditor(component, container, xmlDoc) { - // 清空容器 - container.innerHTML = ''; - - // 创建编辑器容器 - 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部分 - renderEnvironmentSection(component, visualEditor, rootElement); - - // 渲染Console输出和日志部分 - renderConsoleAndLogSection(component, visualEditor, rootElement); - - // 渲染ModelGroup部分 - renderModelGroupsSection(component, visualEditor, rootElement); - - // 渲染Services部分 - renderServicesSection(component, visualEditor, rootElement); - } else { - // 不是Scenario根元素,显示错误信息 - visualEditor.innerHTML = `
- 无法编辑:XML文档的根元素不是Scenario。 - 请确保XML文档的根元素是Scenario。 -
`; - } - - container.appendChild(visualEditor); - - // 自动保存配置到XML - autoSaveToXml(component); -} - -/** - * 自动保存表单内容到XML - * @param {HTMLElement} component - 组件实例 - */ -export function autoSaveToXml(component) { - // 为所有输入框添加change事件 - const inputs = component.shadowRoot.querySelectorAll('.property-input'); - inputs.forEach(input => { - input.addEventListener('change', () => { - if (component.updateXmlFromVisualEditor) { - component.updateXmlFromVisualEditor(); - } - }); - }); -} \ No newline at end of file diff --git a/XNSimHtml/components/run-env-config/utils.js b/XNSimHtml/components/run-env-config/utils.js deleted file mode 100644 index d89324f..0000000 --- a/XNSimHtml/components/run-env-config/utils.js +++ /dev/null @@ -1,445 +0,0 @@ -/** - * 工具函数模块 - * @type {module} - */ -import { saveFileContent } from './file-operations.js'; - -/** - * 格式化XML字符串 - * @param {string} xml - XML字符串 - * @returns {string} 格式化后的XML字符串 - */ -export function 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字符 - * @param {string} unsafe - 不安全的HTML字符串 - * @returns {string} 转义后的HTML字符串 - */ -export function escapeHtml(unsafe) { - return unsafe - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -} - -/** - * 创建模态对话框 - * @param {HTMLElement} shadowRoot - Shadow DOM根元素 - * @param {string} title - 对话框标题 - * @param {HTMLElement|string} content - 对话框内容 - * @param {Function} confirmCallback - 确认按钮回调函数 - * @returns {Object} 对话框引用 - */ -export function createModal(shadowRoot, 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 - shadowRoot.appendChild(modalOverlay); - - // 返回引用 - return { - modal: modalOverlay, - confirmButton: confirmButton - }; -} - -/** - * 标记编辑状态 - * @param {HTMLElement} shadowRoot - Shadow DOM根元素 - * @param {boolean} isEdited - 是否已编辑 - * @returns {boolean} 新的编辑状态 - */ -export function markEdited(shadowRoot, isEdited) { - if (!isEdited) { - // 更新保存按钮样式 - const saveButton = shadowRoot.getElementById('saveConfig'); - if (saveButton) { - saveButton.classList.add('modified'); - saveButton.title = '文件已修改,请保存'; - } - return true; - } - return isEdited; -} - -/** - * 重置编辑状态 - * @param {HTMLElement} shadowRoot - Shadow DOM根元素 - * @returns {boolean} 重置后的编辑状态(false) - */ -export function resetEditState(shadowRoot) { - // 更新保存按钮样式 - const saveButton = shadowRoot.getElementById('saveConfig'); - if (saveButton) { - saveButton.classList.remove('modified'); - saveButton.title = ''; - } - return false; -} - -/** - * 检查是否需要保存当前更改 - * @param {HTMLElement} component - 组件实例 - * @returns {Promise} 如果可以继续操作返回true,否则返回false - */ -export async function checkSaveNeeded(component) { - // 检查是否有未保存的修改 - const saveButton = component.shadowRoot.getElementById('saveConfig'); - const hasUnsavedChanges = saveButton && saveButton.classList.contains('modified'); - - if (hasUnsavedChanges) { - // 使用自定义对话框替代简单的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 { - // 保存文件 - if (component.currentFile && component.xmlContent) { - await saveFileContent(component, component.currentFile, component.xmlContent); - resetEditState(component.shadowRoot); - return true; // 继续执行后续操作 - } else { - return false; // 无法保存,不继续执行 - } - } catch (error) { - console.error('保存出错:', error); - return false; // 保存失败,不继续执行 - } - } else if (dialogResult === 'dont-save') { - // 不保存,但继续执行后续操作 - return true; - } else { - // 用户取消,不执行后续操作 - return false; - } - } - - // 没有编辑状态,直接返回true允许继续操作 - return true; -} \ No newline at end of file diff --git a/XNSimHtml/components/run-env-config/xml-editor.js b/XNSimHtml/components/run-env-config/xml-editor.js deleted file mode 100644 index f991fbf..0000000 --- a/XNSimHtml/components/run-env-config/xml-editor.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * XML编辑模块 - * @type {module} - */ -import { markEdited } from './utils.js'; - -/** - * 更新XML内容从可视化编辑器 - * @param {HTMLElement} component - 组件实例 - * @param {boolean} silent - 是否静默执行(不显示提示) - * @returns {string} 更新后的XML内容 - */ -export function updateXmlFromVisualEditor(component, silent = true) { - if (!component.xmlDoc) { - return component.xmlContent; - } - - try { - // 获取所有输入元素 - const inputs = component.shadowRoot.querySelectorAll('[data-path]'); - - // 遍历输入元素,更新XML - inputs.forEach(input => { - const path = input.dataset.path; - const value = input.type === 'checkbox' ? (input.checked ? '1' : '0') : input.value; - - if (!path) return; - - // 解析路径 - if (path.includes('@')) { - // 属性路径 - const [elementPath, attrName] = path.split('@'); - - if (elementPath) { - // 包含元素路径和属性名 - let element; - - if (elementPath.includes('/')) { - // 使用/分隔的路径 - const pathParts = elementPath.split('/'); - let currentElement = component.xmlDoc.documentElement; - - for (let i = 0; i < pathParts.length; i++) { - const part = pathParts[i]; - - if (part === '') continue; - - if (part.includes('[') && part.includes(']')) { - // 带索引的元素 - const tagName = part.substring(0, part.indexOf('[')); - const index = parseInt(part.substring(part.indexOf('[') + 1, part.indexOf(']'))); - - const elements = currentElement.getElementsByTagName(tagName); - if (elements.length > index) { - currentElement = elements[index]; - } else { - console.warn(`无法找到元素: ${part}`); - return; - } - } else { - // 不带索引的元素 - const elements = currentElement.getElementsByTagName(part); - if (elements.length > 0) { - currentElement = elements[0]; - } else { - console.warn(`无法找到元素: ${part}`); - return; - } - } - } - - element = currentElement; - } else if (elementPath.includes('[') && elementPath.includes(']')) { - // 带索引的元素 e.g. ModelGroup[0] - const tagName = elementPath.substring(0, elementPath.indexOf('[')); - const index = parseInt(elementPath.substring(elementPath.indexOf('[') + 1, elementPath.indexOf(']'))); - - const elements = component.xmlDoc.getElementsByTagName(tagName); - if (elements.length > index) { - element = elements[index]; - } else { - console.warn(`无法找到元素: ${elementPath}`); - return; - } - } else { - // 简单元素 e.g. Environment - element = component.xmlDoc.getElementsByTagName(elementPath)[0]; - } - - if (element) { - if (input.dataset.isNew === 'true') { - // 新属性 - element.setAttribute(attrName, value); - } else { - // 已有属性,检查是否修改 - const oldValue = element.getAttribute(attrName); - if (oldValue !== value) { - element.setAttribute(attrName, value); - } - } - } - } else { - // 直接在根元素上的属性 - component.xmlDoc.documentElement.setAttribute(attrName, value); - } - } else { - // 元素内容路径 (暂未使用) - console.warn('不支持的路径格式:', path); - } - }); - - // 生成更新后的XML - const serializer = new XMLSerializer(); - const xmlString = serializer.serializeToString(component.xmlDoc); - - // 更新组件的xmlContent - component.xmlContent = xmlString; - - // 标记为已编辑 - component.isEdited = markEdited(component.shadowRoot, component.isEdited); - - return xmlString; - } catch (error) { - if (!silent) { - console.error('更新XML失败:', error); - alert(`更新XML失败: ${error.message}`); - } - return component.xmlContent; - } -} \ No newline at end of file diff --git a/XNSimHtml/components/sub-toolbar.js b/XNSimHtml/components/sub-toolbar.js index bbe6be9..e478cf6 100644 --- a/XNSimHtml/components/sub-toolbar.js +++ b/XNSimHtml/components/sub-toolbar.js @@ -221,8 +221,8 @@ class SubToolbar extends HTMLElement {