XNSim/XNSimHtml/bak/run-env-config.js
2025-04-28 12:25:20 +08:00

3295 lines
137 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = '<div class="no-file-selected">请选择一个运行环境配置文件查看内容</div>';
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 = `<pre>${this.escapeHtml(this.xmlContent)}</pre>`;
return;
}
// 创建可视化编辑器
contentElement.innerHTML = '';
this.renderVisualEditor(contentElement, this.xmlDoc);
} catch (error) {
contentElement.innerHTML = `<pre>${this.escapeHtml(this.xmlContent)}</pre>`;
}
}
// 渲染可视化编辑界面
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 = `<div style="color: red; padding: 16px;">
无法编辑XML文档的根元素不是Scenario。
请确保XML文档的根元素是Scenario。
</div>`;
}
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 = '&times;';
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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
render() {
this.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;
}
</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>
`;
}
// 在完成渲染后同时处理状态恢复
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 = '<div class="no-file-selected">请选择一个运行环境配置文件查看内容</div>';
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);