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