运行环境配置改为构型配置
This commit is contained in:
parent
b5d652a77e
commit
50795d16ac
Binary file not shown.
1046
XNSimHtml/components/configuration-config.js
Normal file
1046
XNSimHtml/components/configuration-config.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -99,8 +99,8 @@ class ContentArea extends HTMLElement {
|
|||||||
case 'qa':
|
case 'qa':
|
||||||
contentElement = document.createElement('qa-component');
|
contentElement = document.createElement('qa-component');
|
||||||
break;
|
break;
|
||||||
case 'run-env-config':
|
case 'configuration-config':
|
||||||
contentElement = document.createElement('run-env-config');
|
contentElement = document.createElement('configuration-config');
|
||||||
break;
|
break;
|
||||||
case 'model-config':
|
case 'model-config':
|
||||||
contentElement = document.createElement('model-config');
|
contentElement = document.createElement('model-config');
|
||||||
|
@ -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);
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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 = '<div class="no-file-selected">请选择一个运行环境配置文件查看内容</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空文件选择
|
|
||||||
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 = '<div class="no-file-selected">请选择一个运行环境配置文件查看内容</div>';
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
@ -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<boolean>} 是否成功加载
|
|
||||||
*/
|
|
||||||
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<void>}
|
|
||||||
*/
|
|
||||||
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<boolean>} 是否成功保存
|
|
||||||
*/
|
|
||||||
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<boolean>} 是否成功创建
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 = `
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 16px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-container {
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
||||||
padding: 16px;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-selector-header {
|
|
||||||
flex-grow: 1;
|
|
||||||
margin-right: 16px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-selector-label {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
margin-right: 10px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-selector-header select {
|
|
||||||
flex-grow: 1;
|
|
||||||
padding: 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-button {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-left: 8px;
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
background-image: url('assets/icons/png/refresh_b.png');
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-button:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-button.refreshing {
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button {
|
|
||||||
background-color: #7986E7;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button.save-button.modified {
|
|
||||||
background-color: #E77979;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button.save-button.modified:hover {
|
|
||||||
background-color: #D66868;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button:hover {
|
|
||||||
background-color: #6875D6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-content {
|
|
||||||
padding: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-content {
|
|
||||||
flex-grow: 1;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 12px;
|
|
||||||
overflow: auto;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
min-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-content pre {
|
|
||||||
margin: 0;
|
|
||||||
font-family: monospace;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-file-selected {
|
|
||||||
color: #888;
|
|
||||||
font-style: italic;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.visual-editor {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-section {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 16px;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: #444;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.property-group {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.property-item {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.property-label {
|
|
||||||
display: block;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
color: #555;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.property-input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.property-input:focus {
|
|
||||||
border-color: #7986E7;
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 0 2px rgba(121, 134, 231, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-button {
|
|
||||||
background-color: #7986E7;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-button:hover {
|
|
||||||
background-color: #6875D6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item {
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 12px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item-title {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #555;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #7986E7;
|
|
||||||
font-size: 18px;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-icon:hover {
|
|
||||||
color: #6875D6;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 模态对话框样式 */
|
|
||||||
.modal {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
z-index: 1000;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 24px;
|
|
||||||
width: 500px;
|
|
||||||
max-width: 90%;
|
|
||||||
max-height: 90vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-form {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-button {
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-button {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-button {
|
|
||||||
background-color: #7986E7;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-button:hover {
|
|
||||||
background-color: #6875D6;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="config-container">
|
|
||||||
<div class="config-header">
|
|
||||||
<div class="file-selector-header">
|
|
||||||
<div class="file-selector-label">运行环境配置文件:</div>
|
|
||||||
<select id="scenarioFile">
|
|
||||||
<option value="">-- 请选择运行环境配置文件 --</option>
|
|
||||||
</select>
|
|
||||||
<button class="refresh-button" id="refreshButton" title="刷新文件列表"></button>
|
|
||||||
</div>
|
|
||||||
<div class="action-buttons">
|
|
||||||
<button class="action-button" id="newConfig">新建</button>
|
|
||||||
<button class="action-button save-button" id="saveConfig">保存</button>
|
|
||||||
<button class="action-button" id="saveAsConfig">另存为</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="config-content">
|
|
||||||
<div class="file-content" id="fileContent">
|
|
||||||
<div class="no-file-selected">请选择一个运行环境配置文件查看内容</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新文件内容显示
|
|
||||||
* @param {HTMLElement} component - 组件实例
|
|
||||||
*/
|
|
||||||
export function updateFileContent(component) {
|
|
||||||
const contentElement = component.shadowRoot.getElementById('fileContent');
|
|
||||||
|
|
||||||
if (!contentElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!component.xmlContent) {
|
|
||||||
contentElement.innerHTML = '<div class="no-file-selected">请选择一个运行环境配置文件查看内容</div>';
|
|
||||||
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 = `<pre>${escapeHtml(component.xmlContent)}</pre>`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建可视化编辑器
|
|
||||||
contentElement.innerHTML = '';
|
|
||||||
renderVisualEditor(component, contentElement, component.xmlDoc);
|
|
||||||
} catch (error) {
|
|
||||||
contentElement.innerHTML = `<pre>${escapeHtml(component.xmlContent)}</pre>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渲染可视化编辑界面
|
|
||||||
* @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 = `<div style="color: red; padding: 16px;">
|
|
||||||
无法编辑:XML文档的根元素不是Scenario。
|
|
||||||
请确保XML文档的根元素是Scenario。
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -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, """)
|
|
||||||
.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<boolean>} 如果可以继续操作返回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 = `
|
|
||||||
<style>
|
|
||||||
.modal {
|
|
||||||
display: block;
|
|
||||||
position: fixed;
|
|
||||||
z-index: 1000;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
background-color: rgba(0,0,0,0.4);
|
|
||||||
}
|
|
||||||
.modal-content {
|
|
||||||
background-color: #fefefe;
|
|
||||||
margin: 15% auto;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #888;
|
|
||||||
width: 80%;
|
|
||||||
max-width: 400px;
|
|
||||||
border-radius: 5px;
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.modal-title {
|
|
||||||
margin-top: 0;
|
|
||||||
color: #333;
|
|
||||||
font-size: 18px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.modal-buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
padding: 8px 15px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
min-width: 80px;
|
|
||||||
}
|
|
||||||
.btn-save {
|
|
||||||
background-color: #7986E7;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.btn-save:hover {
|
|
||||||
background-color: #6875D6;
|
|
||||||
}
|
|
||||||
.btn-dont-save {
|
|
||||||
background-color: #E77979;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.btn-dont-save:hover {
|
|
||||||
background-color: #D66868;
|
|
||||||
}
|
|
||||||
.btn-cancel {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.btn-cancel:hover {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="modal-content">
|
|
||||||
<h3 class="modal-title">当前文件有未保存的更改</h3>
|
|
||||||
<p>您想要保存这些更改吗?</p>
|
|
||||||
<div class="modal-buttons">
|
|
||||||
<button class="btn btn-save" id="saveBtn">保存</button>
|
|
||||||
<button class="btn btn-dont-save" id="dontSaveBtn">不保存</button>
|
|
||||||
<button class="btn btn-cancel" id="cancelBtn">取消</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -221,8 +221,8 @@ class SubToolbar extends HTMLElement {
|
|||||||
<!-- 配置子菜单 -->
|
<!-- 配置子菜单 -->
|
||||||
<div class="sub-menu" data-parent="config">
|
<div class="sub-menu" data-parent="config">
|
||||||
<div class="sub-item" data-icon="chip">
|
<div class="sub-item" data-icon="chip">
|
||||||
<img src="assets/icons/png/chip.png" alt="运行环境配置" class="icon">
|
<img src="assets/icons/png/chip.png" alt="构型配置" class="icon">
|
||||||
运行环境配置
|
构型配置
|
||||||
</div>
|
</div>
|
||||||
<div class="sub-item" data-icon="cube">
|
<div class="sub-item" data-icon="cube">
|
||||||
<img src="assets/icons/png/cube.png" alt="模型配置" class="icon">
|
<img src="assets/icons/png/cube.png" alt="模型配置" class="icon">
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<script src="components/system-info.js"></script>
|
<script src="components/system-info.js"></script>
|
||||||
<script src="components/help-component.js"></script>
|
<script src="components/help-component.js"></script>
|
||||||
<script src="components/system-log.js"></script>
|
<script src="components/system-log.js"></script>
|
||||||
<script src="components/run-env-config.js" type="module"></script>
|
<script src="components/configuration-config.js"></script>
|
||||||
<script src="components/model-config.js" type="module"></script>
|
<script src="components/model-config.js" type="module"></script>
|
||||||
<script src="components/service-config.js" type="module"></script>
|
<script src="components/service-config.js" type="module"></script>
|
||||||
<script src="components/interface-config.js" type="module"></script>
|
<script src="components/interface-config.js" type="module"></script>
|
||||||
@ -523,10 +523,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理运行环境配置标签页
|
// 处理构型配置标签页
|
||||||
if (title === '运行环境配置') {
|
if (title === '构型配置') {
|
||||||
const id = 'run-env-config';
|
const id = 'configuration-config';
|
||||||
tabsContainer.createTab(id, title, icon, parentText, parentTool);
|
tabsContainer.createTab(id, title, icon, parentText, parentTool);
|
||||||
|
contentArea.loadContent(id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -635,7 +636,7 @@
|
|||||||
'资源监控': 'server',
|
'资源监控': 'server',
|
||||||
'帮助': 'help',
|
'帮助': 'help',
|
||||||
'Q&A': 'question',
|
'Q&A': 'question',
|
||||||
'运行环境配置': 'chip',
|
'构型配置': 'chip',
|
||||||
'模型配置': 'cube',
|
'模型配置': 'cube',
|
||||||
'服务配置': 'settings',
|
'服务配置': 'settings',
|
||||||
'接口配置': 'plug',
|
'接口配置': 'plug',
|
||||||
|
@ -7,6 +7,7 @@ const {
|
|||||||
updateConfiguration,
|
updateConfiguration,
|
||||||
deleteConfiguration
|
deleteConfiguration
|
||||||
} = require('../utils/configuration-utils');
|
} = require('../utils/configuration-utils');
|
||||||
|
const { validatePaths } = require('../utils/path-utils');
|
||||||
|
|
||||||
// 获取所有配置列表
|
// 获取所有配置列表
|
||||||
router.get('/configurations', (req, res) => {
|
router.get('/configurations', (req, res) => {
|
||||||
@ -46,9 +47,37 @@ router.post('/configurations', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 更新配置
|
// 更新配置
|
||||||
router.put('/configurations/:id', (req, res) => {
|
router.put('/configurations/:id', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const configData = { ...req.body, ConfID: req.params.id };
|
const configData = { ...req.body, ConfID: req.params.id };
|
||||||
|
|
||||||
|
// 验证路径
|
||||||
|
const pathValidation = await validatePaths({
|
||||||
|
workPath: configData.WorkPath,
|
||||||
|
modelsPath: configData.ModelsPath,
|
||||||
|
servicesPath: configData.ServicesPath
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查是否有无效路径
|
||||||
|
const invalidPaths = Object.entries(pathValidation)
|
||||||
|
.filter(([_, result]) => !result.valid)
|
||||||
|
.map(([key, result]) => ({
|
||||||
|
path: key,
|
||||||
|
error: result.error
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (invalidPaths.length > 0) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: '路径验证失败',
|
||||||
|
details: invalidPaths
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用规范化后的路径更新配置
|
||||||
|
configData.WorkPath = pathValidation.workPath.normalizedPath;
|
||||||
|
configData.ModelsPath = pathValidation.modelsPath.normalizedPath;
|
||||||
|
configData.ServicesPath = pathValidation.servicesPath.normalizedPath;
|
||||||
|
|
||||||
const result = updateConfiguration(configData);
|
const result = updateConfiguration(configData);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -6,7 +6,12 @@ const { getPlanes } = require('../utils/db-utils');
|
|||||||
router.get('/planes', (req, res) => {
|
router.get('/planes', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const planes = getPlanes();
|
const planes = getPlanes();
|
||||||
res.json(planes);
|
// 将BLOB数据转换为base64
|
||||||
|
const planesWithBase64 = planes.map(plane => ({
|
||||||
|
...plane,
|
||||||
|
Icon: plane.Icon ? `data:image/png;base64,${plane.Icon.toString('base64')}` : null
|
||||||
|
}));
|
||||||
|
res.json(planesWithBase64);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取飞机列表失败:', error);
|
console.error('获取飞机列表失败:', error);
|
||||||
res.status(500).json({ error: '获取飞机列表失败', details: error.message });
|
res.status(500).json({ error: '获取飞机列表失败', details: error.message });
|
||||||
|
@ -7,7 +7,7 @@ function getPlanes() {
|
|||||||
|
|
||||||
// 查询所有飞机
|
// 查询所有飞机
|
||||||
const planes = db.prepare(`
|
const planes = db.prepare(`
|
||||||
SELECT PlaneName, Description
|
SELECT PlaneName, Description, Icon
|
||||||
FROM 'Plane'
|
FROM 'Plane'
|
||||||
ORDER BY PlaneName
|
ORDER BY PlaneName
|
||||||
`).all();
|
`).all();
|
||||||
|
91
XNSimHtml/utils/path-utils.js
Normal file
91
XNSimHtml/utils/path-utils.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
const fs = require('fs').promises;
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function ensurePathEndsWithSlash
|
||||||
|
* @description 确保路径以斜杠结尾
|
||||||
|
* @param {string} pathValue - 要处理的路径
|
||||||
|
* @returns {string} 处理后的路径
|
||||||
|
*/
|
||||||
|
function ensurePathEndsWithSlash(pathValue) {
|
||||||
|
if (!pathValue) return pathValue;
|
||||||
|
return pathValue.endsWith('/') ? pathValue : pathValue + '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function validatePaths
|
||||||
|
* @description 验证多个路径是否存在
|
||||||
|
* @param {Object} paths - 要验证的路径对象
|
||||||
|
* @returns {Object} 验证结果,包含每个路径的验证状态和错误信息
|
||||||
|
*/
|
||||||
|
async function validatePaths(paths) {
|
||||||
|
const results = {};
|
||||||
|
|
||||||
|
// 首先验证工作路径
|
||||||
|
if (!paths.workPath) {
|
||||||
|
results.workPath = {
|
||||||
|
valid: false,
|
||||||
|
error: '工作路径不能为空'
|
||||||
|
};
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保工作路径以斜杠结尾
|
||||||
|
const workPath = ensurePathEndsWithSlash(paths.workPath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查工作路径是否存在
|
||||||
|
await fs.access(workPath);
|
||||||
|
results.workPath = {
|
||||||
|
valid: true,
|
||||||
|
normalizedPath: workPath
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
results.workPath = {
|
||||||
|
valid: false,
|
||||||
|
error: error.code === 'ENOENT' ? '工作路径不存在' : '工作路径访问错误'
|
||||||
|
};
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证模型路径和服务路径(相对于工作路径)
|
||||||
|
const relativePaths = {
|
||||||
|
modelsPath: paths.modelsPath,
|
||||||
|
servicesPath: paths.servicesPath
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [key, pathValue] of Object.entries(relativePaths)) {
|
||||||
|
if (!pathValue) {
|
||||||
|
results[key] = {
|
||||||
|
valid: false,
|
||||||
|
error: '路径不能为空'
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 确保相对路径以斜杠结尾
|
||||||
|
const normalizedPath = ensurePathEndsWithSlash(pathValue);
|
||||||
|
// 拼接完整路径
|
||||||
|
const fullPath = path.join(workPath, normalizedPath);
|
||||||
|
// 检查路径是否存在
|
||||||
|
await fs.access(fullPath);
|
||||||
|
results[key] = {
|
||||||
|
valid: true,
|
||||||
|
normalizedPath: normalizedPath
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
results[key] = {
|
||||||
|
valid: false,
|
||||||
|
error: error.code === 'ENOENT' ? '路径不存在' : '路径访问错误'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
validatePaths,
|
||||||
|
ensurePathEndsWithSlash
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user