完成构型配置页面

This commit is contained in:
jinchao 2025-05-28 16:20:01 +08:00
parent 6b2158c26e
commit 7c5021958d
29 changed files with 1085 additions and 7293 deletions

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -102,12 +102,6 @@ class ContentArea extends HTMLElement {
case 'configuration-config':
contentElement = document.createElement('configuration-config');
break;
case 'model-config':
contentElement = document.createElement('model-config');
break;
case 'service-config':
contentElement = document.createElement('service-config');
break;
case 'interface-config':
contentElement = document.createElement('interface-config');
break;

View File

@ -89,10 +89,6 @@ class MainToolbar extends HTMLElement {
<img src="assets/icons/png/develop.png" alt="开发" class="icon">
<span>开发</span>
</div>
<div class="tool-item" data-tool="config">
<img src="assets/icons/png/sliders.png" alt="配置" class="icon">
<span>配置</span>
</div>
<div class="tool-item" data-tool="run">
<img src="assets/icons/png/play.png" alt="运行" class="icon">
<span>运行</span>

View File

@ -1,947 +0,0 @@
/**
* 模型配置组件
* @type {module}
*/
import {
formatXml, escapeHtml, isValidElementName
} from './model-config/utils.js';
import {
loadModelFiles as apiLoadModelFiles,
loadFileContent as apiLoadFileContent,
saveFileContent as apiSaveFileContent,
createNewConfig as apiCreateNewConfig,
saveFileAs as apiSaveFileAs
} from './model-config/api.js';
import {
parseXmlContent, updateXmlFromVisualEditor, ensureCommandListFormat,
updateElementByPath, updateElementValue, addElement, deleteElement
} from './model-config/xml-handler.js';
import {
renderBasicInfoSection, renderAdvancedSection, renderElements, renderOtherSettingsSection
} from './model-config/ui-elements.js';
import {
renderCommandListTable, renderCommandList
} from './model-config/command-handler.js';
import {
showDateTimeDialog
} from './model-config/datetime.js';
import {
showAddElementDialog, showEditElementDialog, showNewConfigDialog,
showSaveAsDialog, showAddCommandDialog, showEditCommandDialog
} from './model-config/dialogs.js';
import {
getStyles
} from './model-config/styles.js';
class ModelConfig extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.selectedFile = null;
this.xmlContent = '';
this.xmlDoc = null;
this.isEdited = false;
this.modelFiles = [];
this.isActive = true; // 添加活动状态标记
}
async connectedCallback() {
this.isActive = true;
this.render();
await this.loadModelFiles();
this.setupEventListeners();
// 延迟调用renderComplete确保DOM已更新
setTimeout(() => {
this.renderComplete();
}, 100);
}
disconnectedCallback() {
this.isActive = false;
}
// 在组件完成渲染后恢复状态与run-env-config组件一致
renderComplete() {
// 在DOM渲染完成后更新文件选择器
setTimeout(() => {
// 如果有选中的文件,尝试恢复它
if (this.selectedFile && (this.xmlContent || this.xmlDoc)) {
const fileSelector = this.shadowRoot.getElementById('fileSelector');
if (fileSelector) {
// 确认文件在列表中
const fileExists = Array.from(fileSelector.options).some(opt => opt.value === this.selectedFile);
if (fileExists) {
// 设置选中的文件
fileSelector.value = this.selectedFile;
// 更新内容显示
const contentArea = this.shadowRoot.querySelector('#contentArea');
if (contentArea && this.xmlDoc) {
contentArea.innerHTML = `
<div class="editor-container">
<div class="editor-panel active" id="visualEditor"></div>
</div>
`;
const visualEditor = this.shadowRoot.querySelector('#visualEditor');
if (visualEditor) {
this.renderVisualEditor(visualEditor, this.xmlDoc);
// 恢复编辑状态标记
if (this.isEdited) {
this.markEdited();
}
}
} else {
console.log('ModelConfig: 无法找到内容区域或XML文档不存在');
}
} else {
// 文件不存在,清除状态
this.selectedFile = null;
this.xmlContent = '';
this.xmlDoc = null;
this.isEdited = false;
}
} else {
console.log('ModelConfig: 未找到文件选择器');
}
}
}, 50); // 增加延迟确保DOM已经完全加载
}
// 重新激活组件的方法(当标签页重新被选中时调用)
async reactivate() {
if (this.isActive) {
return;
}
this.isActive = true;
try {
// 先重新渲染一次UI以确保Shadow DOM结构完整
this.render();
// 加载文件列表
await this.loadModelFiles();
// 设置事件监听器
this.setupEventListeners();
// 调用renderComplete来恢复状态这是核心改进
this.renderComplete();
} catch (error) {
console.error('ModelConfig: 重新激活组件时出错:', error);
}
}
async loadModelFiles() {
try {
this.modelFiles = await apiLoadModelFiles();
this.updateFileSelector();
} catch (error) {
console.error('加载模型文件失败:', error);
// 如果请求失败可能是API不存在等待一段时间后重试
const retryLoadFiles = async () => {
try {
console.log('尝试重新加载模型文件列表...');
this.modelFiles = await apiLoadModelFiles();
this.updateFileSelector();
} catch (retryError) {
console.error('重试加载模型文件失败:', retryError);
}
};
// 延迟3秒后重试
setTimeout(retryLoadFiles, 3000);
}
}
updateFileSelector() {
const fileSelector = this.shadowRoot.getElementById('fileSelector');
if (!fileSelector) return;
// 清空现有选项
fileSelector.innerHTML = '<option value="">-- 选择模型配置文件 --</option>';
// 按修改时间排序,最新的在前面
const sortedFiles = [...this.modelFiles].sort((a, b) =>
new Date(b.mtime) - new Date(a.mtime)
);
// 添加文件到选择器
sortedFiles.forEach(file => {
const option = document.createElement('option');
option.value = file.path;
option.textContent = file.name;
fileSelector.appendChild(option);
});
}
async loadFileContent(filePath) {
try {
const content = await apiLoadFileContent(filePath);
this.selectedFile = filePath;
this.xmlContent = content;
// 解析XML内容
const { xmlDoc, error, basicXml } = parseXmlContent(content);
if (error && !basicXml) {
// 显示错误信息
const configContent = this.shadowRoot.querySelector('.config-content');
configContent.innerHTML = `
<div class="error-message">
<h3>XML解析错误</h3>
<p>文件内容不是有效的XML格式</p>
<pre>${escapeHtml(content)}</pre>
</div>
`;
return;
}
this.xmlDoc = xmlDoc;
if (basicXml) {
this.xmlContent = basicXml;
}
// 特殊处理CommandList元素确保命令以属性方式存储
ensureCommandListFormat(this.xmlDoc);
this.updateFileContent();
this.resetEditState();
} catch (error) {
console.error('加载文件内容失败:', error);
const configContent = this.shadowRoot.querySelector('.config-content');
configContent.innerHTML = `
<div class="error-message">
<h3>加载失败</h3>
<p>${error.message}</p>
</div>
`;
}
}
updateFileContent() {
const contentArea = this.shadowRoot.querySelector('#contentArea');
if (!this.xmlDoc || !this.selectedFile) {
contentArea.innerHTML = `<div class="no-file-selected">请选择一个模型配置文件查看内容</div>`;
return;
}
contentArea.innerHTML = `
<div class="editor-container">
<div class="editor-panel active" id="visualEditor"></div>
</div>
`;
const visualEditor = this.shadowRoot.querySelector('#visualEditor');
this.renderVisualEditor(visualEditor, this.xmlDoc);
}
render() {
this.shadowRoot.innerHTML = `
<style>${getStyles()}</style>
<div class="config-container">
<div class="config-header">
<div class="file-selector-header">
<div class="file-selector-label">模型配置文件</div>
<select id="fileSelector">
<option value="">-- 请选择模型配置文件 --</option>
</select>
<button class="refresh-button" id="refreshFiles" 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 id="contentArea">
<div class="no-file-selected">请选择一个模型配置文件查看内容</div>
</div>
</div>
</div>
`;
}
setupEventListeners() {
// 文件选择器
const fileSelector = this.shadowRoot.getElementById('fileSelector');
fileSelector.addEventListener('change', async (e) => {
const filePath = e.target.value;
if (filePath) {
// 检查是否需要保存当前文件
if (await this.checkSaveNeeded()) {
this.loadFileContent(filePath);
} else {
// 如果用户取消或保存失败,恢复选择器的值
fileSelector.value = this.selectedFile || '';
}
}
});
// 刷新文件列表
const refreshButton = this.shadowRoot.getElementById('refreshFiles');
refreshButton.addEventListener('click', async () => {
// 检查是否需要保存当前文件
if (await this.checkSaveNeeded()) {
refreshButton.classList.add('refreshing');
try {
// 重新加载文件列表
await this.loadModelFiles();
// 清除编辑状态和内容
this.resetEditState();
// 清空内容区域
this.xmlContent = '';
this.xmlDoc = null;
const contentArea = this.shadowRoot.querySelector('#contentArea');
if (contentArea) {
contentArea.innerHTML = `<div class="no-file-selected">请选择一个模型配置文件查看内容</div>`;
}
// 清空文件选择
const fileSelector = this.shadowRoot.getElementById('fileSelector');
if (fileSelector) {
fileSelector.value = '';
}
// 重置选中的文件
this.selectedFile = null;
} catch (error) {
console.error('刷新文件列表失败:', error);
alert('刷新文件列表失败: ' + error.message);
} finally {
// 无论成功失败,都移除刷新动画
setTimeout(() => {
refreshButton.classList.remove('refreshing');
}, 500);
}
}
// 如果用户点击取消,什么也不做,保留当前内容
});
// 新建配置
const newButton = this.shadowRoot.getElementById('newConfig');
newButton.addEventListener('click', async () => {
// 检查是否需要保存当前文件
if (await this.checkSaveNeeded()) {
showNewConfigDialog(async (result) => {
await this.loadModelFiles();
await this.loadFileContent(result.path);
}, this);
}
});
// 保存配置
const saveButton = this.shadowRoot.getElementById('saveConfig');
saveButton.addEventListener('click', async () => {
if (!this.selectedFile) {
alert('请先选择一个文件或创建新文件');
return;
}
await this.saveCurrentFile();
});
// 另存为
const saveAsButton = this.shadowRoot.getElementById('saveAsConfig');
saveAsButton.addEventListener('click', () => {
if (!this.xmlContent) {
alert('没有内容可保存');
return;
}
showSaveAsDialog(this.xmlContent, this.selectedFile, async (result) => {
this.selectedFile = result.path;
this.resetEditState();
await this.loadModelFiles();
}, this);
});
}
renderVisualEditor(container, xmlDoc) {
if (!xmlDoc || !container) return;
// 添加样式
const style = document.createElement('style');
style.textContent = `
.visual-editor {
padding: 10px;
}
.section-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
border-bottom: 1px solid #e0e0e0;
padding-bottom: 8px;
}
.section {
margin-bottom: 20px;
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 16px;
}
.property-row {
display: flex;
margin-bottom: 12px;
align-items: center;
}
.property-label {
width: 150px;
font-weight: 500;
color: #555;
}
.property-input {
flex: 1;
padding: 8px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.property-input:focus {
border-color: #7986E7;
outline: none;
box-shadow: 0 0 0 2px rgba(121, 134, 231, 0.2);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.section-title-text {
font-size: 18px;
font-weight: bold;
color: #333;
}
.section-buttons {
display: flex;
gap: 8px;
}
.section-button {
background-color: #7986E7;
color: white;
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
.section-button:hover {
background-color: #6875D6;
}
.error-message {
color: #d32f2f;
background-color: #ffebee;
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
}
`;
const visualEditor = document.createElement('div');
visualEditor.className = 'visual-editor';
// 获取根元素
const rootElement = xmlDoc.documentElement;
// 只处理Model根元素
if (rootElement.nodeName === 'Model') {
// 创建模态对话框容器
const modalContainer = document.createElement('div');
modalContainer.className = 'modal';
modalContainer.id = 'propertyModal';
container.appendChild(modalContainer);
// 渲染基本信息部分
renderBasicInfoSection(
visualEditor,
rootElement,
xmlDoc,
(inputElement) => this.showDateTimeDialogWrapper(inputElement),
() => this.markEdited()
);
// 渲染高级设置部分
renderAdvancedSection(
visualEditor,
rootElement,
xmlDoc,
(container, rootElement) => this.renderCommandListTableWrapper(container, rootElement),
() => this.markEdited()
);
// 渲染其他设置部分
renderOtherSettingsSection(
visualEditor,
rootElement,
(container, parentElement, parentPath, level = 0) => this.renderElementsWrapper(
container, parentElement, parentPath, level
)
);
} else {
// 不是Model根元素显示错误信息
visualEditor.innerHTML = `<div class="error-message">
无法编辑XML文档的根元素不是Model
请确保XML文档的根元素是Model
</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(); // 更新XML
this.markEdited(); // 标记已编辑
});
});
}
// 显示日期时间对话框包装方法
showDateTimeDialogWrapper(inputElement) {
showDateTimeDialog(inputElement, this.xmlDoc, () => this.markEdited(), this);
}
// 渲染命令列表表格包装方法
renderCommandListTableWrapper(container, rootElement) {
renderCommandListTable(
container,
rootElement,
(rootElement) => this.showAddCommandDialogWrapper(rootElement),
(command, index) => this.showEditCommandDialogWrapper(command, index),
(command, index) => this.deleteCommandWrapper(command, index)
);
}
// 渲染元素包装方法
renderElementsWrapper(container, parentElement, parentPath, level = 0) {
renderElements(
container,
parentElement,
parentPath,
level,
isValidElementName,
(path, value) => this.updateElementByPathWrapper(path, value),
(element, elementPath) => this.deleteElementWrapper(element, elementPath),
(parentElement, name, value) => this.addElementWrapper(parentElement, name, value),
(parentElement, parentPath) => this.showAddElementDialogWrapper(parentElement, parentPath),
(element, elementPath) => this.showEditElementDialogWrapper(element, elementPath)
);
}
// 更新元素路径包装方法
updateElementByPathWrapper(path, value) {
if (updateElementByPath(this.xmlDoc, path, value)) {
this.updateXmlFromVisualEditor();
this.markEdited();
return true;
}
return false;
}
// 删除元素包装方法
deleteElementWrapper(element, elementPath) {
// 确认删除
if (!confirm(`确定要删除元素 ${element.nodeName} 吗?此操作不可撤销。`)) {
return;
}
if (deleteElement(element)) {
this.updateXmlFromVisualEditor();
this.markEdited();
// 重新渲染元素列表
const container = this.shadowRoot.querySelector('#otherSettingsContainer');
if (container) {
container.innerHTML = '';
this.renderElementsWrapper(container, this.xmlDoc.documentElement, '/' + this.xmlDoc.documentElement.nodeName);
}
return true;
}
return false;
}
// 添加元素包装方法
addElementWrapper(parentElement, name, value) {
const newElement = addElement(this.xmlDoc, parentElement, name, value);
if (newElement) {
this.updateXmlFromVisualEditor();
this.markEdited();
return true;
}
return false;
}
// 显示添加元素对话框包装方法
showAddElementDialogWrapper(parentElement, parentPath) {
showAddElementDialog(parentElement, parentPath, this.xmlDoc, () => {
this.updateXmlFromVisualEditor();
this.markEdited();
// 重新渲染元素列表
const container = this.shadowRoot.querySelector('#otherSettingsContainer');
if (container) {
container.innerHTML = '';
this.renderElementsWrapper(container, this.xmlDoc.documentElement, '/' + this.xmlDoc.documentElement.nodeName);
}
}, this);
}
// 显示编辑元素对话框包装方法
showEditElementDialogWrapper(element, elementPath) {
showEditElementDialog(element, elementPath, this.xmlDoc, () => {
this.updateXmlFromVisualEditor();
this.markEdited();
// 更新UI中的值
const inputElement = this.shadowRoot.querySelector(`input[data-path="${elementPath}"]`);
if (inputElement) {
inputElement.value = element.textContent;
}
}, this);
}
// 显示添加命令对话框包装方法
showAddCommandDialogWrapper(rootElement) {
showAddCommandDialog(rootElement, this.xmlDoc, () => {
this.updateXmlFromVisualEditor();
this.markEdited();
// 重新渲染命令列表
const commandListContainer = this.shadowRoot.querySelector('#commandListContainer');
const visualEditor = this.shadowRoot.querySelector('#visualEditor');
if (commandListContainer && visualEditor) {
// 清空当前渲染
visualEditor.innerHTML = '';
// 重新渲染整个编辑器
this.renderVisualEditor(visualEditor, this.xmlDoc);
}
}, this);
}
// 显示编辑命令对话框包装方法
showEditCommandDialogWrapper(commandElement, index) {
showEditCommandDialog(commandElement, index, () => {
this.updateXmlFromVisualEditor();
this.markEdited();
// 更新表格行
const row = this.shadowRoot.querySelector(`.command-row[data-index="${index}"]`);
if (row) {
const name = commandElement.getAttribute('Name') || '';
const call = commandElement.getAttribute('Call') || '';
const description = commandElement.getAttribute('Description') || '';
row.querySelectorAll('.command-cell')[0].textContent = name;
row.querySelectorAll('.command-cell')[1].textContent = call;
row.querySelectorAll('.command-cell')[2].textContent = description || `${name}描述`;
} else {
// 如果找不到行,重新渲染整个编辑器
const visualEditor = this.shadowRoot.querySelector('#visualEditor');
if (visualEditor) {
visualEditor.innerHTML = '';
this.renderVisualEditor(visualEditor, this.xmlDoc);
}
}
}, this);
}
// 删除命令包装方法
deleteCommandWrapper(commandElement, index) {
if (!confirm('确定要删除此命令吗?此操作不可撤销。')) {
return;
}
if (deleteElement(commandElement)) {
this.updateXmlFromVisualEditor();
this.markEdited();
// 从表格中移除行
const row = this.shadowRoot.querySelector(`.command-row[data-index="${index}"]`);
if (row) {
row.parentNode.removeChild(row);
// 更新索引
const rows = this.shadowRoot.querySelectorAll('.command-row');
rows.forEach((r, i) => {
r.dataset.index = i;
});
} else {
// 如果找不到行,重新渲染整个编辑器
const visualEditor = this.shadowRoot.querySelector('#visualEditor');
if (visualEditor) {
visualEditor.innerHTML = '';
this.renderVisualEditor(visualEditor, this.xmlDoc);
}
}
return true;
}
return false;
}
// 从可视化编辑器更新XML
updateXmlFromVisualEditor() {
if (!this.xmlDoc) return;
this.xmlContent = updateXmlFromVisualEditor(this.xmlDoc);
}
// 标记为已编辑
markEdited() {
this.isEdited = true;
// 更新保存按钮样式
const saveButton = this.shadowRoot.getElementById('saveConfig');
if (saveButton) {
saveButton.classList.add('modified');
}
}
// 重置编辑状态
resetEditState() {
this.isEdited = false;
// 更新保存按钮样式
const saveButton = this.shadowRoot.getElementById('saveConfig');
if (saveButton) {
saveButton.classList.remove('modified');
}
}
// 更新命令列表部分
updateCommandListSection() {
if (!this.xmlDoc) return;
const visualEditorContainer = document.getElementById('visualEditorContainer');
if (!visualEditorContainer) return;
// 移除已有的命令列表容器
const existingContainer = document.getElementById('commandListContainer');
if (existingContainer) {
existingContainer.remove();
}
// 获取根元素和命令列表元素
const rootElement = this.xmlDoc.documentElement;
const commandListElement = ensureCommandListFormat(this.xmlDoc);
// 渲染命令列表
renderCommandList(
visualEditorContainer,
commandListElement,
rootElement,
(rootElement) => this.showAddCommandDialogWrapper(rootElement),
(command, index) => this.showEditCommandDialogWrapper(command, index),
(command, index) => this.deleteCommandWrapper(command, index)
);
}
// 添加检查未保存更改的方法
async checkSaveNeeded() {
if (this.isEdited) {
// 使用自定义对话框替代简单的confirm
const dialogResult = await new Promise(resolve => {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'saveConfirmModal';
modal.innerHTML = `
<style>
.modal {
display: block;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 400px;
border-radius: 5px;
position: relative;
text-align: center;
}
.modal-title {
margin-top: 0;
color: #333;
font-size: 18px;
margin-bottom: 20px;
}
.modal-buttons {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.btn {
padding: 8px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
min-width: 80px;
}
.btn-save {
background-color: #7986E7;
color: white;
}
.btn-save:hover {
background-color: #6875D6;
}
.btn-dont-save {
background-color: #E77979;
color: white;
}
.btn-dont-save:hover {
background-color: #D66868;
}
.btn-cancel {
background-color: #f5f5f5;
color: #333;
}
.btn-cancel:hover {
background-color: #e0e0e0;
}
</style>
<div class="modal-content">
<h3 class="modal-title">当前文件有未保存的更改</h3>
<p>您想要保存这些更改吗</p>
<div class="modal-buttons">
<button class="btn btn-save" id="saveBtn">保存</button>
<button class="btn btn-dont-save" id="dontSaveBtn">不保存</button>
<button class="btn btn-cancel" id="cancelBtn">取消</button>
</div>
</div>
`;
document.body.appendChild(modal);
// 添加事件监听
const saveBtn = modal.querySelector('#saveBtn');
const dontSaveBtn = modal.querySelector('#dontSaveBtn');
const cancelBtn = modal.querySelector('#cancelBtn');
const closeModal = () => {
document.body.removeChild(modal);
};
saveBtn.addEventListener('click', () => {
closeModal();
resolve('save');
});
dontSaveBtn.addEventListener('click', () => {
closeModal();
resolve('dont-save');
});
cancelBtn.addEventListener('click', () => {
closeModal();
resolve('cancel');
});
// 点击模态窗口外部也取消
modal.addEventListener('click', (event) => {
if (event.target === modal) {
closeModal();
resolve('cancel');
}
});
});
// 根据对话框结果执行相应操作
if (dialogResult === 'save') {
try {
await this.saveCurrentFile();
return true; // 继续执行后续操作
} catch (error) {
console.error('保存出错:', error);
return false; // 保存失败,不继续执行
}
} else if (dialogResult === 'dont-save') {
// 不保存,但继续执行后续操作
return true;
} else {
// 用户取消,不执行后续操作
return false;
}
}
// 没有编辑状态直接返回true允许继续操作
return true;
}
// 检查未保存更改 (兼容旧版的使用)
checkUnsavedChanges() {
// 由于无法同步返回Promise结果这个方法保留为返回布尔值
// 但为了向前兼容,仍保留这个方法
return this.isEdited;
}
// 保存当前文件
async saveCurrentFile() {
if (!this.selectedFile || !this.isEdited) return false;
try {
await apiSaveFileContent(this.selectedFile, this.xmlContent);
this.resetEditState();
return true;
} catch (error) {
alert('保存失败: ' + error.message);
return false;
}
}
}
customElements.define('model-config', ModelConfig);

View File

@ -1,133 +0,0 @@
/**
* API操作模块
* @type {module}
*/
/**
* 加载模型文件列表
* @returns {Promise<Array>} 模型文件数组
*/
export async function loadModelFiles() {
try {
const response = await fetch('/api/model-files');
if (!response.ok) {
throw new Error(`服务器响应错误: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('加载模型文件失败:', error);
throw error;
}
}
/**
* 加载文件内容
* @param {string} filePath - 文件路径
* @returns {Promise<string>} 文件内容
*/
export async function loadFileContent(filePath) {
try {
const response = await fetch(`/api/model-file-content?path=${encodeURIComponent(filePath)}`);
if (!response.ok) {
throw new Error(`服务器响应错误: ${response.status}`);
}
return await response.text();
} catch (error) {
console.error('加载文件内容失败:', error);
throw error;
}
}
/**
* 保存文件内容
* @param {string} filePath - 文件路径
* @param {string} content - 文件内容
* @returns {Promise<Object>} 保存结果
*/
export async function saveFileContent(filePath, content) {
try {
const response = await fetch('/api/save-model-file', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
path: filePath,
content: content
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `保存失败: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('保存文件失败:', error);
throw error;
}
}
/**
* 创建新的模型配置文件
* @param {string} fileName - 文件名
* @param {boolean} overwrite - 是否覆盖现有文件
* @returns {Promise<Object>} 创建结果
*/
export async function createNewConfig(fileName, overwrite = false) {
try {
const response = await fetch('/api/create-model-file', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ fileName, overwrite })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `创建失败: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('创建新配置文件失败:', error);
throw error;
}
}
/**
* 另存为新文件
* @param {string} fileName - 文件名
* @param {string} content - 文件内容
* @param {string} currentFile - 当前文件路径
* @param {boolean} overwrite - 是否覆盖现有文件
* @returns {Promise<Object>} 保存结果
*/
export async function saveFileAs(fileName, content, currentFile, overwrite = false) {
try {
const response = await fetch('/api/save-model-as', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileName,
content,
currentFile,
overwrite
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `保存失败: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('另存为失败:', error);
throw error;
}
}

View File

@ -1,278 +0,0 @@
/**
* 命令处理模块
* @type {module}
*/
import { escapeHtml } from './utils.js';
/**
* 渲染命令列表表格
* @param {Element} container - 容器元素
* @param {Element} rootElement - XML根元素
* @param {Function} showAddCommandDialog - 显示添加命令对话框的函数
* @param {Function} showEditCommandDialog - 显示编辑命令对话框的函数
* @param {Function} deleteCommand - 删除命令的函数
*/
export function renderCommandListTable(container, rootElement, showAddCommandDialog, showEditCommandDialog, deleteCommand) {
// 查找CommandList元素
const commandListElement = rootElement.querySelector('CommandList');
if (!commandListElement) {
// 创建CommandList部分
const commandListSection = document.createElement('div');
commandListSection.className = 'form-group';
commandListSection.innerHTML = `
<label for="commandListTable">命令列表</label>
<div id="commandListContainer" class="command-table-container">
<table id="commandListTable" class="command-table">
<thead>
<tr>
<th>名称</th>
<th>调用</th>
<th>描述</th>
<th style="width: 100px;">操作</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="4" style="text-align: center;">
<button id="addCommandButton" class="action-button">
添加命令
</button>
</td>
</tr>
</tbody>
</table>
</div>
`;
container.appendChild(commandListSection);
// 添加命令事件监听
const addCommandButton = commandListSection.querySelector('#addCommandButton');
addCommandButton.addEventListener('click', () => {
showAddCommandDialog(rootElement);
});
return;
}
// 创建CommandList部分
const commandListSection = document.createElement('div');
commandListSection.className = 'form-group';
commandListSection.innerHTML = `
<label for="commandListTable">命令列表</label>
<div id="commandListContainer" class="command-table-container">
<table id="commandListTable" class="command-table">
<thead>
<tr>
<th>名称</th>
<th>调用</th>
<th>描述</th>
<th style="width: 100px;">操作</th>
</tr>
</thead>
<tbody>
<!-- 命令会在这里动态添加 -->
</tbody>
</table>
</div>
`;
container.appendChild(commandListSection);
const tableBody = commandListSection.querySelector('tbody');
// 获取命令列表
const commandElements = commandListElement.querySelectorAll('Command');
// 如果没有命令,添加"添加命令"按钮
if (commandElements.length === 0) {
tableBody.innerHTML = `
<tr>
<td colspan="4" style="text-align: center;">
<button id="addCommandButton" class="action-button">
添加命令
</button>
</td>
</tr>
`;
// 添加命令事件监听
const addCommandButton = tableBody.querySelector('#addCommandButton');
addCommandButton.addEventListener('click', () => {
showAddCommandDialog(rootElement);
});
return;
}
// 添加命令到表格
commandElements.forEach((command, index) => {
const name = command.getAttribute('Name') || '';
const call = command.getAttribute('Call') || '';
const description = command.getAttribute('Description') || '';
const row = document.createElement('tr');
row.className = 'command-row';
row.dataset.index = index;
row.innerHTML = `
<td class="command-cell">${escapeHtml(name)}</td>
<td class="command-cell">${escapeHtml(call)}</td>
<td class="command-cell">${escapeHtml(description)}</td>
<td class="action-cell">
<div class="command-actions">
<button class="command-action-button edit-button">编辑</button>
<button class="command-action-button delete-button">删除</button>
</div>
</td>
`;
// 编辑按钮事件
row.querySelector('.edit-button').addEventListener('click', () => {
showEditCommandDialog(command, index);
});
// 删除按钮事件
row.querySelector('.delete-button').addEventListener('click', () => {
deleteCommand(command, index);
});
tableBody.appendChild(row);
});
// 添加"添加命令"按钮行
const addRow = document.createElement('tr');
addRow.innerHTML = `
<td colspan="4" style="text-align: center;">
<button id="addCommandButton" class="action-button">
添加命令
</button>
</td>
`;
// 添加命令事件监听
addRow.querySelector('#addCommandButton').addEventListener('click', () => {
showAddCommandDialog(rootElement);
});
tableBody.appendChild(addRow);
}
/**
* 渲染命令列表
* @param {Element} container - 容器元素
* @param {Element} commandListElement - 命令列表元素
* @param {Element} rootElement - XML根元素
* @param {Function} showAddCommandDialog - 显示添加命令对话框的函数
* @param {Function} showEditCommandDialog - 显示编辑命令对话框的函数
* @param {Function} deleteCommand - 删除命令的函数
*/
export function renderCommandList(container, commandListElement, rootElement, showAddCommandDialog, showEditCommandDialog, deleteCommand) {
// 创建命令列表容器
const commandListContainer = document.createElement('div');
commandListContainer.id = 'commandListContainer';
commandListContainer.className = 'section';
// 创建标题和添加按钮
const header = document.createElement('div');
header.className = 'section-header';
header.innerHTML = `
<h3>命令列表</h3>
<button id="addCommandBtn" class="action-button">添加命令</button>
`;
commandListContainer.appendChild(header);
// 添加按钮事件
const addBtn = header.querySelector('#addCommandBtn');
addBtn.addEventListener('click', () => {
showAddCommandDialog(rootElement);
});
// 创建表格
const table = document.createElement('table');
table.className = 'command-table';
// 创建表头
const thead = document.createElement('thead');
thead.innerHTML = `
<tr>
<th>命令名称</th>
<th>命令调用</th>
<th>描述</th>
<th>操作</th>
</tr>
`;
table.appendChild(thead);
// 创建表体
const tbody = document.createElement('tbody');
// 如果存在命令列表元素,则渲染命令
if (commandListElement) {
const commands = commandListElement.querySelectorAll('Command');
if (commands.length === 0) {
// 如果没有命令,显示空行
const emptyRow = document.createElement('tr');
emptyRow.innerHTML = '<td colspan="4" style="text-align: center;">没有命令记录</td>';
tbody.appendChild(emptyRow);
} else {
// 渲染命令列表
commands.forEach((command, index) => {
const name = command.getAttribute('Name') || '';
const call = command.getAttribute('Call') || '';
const description = command.getAttribute('Description') || '';
const row = document.createElement('tr');
row.className = 'command-row';
row.dataset.index = index;
row.innerHTML = `
<td class="command-cell">${escapeHtml(name)}</td>
<td class="command-cell">${escapeHtml(call)}</td>
<td class="command-cell">${escapeHtml(description)}</td>
<td class="command-actions">
<button class="action-button edit-command">编辑</button>
<button class="action-button delete-command">删除</button>
</td>
`;
tbody.appendChild(row);
// 添加编辑按钮事件
const editBtn = row.querySelector('.edit-command');
editBtn.addEventListener('click', () => {
showEditCommandDialog(command, index);
});
// 添加删除按钮事件
const deleteBtn = row.querySelector('.delete-command');
deleteBtn.addEventListener('click', () => {
deleteCommand(command, index);
});
});
}
}
table.appendChild(tbody);
// 添加"添加命令"按钮行
const addRow = document.createElement('tr');
addRow.innerHTML = `
<td colspan="4" style="text-align: center;">
<button id="addCommandButton" class="action-button">
添加命令
</button>
</td>
`;
// 添加命令事件监听
addRow.querySelector('#addCommandButton').addEventListener('click', () => {
showAddCommandDialog(rootElement);
});
tbody.appendChild(addRow);
commandListContainer.appendChild(table);
container.appendChild(commandListContainer);
}

View File

@ -1,248 +0,0 @@
/**
* 日期时间处理模块
* @type {module}
*/
import { updateElementValue } from './xml-handler.js';
/**
* 显示日期时间选择对话框
* @param {HTMLInputElement} inputElement - 输入元素
* @param {Document} xmlDoc - XML文档对象
* @param {Function} markEdited - 标记已编辑的回调函数
* @param {Element} context - 上下文元素用于访问shadowRoot
*/
export function showDateTimeDialog(inputElement, xmlDoc, markEdited, context) {
// 解析当前日期和时间
let currentDate = new Date();
let currentTime = '00:00:00';
if (inputElement.value) {
try {
const parts = inputElement.value.split(' ');
if (parts.length >= 1) {
const dateParts = parts[0].split('-');
if (dateParts.length === 3) {
const year = parseInt(dateParts[0]);
const month = parseInt(dateParts[1]) - 1; // 月份从0开始
const day = parseInt(dateParts[2]);
currentDate = new Date(year, month, day);
}
}
if (parts.length >= 2) {
currentTime = parts[1];
}
} catch (error) {
console.error('解析日期时间失败:', error);
currentDate = new Date();
}
}
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'dateTimeModal';
// 获取年、月、日
const year = currentDate.getFullYear();
const month = currentDate.getMonth(); // 0-11
const day = currentDate.getDate();
// 生成日历
const daysInMonth = new Date(year, month + 1, 0).getDate();
const firstDay = new Date(year, month, 1).getDay(); // 0-60表示周日
// 生成月份选项
let monthOptions = '';
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
monthNames.forEach((name, idx) => {
monthOptions += `<option value="${idx}" ${idx === month ? 'selected' : ''}>${name}</option>`;
});
// 生成年份选项
let yearOptions = '';
const currentYear = new Date().getFullYear();
for (let y = currentYear - 10; y <= currentYear + 10; y++) {
yearOptions += `<option value="${y}" ${y === year ? 'selected' : ''}>${y}</option>`;
}
// 生成日历表格
let calendarRows = '';
let dayCount = 1;
// 添加表头
calendarRows += '<tr>';
['日', '一', '二', '三', '四', '五', '六'].forEach(dayName => {
calendarRows += `<th>${dayName}</th>`;
});
calendarRows += '</tr>';
// 计算行数
const totalCells = firstDay + daysInMonth;
const rowCount = Math.ceil(totalCells / 7);
// 添加日期行
for (let i = 0; i < rowCount; i++) {
calendarRows += '<tr>';
for (let j = 0; j < 7; j++) {
if ((i === 0 && j < firstDay) || dayCount > daysInMonth) {
calendarRows += '<td></td>';
} else {
const isToday = dayCount === day;
calendarRows += `<td class="calendar-day ${isToday ? 'selected' : ''}" data-day="${dayCount}">${dayCount}</td>`;
dayCount++;
}
}
calendarRows += '</tr>';
}
modal.innerHTML = `
<div class="modal-content" style="max-width: 400px;">
<div class="modal-header">
<div class="modal-title">选择日期和时间</div>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<div class="calendar-container">
<div class="calendar-header">
<select id="calendarMonth">${monthOptions}</select>
<select id="calendarYear">${yearOptions}</select>
</div>
<table class="calendar-table">
<thead>
${calendarRows}
</thead>
</table>
</div>
<div class="time-container" style="margin-top: 15px;">
<label for="timeInput">时间:</label>
<input type="time" id="timeInput" step="1" value="${currentTime}" style="width: 100%; padding: 8px;">
</div>
</div>
<div class="modal-footer">
<button id="cancelDateTime" class="action-button secondary">取消</button>
<button id="confirmDateTime" class="action-button">确定</button>
</div>
</div>
`;
// 添加样式
const style = document.createElement('style');
style.textContent = `
.calendar-container {
width: 100%;
}
.calendar-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.calendar-header select {
padding: 5px;
border: 1px solid #ddd;
border-radius: 4px;
}
.calendar-table {
width: 100%;
border-collapse: collapse;
}
.calendar-table th,
.calendar-table td {
padding: 8px;
text-align: center;
border: 1px solid #ddd;
}
.calendar-day {
cursor: pointer;
}
.calendar-day:hover {
background-color: #f0f0f0;
}
.calendar-day.selected {
background-color: #7986E7;
color: white;
}
`;
// 获取shadowRoot
const shadowRoot = context.shadowRoot;
// 添加到DOM
shadowRoot.appendChild(style);
shadowRoot.appendChild(modal);
modal.style.display = 'block';
// 事件处理
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelDateTime');
const confirmBtn = modal.querySelector('#confirmDateTime');
const calendarMonth = modal.querySelector('#calendarMonth');
const calendarYear = modal.querySelector('#calendarYear');
const calendarDays = modal.querySelectorAll('.calendar-day');
const timeInput = modal.querySelector('#timeInput');
// 选择日期事件
calendarDays.forEach(cell => {
cell.addEventListener('click', (e) => {
// 移除所有选中状态
calendarDays.forEach(day => day.classList.remove('selected'));
// 添加新选中状态
e.target.classList.add('selected');
});
});
// 月份和年份变化时重新渲染日历
const updateCalendar = () => {
const selectedYear = parseInt(calendarYear.value);
const selectedMonth = parseInt(calendarMonth.value);
// 关闭当前对话框
closeModal();
// 更新日期参数并重新显示对话框
currentDate = new Date(selectedYear, selectedMonth, 1);
showDateTimeDialog(inputElement, xmlDoc, markEdited, context);
};
calendarMonth.addEventListener('change', updateCalendar);
calendarYear.addEventListener('change', updateCalendar);
const closeModal = () => {
modal.style.display = 'none';
shadowRoot.removeChild(modal);
if (style.parentNode) {
shadowRoot.removeChild(style);
}
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', () => {
// 获取选中的日期
const selectedDay = modal.querySelector('.calendar-day.selected');
if (!selectedDay) {
closeModal();
return;
}
const day = selectedDay.dataset.day;
const month = parseInt(calendarMonth.value) + 1; // 月份从0开始显示时+1
const year = calendarYear.value;
// 格式化日期
const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
// 获取时间
const time = timeInput.value || '00:00:00';
// 更新输入框值
inputElement.value = `${formattedDate} ${time}`;
// 触发更改事件
updateElementValue(xmlDoc, inputElement.dataset.field, inputElement.value);
markEdited(); // 确保触发修改标志
closeModal();
});
}

View File

@ -1,558 +0,0 @@
/**
* 对话框处理模块
* @type {module}
*/
import { isValidElementName } from './utils.js';
import { addElement, deleteElement, updateElementByPath } from './xml-handler.js';
import { createNewConfig, saveFileAs } from './api.js';
/**
* 显示添加元素对话框
* @param {Element} parentElement - 父元素
* @param {string} parentPath - 父元素路径
* @param {Document} xmlDoc - XML文档对象
* @param {Function} onSuccess - 成功回调函数
* @param {Element} context - 组件上下文用于访问shadowRoot
*/
export function showAddElementDialog(parentElement, parentPath, xmlDoc, onSuccess, context) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'addElementModal';
modal.innerHTML = `
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">添加元素</div>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="elementName">元素名称</label>
<input type="text" id="elementName" class="form-control" placeholder="输入元素名称" />
<div id="elementNameError" class="error-message" style="display: none; font-size: 12px; margin-top: 5px;"></div>
</div>
<div class="form-group">
<label for="elementValue">元素值 <small>(如果需要)</small></label>
<input type="text" id="elementValue" class="form-control" placeholder="输入元素值" />
</div>
</div>
<div class="modal-footer">
<button id="cancelAddElement" class="action-button secondary">取消</button>
<button id="confirmAddElement" class="action-button">添加</button>
</div>
</div>
`;
// 获取shadowRoot
const shadowRoot = context.shadowRoot;
shadowRoot.appendChild(modal);
modal.style.display = 'block';
// 事件处理
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelAddElement');
const confirmBtn = modal.querySelector('#confirmAddElement');
const nameInput = modal.querySelector('#elementName');
const valueInput = modal.querySelector('#elementValue');
const errorDiv = modal.querySelector('#elementNameError');
// 实时验证元素名称
nameInput.addEventListener('input', () => {
const name = nameInput.value.trim();
const isValid = isValidElementName(name);
if (!name) {
errorDiv.textContent = '元素名称不能为空';
errorDiv.style.display = 'block';
confirmBtn.disabled = true;
} else if (!isValid) {
errorDiv.textContent = '无效的元素名称。元素名称必须以字母或下划线开头,不能包含空格或特殊字符。';
errorDiv.style.display = 'block';
confirmBtn.disabled = true;
} else {
errorDiv.style.display = 'none';
confirmBtn.disabled = false;
}
});
const closeModal = () => {
modal.style.display = 'none';
shadowRoot.removeChild(modal);
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', () => {
const name = nameInput.value.trim();
const value = valueInput.value;
if (!name) {
alert('元素名称不能为空');
return;
}
if (!isValidElementName(name)) {
alert('无效的元素名称。元素名称必须以字母或下划线开头,不能包含空格或特殊字符。');
return;
}
// 添加元素
addElement(xmlDoc, parentElement, name, value);
// 调用成功回调
if (onSuccess && typeof onSuccess === 'function') {
onSuccess();
}
closeModal();
});
}
/**
* 显示编辑元素对话框
* @param {Element} element - 要编辑的元素
* @param {string} elementPath - 元素路径
* @param {Document} xmlDoc - XML文档对象
* @param {Function} onSuccess - 成功回调函数
* @param {Element} context - 组件上下文用于访问shadowRoot
*/
export function showEditElementDialog(element, elementPath, xmlDoc, onSuccess, context) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'editElementModal';
modal.innerHTML = `
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">编辑元素</div>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="elementPath">元素路径</label>
<input type="text" id="elementPath" class="form-control" value="${elementPath}" readonly />
</div>
<div class="form-group">
<label for="elementValue">元素值</label>
<input type="text" id="elementValue" class="form-control" value="${element.textContent}" placeholder="输入元素值" />
</div>
</div>
<div class="modal-footer">
<button id="cancelEditElement" class="action-button secondary">取消</button>
<button id="confirmEditElement" class="action-button">保存</button>
</div>
</div>
`;
// 获取shadowRoot
const shadowRoot = context.shadowRoot;
shadowRoot.appendChild(modal);
modal.style.display = 'block';
// 事件处理
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelEditElement');
const confirmBtn = modal.querySelector('#confirmEditElement');
const valueInput = modal.querySelector('#elementValue');
const closeModal = () => {
modal.style.display = 'none';
shadowRoot.removeChild(modal);
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', () => {
const value = valueInput.value;
// 更新元素值
updateElementByPath(xmlDoc, elementPath, value);
// 调用成功回调
if (onSuccess && typeof onSuccess === 'function') {
onSuccess();
}
closeModal();
});
}
/**
* 显示新建配置对话框
* @param {Function} onSuccess - 成功回调函数
* @param {Element} context - 上下文元素用于获取shadowRoot
*/
export function showNewConfigDialog(onSuccess, context) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'newConfigModal';
modal.innerHTML = `
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">创建新模型配置</div>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="newFileName">文件名</label>
<input type="text" id="newFileName" class="form-control" placeholder="输入文件名 (例如: mymodel.mcfg)" />
</div>
</div>
<div class="modal-footer">
<button id="cancelNewConfig" class="action-button secondary">取消</button>
<button id="confirmNewConfig" class="action-button">创建</button>
</div>
</div>
`;
// 获取shadowRoot
const shadowRoot = context.shadowRoot;
shadowRoot.appendChild(modal);
modal.style.display = 'block';
// 事件处理
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelNewConfig');
const confirmBtn = modal.querySelector('#confirmNewConfig');
const closeModal = () => {
modal.style.display = 'none';
shadowRoot.removeChild(modal);
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', async () => {
let fileName = modal.querySelector('#newFileName').value.trim();
if (!fileName) {
alert('文件名不能为空');
return;
}
// 如果文件名不以.mcfg结尾自动添加
if (!fileName.endsWith('.mcfg')) {
fileName += '.mcfg';
}
try {
const result = await createNewConfig(fileName);
if (onSuccess && typeof onSuccess === 'function') {
onSuccess(result);
}
closeModal();
} catch (error) {
if (error.message.includes('文件已存在')) {
if (confirm('文件已存在,是否覆盖?')) {
try {
const result = await createNewConfig(fileName, true);
if (onSuccess && typeof onSuccess === 'function') {
onSuccess(result);
}
closeModal();
} catch (retryError) {
alert('创建文件失败: ' + retryError.message);
}
}
} else {
alert('创建文件失败: ' + error.message);
}
}
});
}
/**
* 显示另存为对话框
* @param {string} xmlContent - XML内容
* @param {string} selectedFile - 当前选中的文件
* @param {Function} onSuccess - 成功回调函数
* @param {Element} context - 上下文元素用于获取shadowRoot
*/
export function showSaveAsDialog(xmlContent, selectedFile, onSuccess, context) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'saveAsModal';
modal.innerHTML = `
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">另存为</div>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="saveAsFileName">文件名</label>
<input type="text" id="saveAsFileName" class="form-control" placeholder="输入文件名 (例如: mymodel.mcfg)" />
</div>
</div>
<div class="modal-footer">
<button id="cancelSaveAs" class="action-button secondary">取消</button>
<button id="confirmSaveAs" class="action-button">保存</button>
</div>
</div>
`;
// 获取shadowRoot
const shadowRoot = context.shadowRoot;
shadowRoot.appendChild(modal);
modal.style.display = 'block';
// 事件处理
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelSaveAs');
const confirmBtn = modal.querySelector('#confirmSaveAs');
const closeModal = () => {
modal.style.display = 'none';
shadowRoot.removeChild(modal);
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', async () => {
let fileName = modal.querySelector('#saveAsFileName').value.trim();
if (!fileName) {
alert('文件名不能为空');
return;
}
// 如果文件名不以.mcfg结尾自动添加
if (!fileName.endsWith('.mcfg')) {
fileName += '.mcfg';
}
try {
const result = await saveFileAs(fileName, xmlContent, selectedFile);
if (onSuccess && typeof onSuccess === 'function') {
onSuccess(result);
}
closeModal();
} catch (error) {
if (error.message.includes('文件已存在')) {
if (confirm('文件已存在,是否覆盖?')) {
try {
const result = await saveFileAs(fileName, xmlContent, selectedFile, true);
if (onSuccess && typeof onSuccess === 'function') {
onSuccess(result);
}
closeModal();
} catch (retryError) {
alert('保存失败: ' + retryError.message);
}
}
} else {
alert('保存失败: ' + error.message);
}
}
});
}
/**
* 显示添加命令对话框
* @param {Element} rootElement - XML根元素
* @param {Document} xmlDoc - XML文档对象
* @param {Function} onSuccess - 成功回调函数
* @param {Element} context - 上下文元素用于获取shadowRoot
*/
export function showAddCommandDialog(rootElement, xmlDoc, onSuccess, context) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'addCommandModal';
modal.innerHTML = `
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">添加命令</div>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="commandName">命令名称</label>
<input type="text" id="commandName" class="form-control" placeholder="输入命令名称" />
</div>
<div class="form-group">
<label for="commandCall">命令调用</label>
<input type="text" id="commandCall" class="form-control" placeholder="输入命令调用" />
</div>
<div class="form-group">
<label for="commandDescription">命令描述</label>
<input type="text" id="commandDescription" class="form-control" placeholder="输入命令描述" />
</div>
</div>
<div class="modal-footer">
<button id="cancelAddCommand" class="action-button secondary">取消</button>
<button id="confirmAddCommand" class="action-button">添加</button>
</div>
</div>
`;
// 获取shadowRoot
const shadowRoot = context.shadowRoot;
shadowRoot.appendChild(modal);
modal.style.display = 'block';
// 事件处理
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelAddCommand');
const confirmBtn = modal.querySelector('#confirmAddCommand');
const closeModal = () => {
modal.style.display = 'none';
shadowRoot.removeChild(modal);
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', () => {
const name = modal.querySelector('#commandName').value.trim();
const call = modal.querySelector('#commandCall').value.trim();
const description = modal.querySelector('#commandDescription').value.trim();
if (!name) {
alert('命令名称不能为空');
return;
}
if (!call) {
alert('命令调用不能为空');
return;
}
try {
// 查找或创建CommandList元素
let commandListElement = rootElement.querySelector('CommandList');
if (!commandListElement) {
commandListElement = xmlDoc.createElement('CommandList');
rootElement.appendChild(commandListElement);
}
// 创建新Command元素
const commandElement = xmlDoc.createElement('Command');
commandElement.setAttribute('Name', name);
commandElement.setAttribute('Call', call);
commandElement.setAttribute('Description', description || `${name}描述`);
// 添加到CommandList
commandListElement.appendChild(commandElement);
// 调用成功回调
if (onSuccess && typeof onSuccess === 'function') {
onSuccess();
}
closeModal();
} catch (error) {
console.error('添加命令失败:', error);
alert('添加命令失败: ' + error.message);
}
});
}
/**
* 显示编辑命令对话框
* @param {Element} commandElement - 命令元素
* @param {number} index - 命令索引
* @param {Function} onSuccess - 成功回调函数
* @param {Element} context - 上下文元素用于获取shadowRoot
*/
export function showEditCommandDialog(commandElement, index, onSuccess, context) {
// 获取当前命令属性
const name = commandElement.getAttribute('Name') || '';
const call = commandElement.getAttribute('Call') || '';
const description = commandElement.getAttribute('Description') || '';
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'editCommandModal';
modal.innerHTML = `
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">编辑命令</div>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="commandName">命令名称</label>
<input type="text" id="commandName" class="form-control" value="${name}" placeholder="输入命令名称" />
</div>
<div class="form-group">
<label for="commandCall">命令调用</label>
<input type="text" id="commandCall" class="form-control" value="${call}" placeholder="输入命令调用" />
</div>
<div class="form-group">
<label for="commandDescription">命令描述</label>
<input type="text" id="commandDescription" class="form-control" value="${description}" placeholder="输入命令描述" />
</div>
</div>
<div class="modal-footer">
<button id="cancelEditCommand" class="action-button secondary">取消</button>
<button id="confirmEditCommand" class="action-button">保存</button>
</div>
</div>
`;
// 获取shadowRoot
const shadowRoot = context.shadowRoot;
shadowRoot.appendChild(modal);
modal.style.display = 'block';
// 事件处理
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelEditCommand');
const confirmBtn = modal.querySelector('#confirmEditCommand');
const closeModal = () => {
modal.style.display = 'none';
shadowRoot.removeChild(modal);
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', () => {
const newName = modal.querySelector('#commandName').value.trim();
const newCall = modal.querySelector('#commandCall').value.trim();
const newDescription = modal.querySelector('#commandDescription').value.trim();
if (!newName) {
alert('命令名称不能为空');
return;
}
if (!newCall) {
alert('命令调用不能为空');
return;
}
try {
// 更新命令属性
commandElement.setAttribute('Name', newName);
commandElement.setAttribute('Call', newCall);
commandElement.setAttribute('Description', newDescription || `${newName}描述`);
// 调用成功回调
if (onSuccess && typeof onSuccess === 'function') {
onSuccess();
}
closeModal();
} catch (error) {
console.error('编辑命令失败:', error);
alert('编辑命令失败: ' + error.message);
}
});
}

View File

@ -1,590 +0,0 @@
/**
* 样式模块
* @type {module}
*/
/**
* 获取组件的CSS样式
* @returns {string} 组件的CSS样式字符串
*/
export function getStyles() {
return `
: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;
appearance: none;
padding-right: 30px;
background-image: url('assets/icons/png/chevron-down_b.png');
background-repeat: no-repeat;
background-position: right 8px center;
background-size: 14px;
}
.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;
}
.action-button.secondary {
background-color: #f0f0f0;
color: #333;
}
.action-button.secondary:hover {
background-color: #e0e0e0;
}
.config-content {
padding: 0;
flex-grow: 1;
display: flex;
flex-direction: column;
overflow: auto;
}
.editor-container {
height: 100%;
display: flex;
flex-direction: column;
}
.editor-panel {
flex: 1;
height: 100%;
overflow: auto;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 12px;
background-color: #f9f9f9;
}
.editor-panel.active {
display: block;
}
.section {
margin-bottom: 20px;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 10px;
background-color: white;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #e0e0e0;
}
.section-title {
font-weight: bold;
color: #555;
}
.property-table {
width: 100%;
border-collapse: collapse;
}
.property-table th, .property-table td {
text-align: left;
padding: 8px;
border-bottom: 1px solid #e0e0e0;
}
.property-table th {
background-color: #f5f5f5;
}
.button-sm {
padding: 2px 8px;
font-size: 12px;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 20px;
border: 1px solid #888;
width: 50%;
max-width: 500px;
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.modal-title {
font-weight: bold;
font-size: 18px;
}
.close {
color: #aaa;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: black;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 15px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
}
.form-control {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
}
.error-message {
color: #d32f2f;
background-color: #ffebee;
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
}
.no-file-selected {
color: #888;
font-style: italic;
text-align: center;
margin-top: 80px;
}
/* 命令表格相关样式 */
.command-table-container {
margin-top: 10px;
width: 100%;
overflow-x: auto;
}
.command-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.command-table th,
.command-table td {
padding: 8px;
text-align: left;
border: 1px solid #ddd;
}
.command-table th {
background-color: #f5f5f5;
font-weight: 600;
}
.command-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.command-table tr:hover {
background-color: #f1f1f1;
}
/* 命令表格单元格和行样式 */
.command-cell {
padding: 8px;
text-align: left;
border: 1px solid #ddd;
vertical-align: middle;
word-break: break-word;
}
.command-row {
transition: background-color 0.2s;
}
.action-cell {
text-align: center;
white-space: nowrap;
}
.command-actions {
display: flex;
gap: 5px;
justify-content: center;
}
.command-action-button {
padding: 3px 8px;
font-size: 12px;
border: none;
border-radius: 3px;
cursor: pointer;
transition: background-color 0.2s;
color: white;
}
.edit-button {
background-color: #7986E7;
}
.edit-button:hover {
background-color: #6875D6;
}
.delete-button {
background-color: #E77979;
}
.delete-button:hover {
background-color: #D66868;
}
/* 输入容器样式 */
.input-container {
display: flex;
align-items: center;
}
/* 图标按钮样式 */
.icon-button {
background-color: transparent;
border: none;
cursor: pointer;
padding: 0;
width: 28px;
height: 28px;
margin-left: 8px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
opacity: 0.7;
transition: opacity 0.2s;
}
.icon-button:hover {
opacity: 1;
}
.calendar-button {
background-image: url('assets/icons/png/calendar_b.png');
width: 24px;
height: 24px;
background-size: 16px;
}
.refresh-button-sm {
background-image: url('assets/icons/png/refresh_b.png');
width: 24px;
height: 24px;
background-size: 16px;
}
.calendar-day.selected {
background-color: #7986E7;
color: white;
}
/* 其它设置部分样式 */
.other-settings-container {
margin-top: 15px;
}
.element-row {
display: flex;
margin-bottom: 8px;
align-items: flex-start;
}
.element-box {
display: flex;
align-items: center;
flex-wrap: wrap;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px;
width: 100%;
}
.toggle-button {
background: none;
border: none;
cursor: pointer;
padding: 0;
margin-right: 5px;
font-size: 14px;
color: #666;
width: 20px;
display: flex;
justify-content: center;
}
.toggle-spacer {
width: 20px;
}
.element-name {
font-weight: 500;
margin-right: 15px;
padding: 5px 0;
color: #333;
min-width: 100px;
}
.element-value-container {
flex: 1;
display: flex;
}
.element-value-input {
flex: 1;
padding: 5px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.element-actions {
display: flex;
gap: 5px;
margin-left: auto;
}
.element-action-button {
padding: 3px 8px;
font-size: 12px;
border: none;
border-radius: 3px;
background-color: #7986E7;
color: white;
cursor: pointer;
}
.element-action-button:hover {
background-color: #6875D6;
}
.form-info {
margin-top: 15px;
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
}
.form-info-text {
color: #666;
margin: 0;
font-size: 14px;
}
/* 视觉编辑器相关样式 */
.visual-editor {
padding: 10px;
}
.section-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
border-bottom: 1px solid #e0e0e0;
padding-bottom: 8px;
}
.section {
margin-bottom: 20px;
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 16px;
}
.property-row {
display: flex;
margin-bottom: 12px;
align-items: center;
}
.property-label {
width: 150px;
font-weight: 500;
color: #555;
}
.property-input {
flex: 1;
padding: 8px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.property-input:focus {
border-color: #7986E7;
outline: none;
box-shadow: 0 0 0 2px rgba(121, 134, 231, 0.2);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.section-title-text {
font-size: 18px;
font-weight: bold;
color: #333;
}
.section-buttons {
display: flex;
gap: 8px;
}
.section-button {
background-color: #7986E7;
color: white;
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
.section-button:hover {
background-color: #6875D6;
}
`;
}

View File

@ -1,593 +0,0 @@
/**
* UI元素渲染模块
* @type {module}
*/
import { escapeHtml } from './utils.js';
import { updateElementValue } from './xml-handler.js';
/**
* 渲染基本信息部分
* @param {Element} container - 容器元素
* @param {Element} rootElement - XML根元素
* @param {Document} xmlDoc - XML文档对象
* @param {Function} showDateTimeDialog - 显示日期时间对话框的回调函数
* @param {Function} markEdited - 标记已编辑的回调函数
*/
export function renderBasicInfoSection(container, rootElement, xmlDoc, showDateTimeDialog, markEdited) {
const section = document.createElement('div');
section.className = 'section';
const sectionHeader = document.createElement('div');
sectionHeader.className = 'section-header';
const sectionTitle = document.createElement('div');
sectionTitle.className = 'section-title-text';
sectionTitle.textContent = '基本信息';
sectionHeader.appendChild(sectionTitle);
section.appendChild(sectionHeader);
// 基本信息字段及描述
const basicInfoFields = [
{ name: 'Name', label: '模型名称', placeholder: '请输入模型名称', tooltip: '模型的名称,用于在系统中标识此模型' },
{ name: 'Description', label: '模型描述', placeholder: '请输入模型描述', tooltip: '对模型功能和用途的简要描述' },
{ name: 'Author', label: '作者', placeholder: '请输入作者', tooltip: '模型创建者或维护者的姓名' },
{ name: 'Version', label: '版本', placeholder: '请输入版本号,如: 1.0.0', tooltip: '模型的版本号,建议使用语义化版本号格式' },
{ name: 'CreateTime', label: '创建时间', placeholder: '创建时间,如: 2023-12-31 12:00:00', tooltip: '模型首次创建的日期和时间', type: 'datetime' },
{ name: 'ChangeTime', label: '修改时间', placeholder: '修改时间,如: 2023-12-31 12:00:00', tooltip: '模型最后一次修改的日期和时间', type: 'datetime' }
];
// 创建两列布局容器
const twoColumnForm = document.createElement('div');
twoColumnForm.className = 'form-container two-column-form';
twoColumnForm.style.display = 'grid';
twoColumnForm.style.gridTemplateColumns = 'repeat(2, 1fr)';
twoColumnForm.style.gap = '16px';
// 处理基本信息字段
basicInfoFields.forEach(field => {
// 获取元素值
const element = rootElement.querySelector(field.name);
const value = element ? element.textContent : '';
// 创建属性行
const row = document.createElement('div');
row.className = 'property-row';
// 创建标签
const label = document.createElement('div');
label.className = 'property-label';
label.textContent = field.label;
label.style.width = '90px'; // 设置固定宽度使标签对齐
// 创建输入容器
const inputContainer = document.createElement('div');
inputContainer.className = 'input-container';
inputContainer.style.flex = '1';
inputContainer.style.display = 'flex';
// 创建输入框
const input = document.createElement('input');
input.type = 'text';
input.className = 'property-input';
input.dataset.field = field.name;
input.value = value;
input.placeholder = field.placeholder;
input.title = field.tooltip;
// 添加输入事件处理
input.addEventListener('change', () => {
updateElementValue(xmlDoc, field.name, input.value);
markEdited();
});
inputContainer.appendChild(input);
// 对于日期时间类型,添加日期选择按钮
if (field.type === 'datetime') {
input.style.flex = '1';
const datetimeButton = document.createElement('button');
datetimeButton.type = 'button';
datetimeButton.className = 'icon-button calendar-button';
datetimeButton.title = '选择日期和时间';
datetimeButton.addEventListener('click', () => {
showDateTimeDialog(input);
});
// 添加一个刷新按钮,快速设置为当前时间
const refreshButton = document.createElement('button');
refreshButton.type = 'button';
refreshButton.className = 'icon-button refresh-button-sm';
refreshButton.title = '设置为当前时间';
refreshButton.addEventListener('click', () => {
const now = new Date();
const dateStr = now.toISOString().split('T')[0];
const timeStr = now.toTimeString().split(' ')[0];
const datetimeStr = `${dateStr} ${timeStr}`;
input.value = datetimeStr;
updateElementValue(xmlDoc, field.name, datetimeStr);
markEdited(); // 确保触发修改标志
});
inputContainer.appendChild(datetimeButton);
inputContainer.appendChild(refreshButton);
}
// 组装行
row.appendChild(label);
row.appendChild(inputContainer);
twoColumnForm.appendChild(row);
});
section.appendChild(twoColumnForm);
container.appendChild(section);
}
/**
* 渲染高级设置部分
* @param {Element} container - 容器元素
* @param {Element} rootElement - XML根元素
* @param {Document} xmlDoc - XML文档对象
* @param {Function} renderCommandListTable - 渲染命令列表表格的函数
* @param {Function} markEdited - 标记已编辑的回调函数
*/
export function renderAdvancedSection(container, rootElement, xmlDoc, renderCommandListTable, markEdited) {
const section = document.createElement('div');
section.className = 'section';
const sectionHeader = document.createElement('div');
sectionHeader.className = 'section-header';
const sectionTitle = document.createElement('div');
sectionTitle.className = 'section-title-text';
sectionTitle.textContent = '运行设置';
sectionHeader.appendChild(sectionTitle);
section.appendChild(sectionHeader);
// 创建表单
const form = document.createElement('div');
form.className = 'form-container';
// 创建节点和优先级所在的行
const nodeAndPriorityRow = document.createElement('div');
nodeAndPriorityRow.className = 'property-row';
nodeAndPriorityRow.style.display = 'grid';
nodeAndPriorityRow.style.gridTemplateColumns = 'auto 1fr auto 1fr';
nodeAndPriorityRow.style.gap = '16px';
nodeAndPriorityRow.style.alignItems = 'center';
// ----- 节点部分 -----
// 创建节点标签
const nodeLabel = document.createElement('div');
nodeLabel.className = 'property-label';
nodeLabel.textContent = '运行节点';
nodeLabel.style.width = '90px'; // 与基本信息部分统一宽度
// 创建节点输入容器
const nodeContainer = document.createElement('div');
nodeContainer.className = 'input-container';
nodeContainer.style.display = 'flex';
nodeContainer.style.gap = '10px';
nodeContainer.style.width = 'calc(100% - 20px)'; // 控制整体宽度
// 获取当前节点值
const nodeElement = rootElement.querySelector('Node');
const nodeValue = nodeElement ? nodeElement.textContent : '0-0';
// 解析当前节点值,格式为"x-y"
let [freqGroup, nodeIndex] = nodeValue.split('-').map(Number);
if (isNaN(freqGroup)) freqGroup = 0;
if (isNaN(nodeIndex)) nodeIndex = 0;
// 创建运行频率下拉框
const freqGroupSelect = document.createElement('select');
freqGroupSelect.className = 'property-input custom-select';
freqGroupSelect.style.flex = '1';
freqGroupSelect.style.width = '120px';
freqGroupSelect.style.maxWidth = '120px';
freqGroupSelect.style.padding = '5px';
freqGroupSelect.style.appearance = 'none';
freqGroupSelect.style.backgroundImage = 'url("assets/icons/png/chevron-down_b.png")';
freqGroupSelect.style.backgroundRepeat = 'no-repeat';
freqGroupSelect.style.backgroundPosition = 'right 8px center';
freqGroupSelect.style.backgroundSize = '14px';
freqGroupSelect.style.paddingRight = '30px';
freqGroupSelect.title = '选择运行频率0=基频1=半频2=1/4频3=1/8频以此类推';
// 创建运行频率标签
const freqGroupLabel = document.createElement('div');
freqGroupLabel.className = 'property-sublabel';
freqGroupLabel.textContent = '运行频率';
freqGroupLabel.style.fontSize = '12px';
freqGroupLabel.style.marginBottom = '3px';
freqGroupLabel.title = '选择运行频率0=基频1=半频2=1/4频3=1/8频以此类推';
// 创建连接符
const connector = document.createElement('div');
connector.textContent = '——';
connector.style.display = 'flex';
connector.style.alignItems = 'flex-start';
connector.style.marginTop = '25px';
connector.style.fontWeight = 'bold';
connector.style.padding = '0 8px';
connector.style.fontSize = '16px';
// 创建节点索引标签
const nodeIndexLabel = document.createElement('div');
nodeIndexLabel.className = 'property-sublabel';
nodeIndexLabel.textContent = '节点号';
nodeIndexLabel.style.fontSize = '12px';
nodeIndexLabel.style.marginBottom = '3px';
nodeIndexLabel.title = '选择节点编号,范围根据运行频率决定';
// 创建运行频率容器
const freqGroupContainer = document.createElement('div');
freqGroupContainer.style.display = 'flex';
freqGroupContainer.style.flexDirection = 'column';
freqGroupContainer.style.width = '120px';
// 创建节点索引容器
const nodeIndexContainer = document.createElement('div');
nodeIndexContainer.style.display = 'flex';
nodeIndexContainer.style.flexDirection = 'column';
nodeIndexContainer.style.width = '80px';
// 设置节点行的工具提示
nodeContainer.title = '运行节点格式为"运行频率-节点编号",运行频率值越大,可用节点数越多';
// 添加运行频率选项
const freqOptions = [
{ value: 0, text: '基频' },
{ value: 1, text: '半频' },
{ value: 2, text: '1/4频' },
{ value: 3, text: '1/8频' },
{ value: 4, text: '1/16频' },
{ value: 5, text: '1/32频' }
];
freqOptions.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option.value;
optionElement.textContent = option.text;
if (parseInt(option.value) === freqGroup) {
optionElement.selected = true;
}
freqGroupSelect.appendChild(optionElement);
});
// 创建节点索引下拉框
const nodeIndexSelect = document.createElement('select');
nodeIndexSelect.className = 'property-input custom-select';
nodeIndexSelect.style.width = '80px';
nodeIndexSelect.style.padding = '5px';
nodeIndexSelect.style.appearance = 'none';
nodeIndexSelect.style.backgroundImage = 'url("assets/icons/png/chevron-down_b.png")';
nodeIndexSelect.style.backgroundRepeat = 'no-repeat';
nodeIndexSelect.style.backgroundPosition = 'right 8px center';
nodeIndexSelect.style.backgroundSize = '14px';
nodeIndexSelect.style.paddingRight = '30px';
nodeIndexSelect.title = '选择节点编号范围从0到2^(运行频率组-1';
// 更新节点索引下拉框的选项
const updateNodeOptions = (freqGroupValue) => {
nodeIndexSelect.innerHTML = ''; // 清空现有选项
// 根据运行频率计算节点数量
const nodeCount = Math.pow(2, freqGroupValue);
// 添加节点选项
for (let i = 0; i < nodeCount; i++) {
const optionElement = document.createElement('option');
optionElement.value = i;
optionElement.textContent = i;
if (i === nodeIndex) {
optionElement.selected = true;
}
nodeIndexSelect.appendChild(optionElement);
}
// 如果当前选中的节点索引超出了范围则重置为0
if (nodeIndex >= nodeCount) {
nodeIndex = 0;
nodeIndexSelect.value = nodeIndex;
// 更新XML值
updateElementValue(xmlDoc, 'Node', `${freqGroupValue}-${nodeIndex}`);
}
};
// 初始化节点选项
updateNodeOptions(freqGroup);
// 添加运行频率下拉框的变更事件
freqGroupSelect.addEventListener('change', () => {
const newFreqGroup = parseInt(freqGroupSelect.value);
updateNodeOptions(newFreqGroup);
// 更新XML值
updateElementValue(xmlDoc, 'Node', `${newFreqGroup}-${nodeIndexSelect.value}`);
markEdited();
});
// 添加节点索引下拉框的变更事件
nodeIndexSelect.addEventListener('change', () => {
// 更新XML值
updateElementValue(xmlDoc, 'Node', `${freqGroupSelect.value}-${nodeIndexSelect.value}`);
markEdited();
});
// 添加到容器
freqGroupContainer.appendChild(freqGroupLabel);
freqGroupContainer.appendChild(freqGroupSelect);
nodeIndexContainer.appendChild(nodeIndexLabel);
nodeIndexContainer.appendChild(nodeIndexSelect);
nodeContainer.appendChild(freqGroupContainer);
nodeContainer.appendChild(connector);
nodeContainer.appendChild(nodeIndexContainer);
// ----- 优先级部分 -----
// 获取优先级值
const priorityElement = rootElement.querySelector('Priority');
const priorityValue = priorityElement ? priorityElement.textContent : '';
// 创建优先级标签
const priorityLabel = document.createElement('div');
priorityLabel.className = 'property-label';
priorityLabel.textContent = '运行优先级';
priorityLabel.style.width = '90px'; // 与其他标签统一宽度
// 创建优先级输入容器
const priorityContainer = document.createElement('div');
priorityContainer.className = 'input-container';
priorityContainer.style.display = 'flex';
priorityContainer.style.width = '100%';
// 创建优先级输入框
const priorityInput = document.createElement('input');
priorityInput.type = 'text';
priorityInput.className = 'property-input';
priorityInput.dataset.field = 'Priority';
priorityInput.value = priorityValue;
priorityInput.placeholder = '运行优先级,如: 99';
priorityInput.title = '模型运行的优先级,数字越大优先级越高';
// 添加优先级输入事件处理
priorityInput.addEventListener('change', () => {
updateElementValue(xmlDoc, 'Priority', priorityInput.value);
markEdited();
});
priorityContainer.appendChild(priorityInput);
// 组装节点和优先级到同一行
nodeAndPriorityRow.appendChild(nodeLabel);
nodeAndPriorityRow.appendChild(nodeContainer);
nodeAndPriorityRow.appendChild(priorityLabel);
nodeAndPriorityRow.appendChild(priorityContainer);
form.appendChild(nodeAndPriorityRow);
// 添加数据包动态库路径字段
const mathLibElement = rootElement.querySelector('MathLib');
const mathLibValue = mathLibElement ? mathLibElement.textContent : '';
// 创建数据包动态库路径行
const mathLibRow = document.createElement('div');
mathLibRow.className = 'property-row';
// 创建标签
const mathLibLabel = document.createElement('div');
mathLibLabel.className = 'property-label';
mathLibLabel.textContent = '数据包动态库路径';
mathLibLabel.style.width = '90px'; // 与其他标签统一宽度
// 创建输入容器
const mathLibContainer = document.createElement('div');
mathLibContainer.className = 'input-container';
mathLibContainer.style.flex = '1';
mathLibContainer.style.display = 'flex';
mathLibContainer.style.marginLeft = '0'; // 确保左对齐
// 创建输入框
const mathLibInput = document.createElement('input');
mathLibInput.type = 'text';
mathLibInput.className = 'property-input';
mathLibInput.dataset.field = 'MathLib';
mathLibInput.value = mathLibValue;
mathLibInput.placeholder = '数据包动态库路径';
mathLibInput.title = '模型使用的数据包动态库的相对文件路径,相对于模型库目录';
// 添加输入事件处理
mathLibInput.addEventListener('change', () => {
updateElementValue(xmlDoc, 'MathLib', mathLibInput.value);
markEdited();
});
mathLibContainer.appendChild(mathLibInput);
// 组装行
mathLibRow.appendChild(mathLibLabel);
mathLibRow.appendChild(mathLibContainer);
form.appendChild(mathLibRow);
// 添加命令列表表格
renderCommandListTable(form, rootElement);
section.appendChild(form);
container.appendChild(section);
}
/**
* 递归渲染XML元素
* @param {Element} container - 容器元素
* @param {Element} parentElement - 父元素
* @param {string} parentPath - 父元素路径
* @param {number} level - 缩进级别
* @param {Function} isValidElementName - 验证元素名称的函数
* @param {Function} updateElementByPath - 更新元素值的函数
* @param {Function} deleteElement - 删除元素的函数
* @param {Function} addElement - 添加元素的函数
* @param {Function} showAddElementDialog - 显示添加元素对话框的函数
* @param {Function} showEditElementDialog - 显示编辑元素对话框的函数
*/
export function renderElements(container, parentElement, parentPath, level = 0,
isValidElementName, updateElementByPath, deleteElement, addElement,
showAddElementDialog, showEditElementDialog) {
// 过滤出需要在其他设置中显示的元素
const commonElements = ['Name', 'Description', 'Author', 'Version', 'CreateTime', 'ChangeTime',
'Node', 'Priority', 'MathLib', 'CommandList'];
Array.from(parentElement.children).forEach(element => {
// 跳过常见元素,这些在基本信息和高级设置中已经显示
if (commonElements.includes(element.nodeName)) {
return;
}
const elementName = element.nodeName;
const elementPath = `${parentPath}/${elementName}`;
// 创建元素行
const elementRow = document.createElement('div');
elementRow.className = 'element-row';
elementRow.dataset.path = elementPath;
// 添加缩进
const indent = level * 20; // 每级缩进20px
elementRow.style.paddingLeft = `${indent}px`;
// 创建元素框
const elementBox = document.createElement('div');
elementBox.className = 'element-box';
// 添加展开/折叠按钮(如果有子元素)
if (element.children.length > 0) {
const toggleButton = document.createElement('button');
toggleButton.className = 'toggle-button';
toggleButton.innerHTML = '▼'; // 默认展开
toggleButton.addEventListener('click', () => {
const childContainer = elementRow.nextElementSibling;
if (childContainer && childContainer.classList.contains('element-children')) {
if (childContainer.style.display === 'none') {
childContainer.style.display = 'block';
toggleButton.innerHTML = '▼';
} else {
childContainer.style.display = 'none';
toggleButton.innerHTML = '►';
}
}
});
elementBox.appendChild(toggleButton);
} else {
// 如果没有子元素,添加占位符
const toggleSpacer = document.createElement('div');
toggleSpacer.className = 'toggle-spacer';
elementBox.appendChild(toggleSpacer);
}
// 添加元素名称
const nameElement = document.createElement('div');
nameElement.className = 'element-name';
nameElement.textContent = elementName;
elementBox.appendChild(nameElement);
// 添加元素值/编辑控件
const valueContainer = document.createElement('div');
valueContainer.className = 'element-value-container';
// 获取元素值
let elementValue = '';
if (element.children.length === 0) {
elementValue = element.textContent;
}
// 创建输入框(如果是叶子节点)
if (element.children.length === 0) {
const valueInput = document.createElement('input');
valueInput.type = 'text';
valueInput.className = 'element-value-input';
valueInput.value = elementValue;
valueInput.placeholder = '输入值';
valueInput.dataset.path = elementPath;
// 添加改变事件
valueInput.addEventListener('change', (e) => {
const newValue = e.target.value;
updateElementByPath(elementPath, newValue);
});
valueContainer.appendChild(valueInput);
}
elementBox.appendChild(valueContainer);
// 添加操作按钮
const actionsContainer = document.createElement('div');
actionsContainer.className = 'element-actions';
// 删除按钮
const deleteButton = document.createElement('button');
deleteButton.className = 'element-action-button';
deleteButton.textContent = '删除';
deleteButton.style.backgroundColor = '#E77979';
deleteButton.addEventListener('click', () => {
deleteElement(element, elementPath);
});
actionsContainer.appendChild(deleteButton);
elementBox.appendChild(actionsContainer);
elementRow.appendChild(elementBox);
container.appendChild(elementRow);
// 如果有子元素,创建子元素容器
if (element.children.length > 0) {
const childContainer = document.createElement('div');
childContainer.className = 'element-children';
childContainer.dataset.parentPath = elementPath;
childContainer.style.paddingLeft = '20px'; // 子元素缩进
// 递归渲染子元素
renderElements(childContainer, element, elementPath, level + 1,
isValidElementName, updateElementByPath, deleteElement, addElement,
showAddElementDialog, showEditElementDialog);
container.appendChild(childContainer);
}
});
}
/**
* 渲染其它设置部分
* @param {Element} container - 容器元素
* @param {Element} rootElement - XML根元素
* @param {Function} renderElements - 递归渲染XML元素的函数
*/
export function renderOtherSettingsSection(container, rootElement, renderElementsFunc) {
const section = document.createElement('div');
section.className = 'settings-section other-settings';
const header = document.createElement('div');
header.className = 'section-header';
header.innerHTML = `
<h3>其他设置</h3>
<div class="section-note" style="font-size: 12px; color: #666; margin-top: 5px;">如需添加参数请手动编辑配置文件</div>
`;
section.appendChild(header);
// 创建元素容器
const elementsContainer = document.createElement('div');
elementsContainer.id = 'otherSettingsContainer';
elementsContainer.className = 'elements-container';
// 渲染所有元素
renderElementsFunc(elementsContainer, rootElement, '/' + rootElement.nodeName);
section.appendChild(elementsContainer);
container.appendChild(section);
}

View File

@ -1,103 +0,0 @@
/**
* XML与HTML工具函数模块
* @type {module}
*/
/**
* 格式化XML字符串
* @param {string} xml - 要格式化的XML字符串
* @returns {string} 格式化后的XML字符串
*/
export function formatXml(xml) {
if (!xml) return '';
try {
// 使用DOMParser解析XML并使用XMLSerializer重新序列化
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xml, 'application/xml');
// 检查解析错误
const parseError = xmlDoc.querySelector('parsererror');
if (parseError) {
return xml; // 如果有解析错误返回原始XML
}
// 创建格式化函数
const PADDING = ' '; // 两个空格作为缩进
const reg = /(>)(<)(\/*)/g;
let pad = 0;
// 替换XML中的>< 为 >\n<
let formatted = new XMLSerializer().serializeToString(xmlDoc)
.replace(reg, '$1\n$2$3')
.split('\n');
let result = '';
// 根据标签添加缩进
formatted.forEach(line => {
// 检查是否是结束标签
if (line.match(/<\//)) {
pad--;
}
result += Array(pad + 1).join(PADDING) + line + '\n';
// 检查是否是开始标签且不是自闭合标签
if (line.match(/<[^\/].*[^\/]>$/)) {
pad++;
}
});
return result.trim();
} catch (error) {
console.error('XML格式化错误:', error);
return xml; // 如果有错误返回原始XML
}
}
/**
* 转义HTML特殊字符
* @param {string} unsafe - 需要转义的字符串
* @returns {string} 转义后的安全字符串
*/
export function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
/**
* 验证元素名称是否符合XML规范
* @param {string} name - 要验证的元素名称
* @returns {boolean} 是否是有效的XML元素名称
*/
export function isValidElementName(name) {
// XML元素名称规则:
// 1. 不能以数字或标点符号开头
// 2. 不能以xml大写、小写、混合大小写开头
// 3. 不能包含空格
// 4. 只能包含字母、数字、下划线、连字符和点
if (!name) return false;
// 检查开头是否为字母或下划线
if (!/^[a-zA-Z_]/.test(name)) {
return false;
}
// 检查是否以xml开头不区分大小写
if (/^xml/i.test(name)) {
return false;
}
// 检查是否包含有效字符
if (!/^[a-zA-Z0-9_\-\.]+$/.test(name)) {
return false;
}
return true;
}

View File

@ -1,192 +0,0 @@
/**
* XML处理模块
* @type {module}
*/
/**
* 解析XML内容
* @param {string} content - XML内容字符串
* @returns {Object} 包含解析结果的对象 {xmlDoc, error, basicXml}
*/
export function parseXmlContent(content) {
const parser = new DOMParser();
let xmlDoc;
let error = null;
let basicXml = null;
try {
xmlDoc = parser.parseFromString(content, 'application/xml');
// 检查解析错误
const parseError = xmlDoc.querySelector('parsererror');
if (parseError) {
throw new Error('XML解析错误');
}
} catch (parseError) {
error = parseError;
// 如果内容为空或解析失败提示用户无法解析xml文件内容
if (!content.trim()) {
// 提示用户无法解析xml文件内容
alert('无法解析xml文件内容');
return { xmlDoc: null, error: '无法解析xml文件内容', basicXml: null };
}
}
return { xmlDoc, error, basicXml };
}
/**
* 确保命令列表格式正确
* @param {Document} xmlDoc - XML文档对象
* @returns {Element|null} - 命令列表元素
*/
export function ensureCommandListFormat(xmlDoc) {
if (!xmlDoc) return null;
const rootElement = xmlDoc.documentElement;
if (!rootElement) return null;
// 查找CommandList元素
let commandListElement = rootElement.querySelector('CommandList');
// 如果不存在,创建一个新的
if (!commandListElement) {
commandListElement = xmlDoc.createElement('CommandList');
rootElement.appendChild(commandListElement);
}
return commandListElement;
}
/**
* 从可视化编辑器更新XML
* @param {Document} xmlDoc - XML文档对象
* @returns {string} 更新后的XML字符串
*/
export function updateXmlFromVisualEditor(xmlDoc) {
if (!xmlDoc) return '';
const modifiedXml = new XMLSerializer().serializeToString(xmlDoc);
return modifiedXml;
}
/**
* 根据路径更新元素值
* @param {Document} xmlDoc - XML文档对象
* @param {string} path - 元素路径
* @param {string} value - 新值
* @returns {boolean} 更新是否成功
*/
export function updateElementByPath(xmlDoc, path, value) {
if (!xmlDoc) return false;
try {
// 分割路径
const pathParts = path.split('/').filter(p => p);
let currentElement = xmlDoc.documentElement;
// 跳过根元素
for (let i = 1; i < pathParts.length; i++) {
const part = pathParts[i];
// 查找子元素
let found = false;
for (let j = 0; j < currentElement.children.length; j++) {
if (currentElement.children[j].nodeName === part) {
currentElement = currentElement.children[j];
found = true;
break;
}
}
if (!found) {
throw new Error(`找不到元素: ${path}`);
}
}
// 更新元素值
currentElement.textContent = value;
return true;
} catch (error) {
console.error('更新元素失败:', error);
return false;
}
}
/**
* 更新元素值
* @param {Document} xmlDoc - XML文档对象
* @param {string} elementName - 元素名称
* @param {string} value - 元素值
* @returns {Element|null} 更新的元素
*/
export function updateElementValue(xmlDoc, elementName, value) {
if (!xmlDoc) return null;
const root = xmlDoc.documentElement;
let element = null;
// 查找元素
for (let i = 0; i < root.children.length; i++) {
if (root.children[i].nodeName === elementName) {
element = root.children[i];
break;
}
}
// 如果元素不存在则创建
if (!element) {
element = xmlDoc.createElement(elementName);
root.appendChild(element);
}
// 更新值
element.textContent = value;
return element;
}
/**
* 添加新元素
* @param {Document} xmlDoc - XML文档对象
* @param {Element} parentElement - 父元素
* @param {string} name - 元素名称
* @param {string} value - 元素值
* @returns {Element|null} 新创建的元素
*/
export function addElement(xmlDoc, parentElement, name, value) {
if (!xmlDoc || !parentElement) return null;
try {
// 创建新元素
const newElement = xmlDoc.createElement(name);
// 设置值
if (value) {
newElement.textContent = value;
}
// 添加到父元素
parentElement.appendChild(newElement);
return newElement;
} catch (error) {
console.error('添加元素失败:', error);
return null;
}
}
/**
* 删除元素
* @param {Element} element - 要删除的元素
* @returns {boolean} 删除是否成功
*/
export function deleteElement(element) {
if (!element || !element.parentNode) return false;
try {
element.parentNode.removeChild(element);
return true;
} catch (error) {
console.error('删除元素失败:', error);
return false;
}
}

View File

@ -74,6 +74,12 @@ class RunLog extends HTMLElement {
font-size: 0.9em;
}
.log-item-source {
color: #888;
font-size: 0.8em;
margin-top: 2px;
}
.log-item-toggle {
background-color: transparent;
color: #333;
@ -318,8 +324,13 @@ class RunLog extends HTMLElement {
}
return response.json();
})
.then(data => resolve(data.files || []))
.catch(error => reject(error));
.then(data => {
resolve(data.files || []);
})
.catch(error => {
console.error('读取目录请求失败:', error);
reject(error);
});
});
}

View File

@ -1,704 +0,0 @@
/**
* 服务配置组件 - 主入口文件
* @type {module}
*/
import { FileOperations } from './service-config/file-operations.js';
import { XmlUtils } from './service-config/xml-utils.js';
import { CommandDialog } from './service-config/command-dialog.js';
import { DateTimeDialog } from './service-config/date-time-dialog.js';
import { VisualEditor } from './service-config/visual-editor.js';
class ServiceConfig extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.selectedFile = null;
this.xmlContent = '';
this.xmlDoc = null;
this.isEdited = false;
this.serviceFiles = [];
this.isActive = true; // 添加活动状态标记
}
async connectedCallback() {
this.isActive = true;
this.render();
await this.loadServiceFiles();
this.setupEventListeners();
// 延迟调用renderComplete确保DOM已更新
setTimeout(() => {
this.renderComplete();
}, 100);
}
disconnectedCallback() {
this.isActive = false;
}
// 在组件完成渲染后恢复状态
renderComplete() {
// 在DOM渲染完成后更新文件选择器
setTimeout(() => {
// 如果有选中的文件,尝试恢复它
if (this.selectedFile && (this.xmlContent || this.xmlDoc)) {
const fileSelector = this.shadowRoot.getElementById('fileSelector');
if (fileSelector) {
// 确认文件在列表中
const fileExists = Array.from(fileSelector.options).some(opt => opt.value === this.selectedFile);
if (fileExists) {
// 设置选中的文件
fileSelector.value = this.selectedFile;
// 更新内容显示
const contentArea = this.shadowRoot.querySelector('#contentArea');
if (contentArea && this.xmlDoc) {
contentArea.innerHTML = `
<div class="editor-container">
<div class="editor-panel active" id="visualEditor"></div>
</div>
`;
const visualEditor = this.shadowRoot.querySelector('#visualEditor');
if (visualEditor) {
VisualEditor.render(visualEditor, this.xmlDoc, () => {
this.updateXmlFromEditor();
this.markEdited();
});
// 恢复编辑状态标记
if (this.isEdited) {
this.markEdited();
}
}
} else {
console.log('ServiceConfig: 无法找到内容区域或XML文档不存在');
}
} else {
// 文件不存在,清除状态
this.selectedFile = null;
this.xmlContent = '';
this.xmlDoc = null;
this.isEdited = false;
}
} else {
console.log('ServiceConfig: 未找到文件选择器');
}
}
}, 50); // 增加延迟确保DOM已完全加载
}
// 重新激活组件的方法(当标签页重新被选中时调用)
async reactivate() {
if (this.isActive) {
return;
}
this.isActive = true;
try {
// 先重新渲染一次UI以确保Shadow DOM结构完整
this.render();
// 加载文件列表
await this.loadServiceFiles();
// 设置事件监听器
this.setupEventListeners();
// 调用renderComplete来恢复状态
this.renderComplete();
} catch (error) {
console.error('ServiceConfig: 重新激活组件时出错:', error);
}
}
async loadServiceFiles() {
this.serviceFiles = await FileOperations.loadServiceFiles();
this.updateFileSelector();
}
updateFileSelector() {
const fileSelector = this.shadowRoot.getElementById('fileSelector');
if (!fileSelector) return;
// 清空现有选项
fileSelector.innerHTML = '<option value="">-- 选择服务配置文件 --</option>';
// 按修改时间排序,最新的在前面
const sortedFiles = [...this.serviceFiles].sort((a, b) =>
new Date(b.mtime) - new Date(a.mtime)
);
// 添加文件到选择器
sortedFiles.forEach(file => {
const option = document.createElement('option');
option.value = file.path;
option.textContent = file.name;
fileSelector.appendChild(option);
});
}
async loadFileContent(filePath) {
const result = await FileOperations.loadFileContent(filePath);
if (result.error) {
const configContent = this.shadowRoot.querySelector('.config-content');
configContent.innerHTML = `
<div class="error-message">
<h3>加载失败</h3>
<p>${result.error}</p>
${result.content ? `<pre>${XmlUtils.escapeHtml(result.content)}</pre>` : ''}
</div>
`;
return;
}
this.selectedFile = filePath;
this.xmlContent = result.content;
this.xmlDoc = result.xmlDoc;
this.updateFileContent();
this.resetEditState();
}
updateFileContent() {
const contentArea = this.shadowRoot.querySelector('#contentArea');
if (!this.xmlDoc || !this.selectedFile) {
contentArea.innerHTML = `<div class="no-file-selected">请选择一个服务配置文件查看内容</div>`;
return;
}
contentArea.innerHTML = `
<div class="editor-container">
<div class="editor-panel active" id="visualEditor"></div>
</div>
`;
const visualEditor = this.shadowRoot.querySelector('#visualEditor');
if (visualEditor) {
VisualEditor.render(visualEditor, this.xmlDoc, () => {
this.updateXmlFromEditor();
this.markEdited();
});
}
}
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;
appearance: none;
padding-right: 30px;
background-image: url('assets/icons/png/chevron-down_b.png');
background-repeat: no-repeat;
background-position: right 8px center;
background-size: 14px;
}
.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;
}
.action-button.secondary {
background-color: #f0f0f0;
color: #333;
}
.action-button.secondary:hover {
background-color: #e0e0e0;
}
.config-content {
padding: 0;
flex-grow: 1;
display: flex;
flex-direction: column;
overflow: auto;
}
.editor-container {
height: 100%;
display: flex;
flex-direction: column;
}
.editor-panel {
flex: 1;
height: 100%;
overflow: auto;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 12px;
background-color: #f9f9f9;
}
.editor-panel.active {
display: block;
}
.no-file-selected {
color: #888;
font-style: italic;
text-align: center;
margin-top: 80px;
}
.error-message {
color: #d32f2f;
background-color: #ffebee;
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
}
</style>
<div class="config-container">
<div class="config-header">
<div class="file-selector-header">
<div class="file-selector-label">服务配置文件</div>
<select id="fileSelector">
<option value="">-- 请选择服务配置文件 --</option>
</select>
<button class="refresh-button" id="refreshFiles" 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 id="contentArea">
<div class="no-file-selected">请选择一个服务配置文件查看内容</div>
</div>
</div>
</div>
`;
}
// 检查是否需要保存当前更改
async checkSaveNeeded() {
if (this.isEdited) {
// 使用自定义对话框替代简单的confirm
const dialogResult = await new Promise(resolve => {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'saveConfirmModal';
modal.innerHTML = `
<style>
.modal {
display: block;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 400px;
border-radius: 5px;
position: relative;
text-align: center;
}
.modal-title {
margin-top: 0;
color: #333;
font-size: 18px;
margin-bottom: 20px;
}
.modal-buttons {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.btn {
padding: 8px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
min-width: 80px;
}
.btn-save {
background-color: #7986E7;
color: white;
}
.btn-save:hover {
background-color: #6875D6;
}
.btn-dont-save {
background-color: #E77979;
color: white;
}
.btn-dont-save:hover {
background-color: #D66868;
}
.btn-cancel {
background-color: #f5f5f5;
color: #333;
}
.btn-cancel:hover {
background-color: #e0e0e0;
}
</style>
<div class="modal-content">
<h3 class="modal-title">当前文件有未保存的更改</h3>
<p>您想要保存这些更改吗</p>
<div class="modal-buttons">
<button class="btn btn-save" id="saveBtn">保存</button>
<button class="btn btn-dont-save" id="dontSaveBtn">不保存</button>
<button class="btn btn-cancel" id="cancelBtn">取消</button>
</div>
</div>
`;
document.body.appendChild(modal);
// 添加事件监听
const saveBtn = modal.querySelector('#saveBtn');
const dontSaveBtn = modal.querySelector('#dontSaveBtn');
const cancelBtn = modal.querySelector('#cancelBtn');
const closeModal = () => {
document.body.removeChild(modal);
};
saveBtn.addEventListener('click', () => {
closeModal();
resolve('save');
});
dontSaveBtn.addEventListener('click', () => {
closeModal();
resolve('dont-save');
});
cancelBtn.addEventListener('click', () => {
closeModal();
resolve('cancel');
});
// 点击模态窗口外部也取消
modal.addEventListener('click', (event) => {
if (event.target === modal) {
closeModal();
resolve('cancel');
}
});
});
// 根据对话框结果执行相应操作
if (dialogResult === 'save') {
try {
// 确保获取当前可视化编辑器的所有值更新到XML文档
this.updateXmlFromEditor();
// 保存文件
await FileOperations.saveFileContent(this.selectedFile, this.xmlContent);
this.resetEditState();
return true; // 继续执行后续操作
} catch (error) {
console.error('保存出错:', error);
alert('保存失败: ' + error.message);
return false; // 保存失败,不继续执行
}
} else if (dialogResult === 'dont-save') {
// 不保存,但继续执行后续操作
return true;
} else {
// 用户取消,不执行后续操作
return false;
}
}
// 没有编辑状态直接返回true允许继续操作
return true;
}
setupEventListeners() {
// 文件选择器
const fileSelector = this.shadowRoot.getElementById('fileSelector');
fileSelector.addEventListener('change', async (e) => {
const filePath = e.target.value;
if (filePath) {
// 检查是否需要保存当前文件
if (await this.checkSaveNeeded()) {
this.loadFileContent(filePath);
} else {
// 如果用户取消或保存失败,恢复选择器的值
fileSelector.value = this.selectedFile || '';
}
}
});
// 刷新文件列表
const refreshButton = this.shadowRoot.getElementById('refreshFiles');
refreshButton.addEventListener('click', async () => {
// 检查是否需要保存当前文件
if (await this.checkSaveNeeded()) {
refreshButton.classList.add('refreshing');
try {
// 重新加载文件列表
await this.loadServiceFiles();
// 清除编辑状态和内容
this.resetEditState();
// 清空内容区域
this.xmlContent = '';
this.xmlDoc = null;
const contentArea = this.shadowRoot.querySelector('#contentArea');
if (contentArea) {
contentArea.innerHTML = `<div class="no-file-selected">请选择一个服务配置文件查看内容</div>`;
}
// 清空文件选择
const fileSelector = this.shadowRoot.getElementById('fileSelector');
if (fileSelector) {
fileSelector.value = '';
}
// 重置选中的文件
this.selectedFile = null;
} catch (error) {
console.error('刷新文件列表失败:', error);
alert('刷新文件列表失败: ' + error.message);
} finally {
// 无论成功失败,都移除刷新动画
setTimeout(() => {
refreshButton.classList.remove('refreshing');
}, 500);
}
}
// 如果用户点击取消,什么也不做,保留当前内容
});
// 新建配置
const newButton = this.shadowRoot.getElementById('newConfig');
newButton.addEventListener('click', async () => {
// 检查是否需要保存当前文件
if (await this.checkSaveNeeded()) {
CommandDialog.showNewConfigDialog(async (fileName) => {
try {
const result = await FileOperations.createNewConfig(fileName);
await this.loadServiceFiles();
await this.loadFileContent(result.path);
} catch (error) {
alert('创建失败: ' + error.message);
}
});
}
});
// 保存配置
const saveButton = this.shadowRoot.getElementById('saveConfig');
saveButton.addEventListener('click', async () => {
if (!this.selectedFile) {
alert('请先选择一个文件或创建新文件');
return;
}
try {
// 确保获取当前可视化编辑器的所有值更新到XML文档
this.updateXmlFromEditor();
// 保存文件
await FileOperations.saveFileContent(this.selectedFile, this.xmlContent);
this.resetEditState();
alert('保存成功');
} catch (error) {
console.error('保存出错:', error);
alert('保存失败: ' + error.message);
}
});
// 另存为
const saveAsButton = this.shadowRoot.getElementById('saveAsConfig');
saveAsButton.addEventListener('click', () => {
if (!this.xmlContent) {
alert('没有内容可保存');
return;
}
CommandDialog.showSaveAsDialog(this.selectedFile, async (fileName) => {
try {
const result = await FileOperations.saveFileAs(fileName, this.xmlContent, this.selectedFile);
if (!result.success) {
if (result.data && result.data.code === 'FILE_EXISTS') {
if (confirm('文件已存在,是否覆盖?')) {
const retryResult = await FileOperations.saveFileAs(fileName, this.xmlContent, this.selectedFile, true);
if (retryResult.success) {
this.selectedFile = retryResult.data.path;
this.resetEditState();
await this.loadServiceFiles();
} else {
alert('保存失败: ' + retryResult.error);
}
}
} else {
alert('保存失败: ' + result.error);
}
} else {
this.selectedFile = result.data.path;
this.resetEditState();
await this.loadServiceFiles();
}
} catch (error) {
alert('保存失败: ' + error.message);
}
});
});
}
// 标记编辑状态
markEdited() {
this.isEdited = true;
// 更新保存按钮样式
const saveButton = this.shadowRoot.querySelector('.save-button');
if (saveButton) {
saveButton.classList.add('modified');
saveButton.title = '文件已修改,请保存';
}
}
// 重置编辑状态
resetEditState() {
this.isEdited = false;
// 更新保存按钮样式
const saveButton = this.shadowRoot.querySelector('.save-button');
if (saveButton) {
saveButton.classList.remove('modified');
saveButton.title = '保存';
}
}
// 更新XML
updateXmlFromEditor() {
if (!this.xmlDoc) return;
// 从可视化编辑器中获取所有输入字段的值并更新到XML文档
// 这部分逻辑已经在VisualEditor中处理这里只需序列化XML即可
// 重新序列化XML内容
const serializer = new XMLSerializer();
this.xmlContent = XmlUtils.formatXml(serializer.serializeToString(this.xmlDoc));
}
}
customElements.define('service-config', ServiceConfig);

View File

@ -1,700 +0,0 @@
/**
* 命令对话框模块处理添加和编辑命令功能
* @type {module}
*/
import { XmlUtils } from './xml-utils.js';
export class CommandDialog {
/**
* 显示添加命令对话框
* @param {HTMLElement} parentElement - 父元素用于挂载对话框
* @param {Element} commandListElement - XML 命令列表元素
* @param {Document} xmlDoc - XML文档
* @param {Function} onSuccess - 成功回调函数
*/
static showAddDialog(parentElement, commandListElement, xmlDoc, onSuccess) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'addCommandModal';
modal.innerHTML = `
<style>
.modal {
display: block;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 20px;
border: 1px solid #888;
width: 50%;
max-width: 500px;
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.modal-title {
font-weight: bold;
font-size: 18px;
}
.close {
color: #aaa;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: black;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 15px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
}
.form-control {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
}
.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:hover {
background-color: #6875D6;
}
.action-button.secondary {
background-color: #f0f0f0;
color: #333;
}
.action-button.secondary:hover {
background-color: #e0e0e0;
}
</style>
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">添加指令</div>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="commandName">指令名称</label>
<input type="text" id="commandName" class="form-control" placeholder="输入指令名称" />
</div>
<div class="form-group">
<label for="commandCall">指令调用</label>
<input type="text" id="commandCall" class="form-control" placeholder="输入指令调用" />
</div>
<div class="form-group">
<label for="commandDescription">指令描述</label>
<input type="text" id="commandDescription" class="form-control" placeholder="输入指令描述" />
</div>
</div>
<div class="modal-footer">
<button id="cancelAddCommand" class="action-button secondary">取消</button>
<button id="confirmAddCommand" class="action-button">添加</button>
</div>
</div>
`;
// 添加到父元素
parentElement.appendChild(modal);
// 事件处理
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelAddCommand');
const confirmBtn = modal.querySelector('#confirmAddCommand');
const nameInput = modal.querySelector('#commandName');
const callInput = modal.querySelector('#commandCall');
const descInput = modal.querySelector('#commandDescription');
const closeModal = () => {
if (parentElement.contains(modal)) {
parentElement.removeChild(modal);
}
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', () => {
const name = nameInput.value.trim();
const call = callInput.value.trim();
const description = descInput.value.trim();
if (!name) {
alert('指令名称不能为空');
return;
}
try {
// 创建新命令元素
const newCommand = xmlDoc.createElement('Command');
newCommand.setAttribute('Name', name);
newCommand.setAttribute('Call', call);
newCommand.setAttribute('Description', description || `${name}描述`);
// 添加到命令列表
commandListElement.appendChild(newCommand);
// 调用成功回调
if (onSuccess) {
onSuccess();
}
closeModal();
} catch (error) {
console.error('添加指令失败:', error);
alert('添加指令失败: ' + error.message);
}
});
// 点击模态窗口外部关闭
modal.addEventListener('click', (event) => {
if (event.target === modal) {
closeModal();
}
});
}
/**
* 显示编辑命令对话框
* @param {HTMLElement} parentElement - 父元素用于挂载对话框
* @param {Element} commandElement - 要编辑的命令元素
* @param {number} index - 命令索引
* @param {Function} onSuccess - 成功回调函数
*/
static showEditDialog(parentElement, commandElement, index, onSuccess) {
// 获取当前命令属性
const name = commandElement.getAttribute('Name') || '';
const call = commandElement.getAttribute('Call') || '';
const description = commandElement.getAttribute('Description') || '';
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'editCommandModal';
modal.innerHTML = `
<style>
.modal {
display: block;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 20px;
border: 1px solid #888;
width: 50%;
max-width: 500px;
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.modal-title {
font-weight: bold;
font-size: 18px;
}
.close {
color: #aaa;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: black;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 15px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
}
.form-control {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
}
.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:hover {
background-color: #6875D6;
}
.action-button.secondary {
background-color: #f0f0f0;
color: #333;
}
.action-button.secondary:hover {
background-color: #e0e0e0;
}
</style>
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">编辑指令</div>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="commandName">指令名称</label>
<input type="text" id="commandName" class="form-control" value="${XmlUtils.escapeHtml(name)}" placeholder="输入指令名称" />
</div>
<div class="form-group">
<label for="commandCall">指令调用</label>
<input type="text" id="commandCall" class="form-control" value="${XmlUtils.escapeHtml(call)}" placeholder="输入指令调用" />
</div>
<div class="form-group">
<label for="commandDescription">指令描述</label>
<input type="text" id="commandDescription" class="form-control" value="${XmlUtils.escapeHtml(description)}" placeholder="输入指令描述" />
</div>
</div>
<div class="modal-footer">
<button id="cancelEditCommand" class="action-button secondary">取消</button>
<button id="confirmEditCommand" class="action-button">保存</button>
</div>
</div>
`;
// 添加到父元素
parentElement.appendChild(modal);
// 事件处理
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelEditCommand');
const confirmBtn = modal.querySelector('#confirmEditCommand');
const nameInput = modal.querySelector('#commandName');
const callInput = modal.querySelector('#commandCall');
const descInput = modal.querySelector('#commandDescription');
const closeModal = () => {
if (parentElement.contains(modal)) {
parentElement.removeChild(modal);
}
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', () => {
const newName = nameInput.value.trim();
const newCall = callInput.value.trim();
const newDescription = descInput.value.trim();
if (!newName) {
alert('指令名称不能为空');
return;
}
try {
// 更新命令属性
commandElement.setAttribute('Name', newName);
commandElement.setAttribute('Call', newCall);
commandElement.setAttribute('Description', newDescription || `${newName}描述`);
// 调用成功回调
if (onSuccess) {
onSuccess();
}
closeModal();
} catch (error) {
console.error('编辑指令失败:', error);
alert('编辑指令失败: ' + error.message);
}
});
// 点击模态窗口外部关闭
modal.addEventListener('click', (event) => {
if (event.target === modal) {
closeModal();
}
});
}
/**
* 显示新建配置对话框
* @param {Function} onConfirm - 确认回调函数参数为文件名
*/
static showNewConfigDialog(onConfirm) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'newConfigModal';
modal.innerHTML = `
<style>
.modal {
display: block;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 500px;
border-radius: 5px;
position: relative;
}
.modal-title {
margin-top: 0;
color: #333;
}
.close {
color: #aaa;
position: absolute;
right: 20px;
top: 10px;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: #000;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ddd;
border-radius: 4px;
}
.form-footer {
text-align: right;
margin-top: 20px;
}
.btn {
padding: 8px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-left: 10px;
}
.btn-cancel {
background-color: #f5f5f5;
color: #333;
}
.btn-confirm {
background-color: #7986E7;
color: white;
}
</style>
<div class="modal-content">
<span class="close">&times;</span>
<h3 class="modal-title">新建服务配置文件</h3>
<div class="form-group">
<label for="newFileName">文件名</label>
<input type="text" id="newFileName" placeholder="请输入文件名" />
</div>
<div class="form-footer">
<button class="btn btn-cancel" id="cancelBtn">取消</button>
<button class="btn btn-confirm" id="confirmBtn">创建</button>
</div>
</div>
`;
document.body.appendChild(modal);
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelBtn');
const confirmBtn = modal.querySelector('#confirmBtn');
const fileNameInput = modal.querySelector('#newFileName');
const closeModal = () => {
document.body.removeChild(modal);
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', async () => {
let fileName = fileNameInput.value.trim();
if (!fileName) {
alert('请输入文件名');
return;
}
// 确保文件名以.scfg结尾
if (!fileName.toLowerCase().endsWith('.scfg')) {
fileName += '.scfg';
}
closeModal();
// 调用确认回调
if (onConfirm) {
onConfirm(fileName);
}
});
// 处理回车键
fileNameInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
confirmBtn.click();
}
});
// 打开后聚焦输入框
setTimeout(() => {
fileNameInput.focus();
}, 100);
}
/**
* 显示另存为对话框
* @param {string} currentFileName - 当前文件名
* @param {Function} onConfirm - 确认回调函数参数为文件名
*/
static showSaveAsDialog(currentFileName, onConfirm) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'saveAsModal';
modal.innerHTML = `
<style>
.modal {
display: block;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 500px;
border-radius: 5px;
position: relative;
}
.modal-title {
margin-top: 0;
color: #333;
}
.close {
color: #aaa;
position: absolute;
right: 20px;
top: 10px;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: #000;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ddd;
border-radius: 4px;
}
.form-footer {
text-align: right;
margin-top: 20px;
}
.btn {
padding: 8px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-left: 10px;
}
.btn-cancel {
background-color: #f5f5f5;
color: #333;
}
.btn-confirm {
background-color: #7986E7;
color: white;
}
</style>
<div class="modal-content">
<span class="close">&times;</span>
<h3 class="modal-title">另存为新文件</h3>
<div class="form-group">
<label for="saveAsFileName">文件名</label>
<input type="text" id="saveAsFileName" placeholder="请输入文件名" />
</div>
<div class="form-footer">
<button class="btn btn-cancel" id="cancelBtn">取消</button>
<button class="btn btn-confirm" id="confirmBtn">保存</button>
</div>
</div>
`;
document.body.appendChild(modal);
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelBtn');
const confirmBtn = modal.querySelector('#confirmBtn');
const fileNameInput = modal.querySelector('#saveAsFileName');
// 填充当前文件名
if (currentFileName) {
const fileName = currentFileName.split('/').pop();
fileNameInput.value = fileName;
}
const closeModal = () => {
document.body.removeChild(modal);
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', () => {
let fileName = fileNameInput.value.trim();
if (!fileName) {
alert('请输入文件名');
return;
}
// 确保文件名以.scfg结尾
if (!fileName.toLowerCase().endsWith('.scfg')) {
fileName += '.scfg';
}
closeModal();
// 调用确认回调
if (onConfirm) {
onConfirm(fileName);
}
});
// 处理回车键
fileNameInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
confirmBtn.click();
}
});
// 打开后聚焦输入框
setTimeout(() => {
fileNameInput.focus();
fileNameInput.select(); // 全选文本,方便修改
}, 100);
}
}

View File

@ -1,349 +0,0 @@
/**
* 日期时间选择对话框模块
* @type {module}
*/
export class DateTimeDialog {
/**
* 显示日期时间选择对话框
* @param {HTMLElement} parentElement - 父元素用于挂载对话框
* @param {HTMLInputElement} inputElement - 日期时间输入框
* @param {Function} onConfirm - 确认回调函数
*/
static show(parentElement, inputElement, onConfirm) {
// 解析当前日期和时间
let currentDate = new Date();
let currentTime = '00:00:00';
if (inputElement.value) {
try {
const parts = inputElement.value.split(' ');
if (parts.length >= 1) {
const dateParts = parts[0].split('-');
if (dateParts.length === 3) {
const year = parseInt(dateParts[0]);
const month = parseInt(dateParts[1]) - 1; // 月份从0开始
const day = parseInt(dateParts[2]);
currentDate = new Date(year, month, day);
}
}
if (parts.length >= 2) {
currentTime = parts[1];
}
} catch (error) {
console.error('解析日期时间失败:', error);
currentDate = new Date();
}
}
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'dateTimeModal';
// 获取年、月、日
const year = currentDate.getFullYear();
const month = currentDate.getMonth(); // 0-11
const day = currentDate.getDate();
// 生成日历
const daysInMonth = new Date(year, month + 1, 0).getDate();
const firstDay = new Date(year, month, 1).getDay(); // 0-60表示周日
// 生成月份选项
let monthOptions = '';
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
monthNames.forEach((name, idx) => {
monthOptions += `<option value="${idx}" ${idx === month ? 'selected' : ''}>${name}</option>`;
});
// 生成年份选项
let yearOptions = '';
const currentYear = new Date().getFullYear();
for (let y = currentYear - 10; y <= currentYear + 10; y++) {
yearOptions += `<option value="${y}" ${y === year ? 'selected' : ''}>${y}</option>`;
}
// 生成日历表格
let calendarRows = '';
let dayCount = 1;
// 添加表头
calendarRows += '<tr>';
['日', '一', '二', '三', '四', '五', '六'].forEach(dayName => {
calendarRows += `<th>${dayName}</th>`;
});
calendarRows += '</tr>';
// 计算行数
const totalCells = firstDay + daysInMonth;
const rowCount = Math.ceil(totalCells / 7);
// 添加日期行
for (let i = 0; i < rowCount; i++) {
calendarRows += '<tr>';
for (let j = 0; j < 7; j++) {
if ((i === 0 && j < firstDay) || dayCount > daysInMonth) {
calendarRows += '<td></td>';
} else {
const isToday = dayCount === day;
calendarRows += `<td class="calendar-day ${isToday ? 'selected' : ''}" data-day="${dayCount}">${dayCount}</td>`;
dayCount++;
}
}
calendarRows += '</tr>';
}
modal.innerHTML = `
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">选择日期和时间</div>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<div class="calendar-container">
<div class="calendar-header">
<select id="calendarMonth" class="form-control" style="max-width: 120px;">${monthOptions}</select>
<select id="calendarYear" class="form-control" style="max-width: 100px;">${yearOptions}</select>
</div>
<table class="calendar-table">
<thead>
${calendarRows}
</thead>
</table>
</div>
<div class="time-container" style="margin-top: 15px;">
<label for="timeInput">时间:</label>
<input type="time" id="timeInput" class="form-control" step="1" value="${currentTime}">
</div>
</div>
<div class="modal-footer">
<button id="cancelDateTime" class="action-button secondary">取消</button>
<button id="confirmDateTime" class="action-button">确定</button>
</div>
</div>
<style>
.modal {
display: block;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 20px;
border: 1px solid #888;
width: 50%;
max-width: 500px;
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.modal-title {
font-weight: bold;
font-size: 18px;
}
.close {
color: #aaa;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: black;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 15px;
}
.calendar-container {
width: 100%;
}
.calendar-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.calendar-header select {
padding: 5px;
border: 1px solid #ddd;
border-radius: 4px;
}
.calendar-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
.calendar-table th,
.calendar-table td {
padding: 8px;
text-align: center;
border: 1px solid #ddd;
}
.calendar-day {
cursor: pointer;
}
.calendar-day:hover {
background-color: #f0f0f0;
}
.calendar-day.selected {
background-color: #7986E7;
color: white;
}
.form-control {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
}
.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:hover {
background-color: #6875D6;
}
.action-button.secondary {
background-color: #f0f0f0;
color: #333;
}
.action-button.secondary:hover {
background-color: #e0e0e0;
}
</style>
`;
// 添加到父元素
parentElement.appendChild(modal);
// 事件处理
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelDateTime');
const confirmBtn = modal.querySelector('#confirmDateTime');
const calendarMonth = modal.querySelector('#calendarMonth');
const calendarYear = modal.querySelector('#calendarYear');
const calendarDays = modal.querySelectorAll('.calendar-day');
const timeInput = modal.querySelector('#timeInput');
// 选择日期事件
calendarDays.forEach(cell => {
cell.addEventListener('click', (e) => {
// 移除所有选中状态
calendarDays.forEach(day => day.classList.remove('selected'));
// 添加新选中状态
e.target.classList.add('selected');
});
});
// 月份和年份变化时重新渲染日历
const updateCalendar = () => {
const selectedYear = parseInt(calendarYear.value);
const selectedMonth = parseInt(calendarMonth.value);
// 关闭当前对话框
closeModal();
// 更新日期参数并重新显示对话框
currentDate = new Date(selectedYear, selectedMonth, 1);
DateTimeDialog.show(parentElement, inputElement, onConfirm);
};
calendarMonth.addEventListener('change', updateCalendar);
calendarYear.addEventListener('change', updateCalendar);
const closeModal = () => {
if (parentElement.contains(modal)) {
parentElement.removeChild(modal);
}
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', () => {
// 获取选中的日期
const selectedDay = modal.querySelector('.calendar-day.selected');
if (!selectedDay) {
closeModal();
return;
}
const day = selectedDay.dataset.day;
const month = parseInt(calendarMonth.value) + 1; // 月份从0开始显示时+1
const year = calendarYear.value;
// 格式化日期
const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
// 获取时间
const time = timeInput.value || '00:00:00';
// 更新输入框值
const dateTimeValue = `${formattedDate} ${time}`;
// 调用确认回调
if (onConfirm) {
onConfirm(dateTimeValue);
}
closeModal();
});
// 点击模态窗口外部关闭
modal.addEventListener('click', (event) => {
if (event.target === modal) {
closeModal();
}
});
}
/**
* 获取当前格式化的日期时间
* @returns {string} 格式化的日期时间字符串
*/
static getCurrentDateTime() {
const now = new Date();
const dateStr = now.toISOString().split('T')[0];
const timeStr = now.toTimeString().split(' ')[0];
return `${dateStr} ${timeStr}`;
}
}

View File

@ -1,182 +0,0 @@
/**
* 文件操作相关的功能类
* @type {module}
*/
import { XmlUtils } from './xml-utils.js';
export class FileOperations {
/**
* 加载服务文件列表
*/
static async loadServiceFiles() {
try {
const response = await fetch('/api/service-files');
if (!response.ok) {
throw new Error(`服务器响应错误: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('加载服务文件失败:', error);
// 延迟3秒后重试
return new Promise((resolve) => {
setTimeout(async () => {
try {
console.log('尝试重新加载服务文件列表...');
const retryResponse = await fetch('/api/service-files');
if (retryResponse.ok) {
resolve(await retryResponse.json());
} else {
console.error('重试加载服务文件失败');
resolve([]);
}
} catch (retryError) {
console.error('重试加载服务文件失败:', retryError);
resolve([]);
}
}, 3000);
});
}
}
/**
* 加载文件内容
*/
static async loadFileContent(filePath) {
try {
const response = await fetch(`/api/service-file-content?path=${encodeURIComponent(filePath)}`);
if (!response.ok) {
throw new Error(`服务器响应错误: ${response.status}`);
}
const content = await response.text();
// 解析XML内容
const parser = new DOMParser();
let xmlDoc;
try {
xmlDoc = parser.parseFromString(content, 'application/xml');
// 检查解析错误
const parseError = xmlDoc.querySelector('parsererror');
if (parseError) {
throw new Error('XML解析错误');
}
// 特殊处理CommandList元素确保命令以属性方式存储
XmlUtils.ensureCommandListFormat(xmlDoc);
return {
content,
xmlDoc,
error: null
};
} catch (parseError) {
console.error('XML解析错误:', parseError);
}
} catch (error) {
console.error('加载文件内容失败:', error);
return {
content: null,
xmlDoc: null,
error: error.message
};
}
}
/**
* 保存文件内容
*/
static async saveFileContent(filePath, content) {
try {
const response = await fetch('/api/save-service-file', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
path: filePath,
content: content
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `保存失败: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('保存文件失败:', error);
throw error;
}
}
/**
* 创建新配置
*/
static async createNewConfig(fileName) {
try {
const response = await fetch('/api/create-service-file', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ fileName })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `创建失败: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('创建新配置文件失败:', error);
throw error;
}
}
/**
* 另存为新文件
*/
static async saveFileAs(fileName, content, currentFile, overwrite = false) {
try {
const response = await fetch('/api/save-service-as', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileName,
content,
currentFile,
overwrite
})
});
if (!response.ok) {
const errorData = await response.json();
return {
success: false,
data: errorData,
error: errorData.error || `保存失败: ${response.status}`
};
}
const result = await response.json();
return {
success: true,
data: result,
error: null
};
} catch (error) {
console.error('另存为失败:', error);
return {
success: false,
data: null,
error: error.message
};
}
}
}

View File

@ -1,722 +0,0 @@
/**
* 可视化编辑器模块
* @type {module}
*/
import { XmlUtils } from './xml-utils.js';
import { DateTimeDialog } from './date-time-dialog.js';
import { CommandDialog } from './command-dialog.js';
export class VisualEditor {
/**
* 渲染可视化编辑器
* @param {HTMLElement} container - 容器元素
* @param {Document} xmlDoc - XML文档
* @param {Function} onEdit - 编辑回调函数
* @returns {boolean} 是否渲染成功
*/
static render(container, xmlDoc, onEdit) {
if (!xmlDoc || !container) return false;
const style = document.createElement('style');
style.textContent = `
.section {
margin-bottom: 20px;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 10px;
background-color: white;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #e0e0e0;
}
.section-title {
font-weight: bold;
color: #555;
}
.property-table {
width: 100%;
border-collapse: collapse;
}
.property-table th, .property-table td {
text-align: left;
padding: 8px;
border-bottom: 1px solid #e0e0e0;
}
.property-table th {
background-color: #f5f5f5;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.form-control {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ddd;
border-radius: 4px;
}
.inline-form {
display: flex;
gap: 16px;
}
.inline-form .form-group {
flex: 1;
}
.two-column-form {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.input-container {
display: flex;
align-items: center;
gap: 8px;
}
.input-container .icon-button {
min-width: 28px;
height: 28px;
background-size: 16px;
background-position: center;
background-repeat: no-repeat;
border: none;
cursor: pointer;
background-color: transparent;
opacity: 0.7;
}
.input-container .icon-button:hover {
opacity: 1;
}
.calendar-button {
background-image: url('assets/icons/png/calendar_b.png');
}
.command-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
.command-table th, .command-table td {
padding: 8px;
text-align: left;
border: 1px solid #ddd;
}
.command-table th {
background-color: #f5f5f5;
}
.element-box {
display: flex;
flex-direction: column;
padding: 10px;
border: 1px solid #e0e0e0;
border-radius: 4px;
margin-bottom: 10px;
background-color: #f9f9f9;
}
.element-header {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.element-name {
font-weight: 500;
color: #444;
flex: 1;
}
.element-value {
width: 100%;
}
.element-value input {
max-width: 100%;
}
.element-input-row {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
.element-input-row .form-control {
flex: 1;
}
.nested-elements {
padding-left: 20px;
margin-top: 5px;
border-left: 1px dashed #ccc;
}
`;
const visualEditor = document.createElement('div');
visualEditor.className = 'visual-editor';
// 获取根元素
const rootElement = xmlDoc.documentElement;
// 只处理Service根元素
if (rootElement.nodeName === 'Service') {
// 添加基本信息部分
visualEditor.appendChild(this.createBasicInfoSection(xmlDoc, onEdit));
// 添加命令列表部分
visualEditor.appendChild(this.createCommandsSection(xmlDoc, onEdit));
// 添加其他设置部分
visualEditor.appendChild(this.createOtherSettingsSection(xmlDoc, onEdit));
container.appendChild(style);
container.appendChild(visualEditor);
return true;
} else {
// 不是Service根元素显示错误信息
visualEditor.innerHTML = `<div class="error-message">
无法编辑XML文档的根元素不是Service
请确保XML文档的根元素是Service
</div>`;
container.appendChild(style);
container.appendChild(visualEditor);
return false;
}
}
/**
* 创建基本信息部分
*/
static createBasicInfoSection(xmlDoc, onEdit) {
const rootElement = xmlDoc.documentElement;
// 创建基本信息部分
const basicInfoSection = document.createElement('div');
basicInfoSection.className = 'section';
const basicInfoHeader = document.createElement('div');
basicInfoHeader.className = 'section-header';
basicInfoHeader.innerHTML = '<div class="section-title">基本信息</div>';
basicInfoSection.appendChild(basicInfoHeader);
// 获取基本信息数据
const nameElement = rootElement.querySelector('Name') || xmlDoc.createElement('Name');
const descElement = rootElement.querySelector('Description') || xmlDoc.createElement('Description');
const authorElement = rootElement.querySelector('Author') || xmlDoc.createElement('Author');
const versionElement = rootElement.querySelector('Version') || xmlDoc.createElement('Version');
const createTimeElement = rootElement.querySelector('CreateTime') || xmlDoc.createElement('CreateTime');
const changeTimeElement = rootElement.querySelector('ChangeTime') || xmlDoc.createElement('ChangeTime');
// 确保元素存在于XML中
if (!rootElement.querySelector('Name')) rootElement.appendChild(nameElement);
if (!rootElement.querySelector('Description')) rootElement.appendChild(descElement);
if (!rootElement.querySelector('Author')) rootElement.appendChild(authorElement);
if (!rootElement.querySelector('Version')) rootElement.appendChild(versionElement);
if (!rootElement.querySelector('CreateTime')) {
createTimeElement.textContent = DateTimeDialog.getCurrentDateTime();
rootElement.appendChild(createTimeElement);
}
if (!rootElement.querySelector('ChangeTime')) {
changeTimeElement.textContent = DateTimeDialog.getCurrentDateTime();
rootElement.appendChild(changeTimeElement);
}
// 创建基本信息表单
const basicInfoForm = document.createElement('div');
basicInfoForm.className = 'form-container';
// 服务名称和描述在同一行
const nameDescContainer = document.createElement('div');
nameDescContainer.className = 'inline-form';
nameDescContainer.innerHTML = `
<div class="form-group">
<label for="serviceName">服务名称</label>
<input type="text" id="serviceName" class="form-control" placeholder="请输入服务名称" value="${XmlUtils.escapeHtml(nameElement.textContent || '')}" />
</div>
<div class="form-group">
<label for="serviceDesc">服务描述</label>
<input type="text" id="serviceDesc" class="form-control" placeholder="请输入服务描述" value="${XmlUtils.escapeHtml(descElement.textContent || '')}" />
</div>
`;
basicInfoForm.appendChild(nameDescContainer);
// 其他信息以两列布局
const otherInfoContainer = document.createElement('div');
otherInfoContainer.className = 'two-column-form';
otherInfoContainer.style.marginTop = '15px';
otherInfoContainer.innerHTML = `
<div class="form-group">
<label for="serviceAuthor">作者</label>
<input type="text" id="serviceAuthor" class="form-control" placeholder="请输入作者" value="${XmlUtils.escapeHtml(authorElement.textContent || '')}" />
</div>
<div class="form-group">
<label for="serviceVersion">版本</label>
<input type="text" id="serviceVersion" class="form-control" placeholder="请输入版本号" value="${XmlUtils.escapeHtml(versionElement.textContent || '1.0.0')}" />
</div>
<div class="form-group">
<label for="serviceCreateTime">创建时间</label>
<div class="input-container">
<input type="text" id="serviceCreateTime" class="form-control" placeholder="YYYY-MM-DD HH:MM:SS" value="${XmlUtils.escapeHtml(createTimeElement.textContent || '')}" />
<button class="icon-button calendar-button" title="选择日期和时间" id="createTimeBtn"></button>
<button class="icon-button refresh-button-sm" title="设置为当前时间" id="refreshCreateTimeBtn" style="background-image: url('assets/icons/png/refresh_b.png'); min-width: 28px; height: 28px; background-size: 16px; background-position: center; background-repeat: no-repeat; border: none; cursor: pointer; background-color: transparent; opacity: 0.7;"></button>
</div>
</div>
<div class="form-group">
<label for="serviceChangeTime">修改时间</label>
<div class="input-container">
<input type="text" id="serviceChangeTime" class="form-control" placeholder="YYYY-MM-DD HH:MM:SS" value="${XmlUtils.escapeHtml(changeTimeElement.textContent || '')}" />
<button class="icon-button calendar-button" title="选择日期和时间" id="changeTimeBtn"></button>
<button class="icon-button refresh-button-sm" title="设置为当前时间" id="refreshChangeTimeBtn" style="background-image: url('assets/icons/png/refresh_b.png'); min-width: 28px; height: 28px; background-size: 16px; background-position: center; background-repeat: no-repeat; border: none; cursor: pointer; background-color: transparent; opacity: 0.7;"></button>
</div>
</div>
`;
basicInfoForm.appendChild(otherInfoContainer);
basicInfoSection.appendChild(basicInfoForm);
// 添加事件监听
basicInfoSection.addEventListener('change', (e) => {
if (e.target.id === 'serviceName') {
nameElement.textContent = e.target.value;
if (onEdit) onEdit();
} else if (e.target.id === 'serviceDesc') {
descElement.textContent = e.target.value;
if (onEdit) onEdit();
} else if (e.target.id === 'serviceAuthor') {
authorElement.textContent = e.target.value;
if (onEdit) onEdit();
} else if (e.target.id === 'serviceVersion') {
versionElement.textContent = e.target.value;
if (onEdit) onEdit();
} else if (e.target.id === 'serviceCreateTime') {
createTimeElement.textContent = e.target.value;
if (onEdit) onEdit();
} else if (e.target.id === 'serviceChangeTime') {
changeTimeElement.textContent = e.target.value;
if (onEdit) onEdit();
}
});
// 添加日期时间选择器事件
basicInfoSection.querySelector('#createTimeBtn').addEventListener('click', () => {
const input = basicInfoSection.querySelector('#serviceCreateTime');
DateTimeDialog.show(basicInfoSection.ownerDocument.body, input, (newValue) => {
input.value = newValue;
createTimeElement.textContent = newValue;
if (onEdit) onEdit();
});
});
basicInfoSection.querySelector('#changeTimeBtn').addEventListener('click', () => {
const input = basicInfoSection.querySelector('#serviceChangeTime');
DateTimeDialog.show(basicInfoSection.ownerDocument.body, input, (newValue) => {
input.value = newValue;
changeTimeElement.textContent = newValue;
if (onEdit) onEdit();
});
});
// 添加刷新按钮事件
basicInfoSection.querySelector('#refreshCreateTimeBtn').addEventListener('click', () => {
const input = basicInfoSection.querySelector('#serviceCreateTime');
const datetimeStr = DateTimeDialog.getCurrentDateTime();
input.value = datetimeStr;
createTimeElement.textContent = datetimeStr;
if (onEdit) onEdit();
});
basicInfoSection.querySelector('#refreshChangeTimeBtn').addEventListener('click', () => {
const input = basicInfoSection.querySelector('#serviceChangeTime');
const datetimeStr = DateTimeDialog.getCurrentDateTime();
input.value = datetimeStr;
changeTimeElement.textContent = datetimeStr;
if (onEdit) onEdit();
});
return basicInfoSection;
}
/**
* 创建命令列表部分
*/
static createCommandsSection(xmlDoc, onEdit) {
const rootElement = xmlDoc.documentElement;
// 创建命令列表部分
const commandsSection = document.createElement('div');
commandsSection.className = 'section';
const commandsHeader = document.createElement('div');
commandsHeader.className = 'section-header';
commandsHeader.innerHTML = `
<div class="section-title">指令列表</div>
<button id="addCommandBtn" class="action-button">添加指令</button>
`;
commandsSection.appendChild(commandsHeader);
// 获取或创建命令列表
let commandListElement = rootElement.querySelector('CommandList');
if (!commandListElement) {
commandListElement = xmlDoc.createElement('CommandList');
rootElement.appendChild(commandListElement);
}
// 创建命令表格
const commandsTable = document.createElement('table');
commandsTable.className = 'command-table';
commandsTable.innerHTML = `
<thead>
<tr>
<th style="width: 20%;">名称</th>
<th style="width: 25%;">调用</th>
<th style="width: 40%;">描述</th>
<th style="width: 15%;">操作</th>
</tr>
</thead>
<tbody id="commandsTableBody">
</tbody>
`;
// 创建表格内容
const commandsTableBody = commandsTable.querySelector('#commandsTableBody');
// 更新命令表格的函数
const updateCommandTable = () => {
// 清空表格
commandsTableBody.innerHTML = '';
// 获取所有命令
const commandElements = commandListElement.querySelectorAll('Command');
if (commandElements.length === 0) {
// 如果没有命令,显示空行
const emptyRow = document.createElement('tr');
emptyRow.innerHTML = '<td colspan="4" style="text-align: center;">暂无指令</td>';
commandsTableBody.appendChild(emptyRow);
} else {
// 添加所有命令到表格
commandElements.forEach((command, index) => {
const name = command.getAttribute('Name') || '';
const call = command.getAttribute('Call') || '';
const description = command.getAttribute('Description') || '';
const row = document.createElement('tr');
row.dataset.index = index;
row.innerHTML = `
<td>${XmlUtils.escapeHtml(name)}</td>
<td>${XmlUtils.escapeHtml(call)}</td>
<td>${XmlUtils.escapeHtml(description)}</td>
<td>
<button class="action-button edit-btn" data-index="${index}">编辑</button>
<button class="action-button delete-btn" data-index="${index}" style="background-color: #E77979;">删除</button>
</td>
`;
commandsTableBody.appendChild(row);
});
// 添加编辑和删除事件
commandsTableBody.querySelectorAll('.edit-btn').forEach(btn => {
btn.addEventListener('click', () => {
const index = parseInt(btn.dataset.index);
const command = commandElements[index];
CommandDialog.showEditDialog(
commandsSection.ownerDocument.body,
command,
index,
() => {
updateCommandTable();
if (onEdit) onEdit();
}
);
});
});
commandsTableBody.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', () => {
const index = parseInt(btn.dataset.index);
const command = commandElements[index];
if (confirm(`确定要删除指令 ${command.getAttribute('Name')} 吗?`)) {
commandListElement.removeChild(command);
updateCommandTable();
if (onEdit) onEdit();
}
});
});
}
};
// 初始化表格
updateCommandTable();
// 添加"添加指令"按钮事件
commandsSection.querySelector('#addCommandBtn').addEventListener('click', () => {
CommandDialog.showAddDialog(
commandsSection.ownerDocument.body,
commandListElement,
xmlDoc,
() => {
updateCommandTable();
if (onEdit) onEdit();
}
);
});
// 添加表格
commandsSection.appendChild(commandsTable);
return commandsSection;
}
/**
* 创建其他设置部分
*/
static createOtherSettingsSection(xmlDoc, onEdit) {
const rootElement = xmlDoc.documentElement;
// 创建其他设置部分
const otherSettingsSection = document.createElement('div');
otherSettingsSection.className = 'section';
const otherSettingsHeader = document.createElement('div');
otherSettingsHeader.className = 'section-header';
otherSettingsHeader.innerHTML = `
<div class="section-title">其他设置</div>
<span class="settings-hint" style="font-size: 12px; color: #777; font-style: italic;">如需添加参数请手动编辑配置文件</span>
`;
otherSettingsSection.appendChild(otherSettingsHeader);
// 创建其他设置内容
const otherSettingsContent = document.createElement('div');
otherSettingsContent.className = 'other-settings-container';
// 递归处理元素及其子元素
const processElement = (parentElement, containerElement, level = 0) => {
// 筛选节点元素,排除文本节点
const childElements = Array.from(parentElement.childNodes)
.filter(node => node.nodeType === 1); // 只处理元素节点
if (childElements.length === 0) return;
childElements.forEach(element => {
// 排除已处理的基本信息元素和命令列表
const nodeName = element.nodeName;
if (['Name', 'Description', 'Author', 'Version', 'CreateTime',
'ChangeTime', 'CommandList'].includes(nodeName)) {
return;
}
// 创建元素框
const elementBox = document.createElement('div');
elementBox.className = 'element-box';
elementBox.id = `element-${getElementPath(element).replace(/\./g, '-')}`;
if (level > 0) {
elementBox.style.marginLeft = `${level * 10}px`;
}
// 创建元素头部(包含名称)
const elementHeader = document.createElement('div');
elementHeader.className = 'element-header';
// 创建元素名称
const elementName = document.createElement('div');
elementName.className = 'element-name';
elementName.textContent = nodeName;
elementHeader.appendChild(elementName);
// 将元素头部添加到元素框
elementBox.appendChild(elementHeader);
// 创建元素内容容器
const elementContent = document.createElement('div');
elementContent.className = 'element-value';
// 处理不同类型的元素内容
const hasChildElements = Array.from(element.childNodes)
.some(node => node.nodeType === 1);
if (hasChildElements) {
// 如果有子元素,为它们创建一个嵌套容器
const childContainer = document.createElement('div');
childContainer.className = 'nested-elements';
// 递归处理子元素
processElement(element, childContainer, level + 1);
elementContent.appendChild(childContainer);
// 为父元素添加删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.className = 'action-button';
deleteBtn.textContent = '删除';
deleteBtn.style.backgroundColor = '#e77979';
deleteBtn.style.marginTop = '8px';
deleteBtn.addEventListener('click', () => {
if (confirm(`确定要删除 ${nodeName} 及其所有子元素吗?`)) {
try {
// 从DOM和XML中删除元素
parentElement.removeChild(element);
// 移除元素框
if (elementBox.parentNode) {
elementBox.parentNode.removeChild(elementBox);
}
// 触发编辑回调
if (onEdit) onEdit();
// 如果没有其他元素了,显示"没有其他设置项"提示
const otherSettingsContainer = otherSettingsContent;
if (otherSettingsContainer && otherSettingsContainer.children.length === 0) {
const noElementsMsg = document.createElement('div');
noElementsMsg.style.padding = '10px';
noElementsMsg.style.color = '#666';
noElementsMsg.textContent = '没有其他设置项';
otherSettingsContainer.appendChild(noElementsMsg);
}
} catch (error) {
console.error('删除元素时出错:', error);
alert(`删除元素失败: ${error.message}`);
}
}
});
elementContent.appendChild(deleteBtn);
} else {
// 如果没有子元素,创建可编辑的文本输入
const elementValue = element.textContent || '';
const inputRow = document.createElement('div');
inputRow.className = 'element-input-row';
const input = document.createElement('input');
input.type = 'text';
input.className = 'form-control';
input.value = elementValue;
input.dataset.elementPath = getElementPath(element);
// 为输入框添加事件监听
input.addEventListener('change', (e) => {
element.textContent = e.target.value;
if (onEdit) onEdit();
});
inputRow.appendChild(input);
// 添加删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.className = 'action-button';
deleteBtn.textContent = '删除';
deleteBtn.style.backgroundColor = '#e77979';
deleteBtn.addEventListener('click', () => {
if (confirm(`确定要删除 ${nodeName} 吗?`)) {
try {
// 从DOM和XML中删除元素
parentElement.removeChild(element);
// 移除元素框
if (elementBox.parentNode) {
elementBox.parentNode.removeChild(elementBox);
}
// 触发编辑回调
if (onEdit) onEdit();
// 如果没有其他元素了,显示"没有其他设置项"提示
const otherSettingsContainer = otherSettingsContent;
if (otherSettingsContainer && otherSettingsContainer.querySelectorAll('.element-box').length === 0) {
const noElementsMsg = document.createElement('div');
noElementsMsg.style.padding = '10px';
noElementsMsg.style.color = '#666';
noElementsMsg.textContent = '没有其他设置项';
otherSettingsContainer.appendChild(noElementsMsg);
}
} catch (error) {
console.error('删除元素时出错:', error);
alert(`删除元素失败: ${error.message}`);
}
}
});
inputRow.appendChild(deleteBtn);
elementContent.appendChild(inputRow);
}
// 将元素内容添加到元素框
elementBox.appendChild(elementContent);
// 将元素框添加到容器
containerElement.appendChild(elementBox);
});
};
// 获取元素的路径,用于唯一标识
const getElementPath = (element) => {
const path = [];
let current = element;
while (current && current !== xmlDoc) {
const nodeName = current.nodeName;
path.unshift(nodeName);
current = current.parentNode;
}
return path.join('.');
};
// 处理其他元素(排除标准元素)
const standardElements = ['Name', 'Description', 'Author', 'Version',
'CreateTime', 'ChangeTime', 'CommandList'];
const otherElements = Array.from(rootElement.childNodes)
.filter(node => node.nodeType === 1 && !standardElements.includes(node.nodeName));
if (otherElements.length > 0) {
// 处理所有其他元素
processElement(rootElement, otherSettingsContent);
} else {
// 如果没有其他元素,显示提示
const noElementsMsg = document.createElement('div');
noElementsMsg.style.padding = '10px';
noElementsMsg.style.color = '#666';
noElementsMsg.textContent = '没有其他设置项';
otherSettingsContent.appendChild(noElementsMsg);
}
otherSettingsSection.appendChild(otherSettingsContent);
return otherSettingsSection;
}
}

View File

@ -1,131 +0,0 @@
/**
* XML工具类包含XML处理相关的方法
* @type {module}
*/
export class XmlUtils {
/**
* HTML转义
*/
static escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
/**
* 确保CommandList元素符合特定格式
*/
static ensureCommandListFormat(xmlDoc) {
// 如果没有rootElement返回null
if (!xmlDoc) return null;
const rootElement = xmlDoc.documentElement;
// 查找或创建CommandList元素
let commandListElement = rootElement.querySelector('CommandList');
if (!commandListElement) {
commandListElement = xmlDoc.createElement('CommandList');
rootElement.appendChild(commandListElement);
}
return commandListElement;
}
/**
* 格式化XML
*/
static formatXml(xml) {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xml, 'application/xml');
return XmlUtils.formatNode(xmlDoc, 0);
}
/**
* 递归处理XML节点
*/
static formatNode(node, level) {
if (!node) return '';
let result = '';
const indentStr = ' '.repeat(level);
// 处理不同类型的节点
switch (node.nodeType) {
case Node.DOCUMENT_NODE:
// 文档节点,处理子节点
result = '<?xml version="1.0" encoding="UTF-8"?>\n';
for (let i = 0; i < node.childNodes.length; i++) {
result += XmlUtils.formatNode(node.childNodes[i], level);
}
break;
case Node.ELEMENT_NODE:
// 元素节点
result += indentStr + '<' + node.nodeName;
// 处理属性
for (let i = 0; i < node.attributes.length; i++) {
const attr = node.attributes[i];
result += ' ' + attr.name + '="' + attr.value + '"';
}
if (node.childNodes.length === 0) {
// 没有子节点,使用自闭合标签
result += ' />\n';
} else {
result += '>';
let hasElement = false;
let textContent = '';
// 检查是否只有文本内容
for (let i = 0; i < node.childNodes.length; i++) {
if (node.childNodes[i].nodeType === Node.ELEMENT_NODE) {
hasElement = true;
break;
} else if (node.childNodes[i].nodeType === Node.TEXT_NODE) {
textContent += node.childNodes[i].nodeValue;
}
}
if (hasElement) {
// 有元素子节点,换行并增加缩进
result += '\n';
for (let i = 0; i < node.childNodes.length; i++) {
result += XmlUtils.formatNode(node.childNodes[i], level + 1);
}
result += indentStr;
} else {
// 只有文本内容,不换行
result += textContent.trim();
}
result += '</' + node.nodeName + '>\n';
}
break;
case Node.TEXT_NODE:
// 文本节点,去除空白文本节点
const text = node.nodeValue.trim();
if (text) {
result += text;
}
break;
case Node.COMMENT_NODE:
// 注释节点
result += indentStr + '<!--' + node.nodeValue + '-->\n';
break;
default:
// 其他类型节点
break;
}
return result;
}
}

View File

@ -218,23 +218,12 @@ class SubToolbar extends HTMLElement {
Q&A
</div>
</div>
<!-- 配置子菜单 -->
<div class="sub-menu" data-parent="config">
<!-- 开发子菜单 -->
<div class="sub-menu" data-parent="develop">
<div class="sub-item" data-icon="chip">
<img src="assets/icons/png/chip.png" alt="构型配置" class="icon">
构型配置
</div>
<div class="sub-item" data-icon="cube">
<img src="assets/icons/png/cube.png" alt="模型配置" class="icon">
模型配置
</div>
<div class="sub-item" data-icon="settings">
<img src="assets/icons/png/settings.png" alt="服务配置" class="icon">
服务配置
</div>
</div>
<!-- 开发子菜单 -->
<div class="sub-menu" data-parent="develop">
<div class="sub-item" data-icon="plug">
<img src="assets/icons/png/plug.png" alt="接口配置" class="icon">
接口配置

View File

@ -20,8 +20,6 @@
<script src="components/help-component.js"></script>
<script src="components/system-log.js"></script>
<script src="components/configuration-config.js"></script>
<script src="components/model-config.js" type="module"></script>
<script src="components/service-config.js" type="module"></script>
<script src="components/interface-config.js" type="module"></script>
<script src="components/run-test.js"></script>
<script src="components/run-simulation.js"></script>
@ -531,20 +529,6 @@
return;
}
// 处理模型配置标签页
if (title === '模型配置') {
const id = 'model-config';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理服务配置标签页
if (title === '服务配置') {
const id = 'service-config';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理接口配置标签页
if (title === '接口配置') {
const id = 'interface-config';
@ -637,8 +621,6 @@
'帮助': 'help',
'Q&A': 'question',
'构型配置': 'chip',
'模型配置': 'cube',
'服务配置': 'settings',
'接口配置': 'plug',
'运行测试': 'flask',
'运行仿真': 'rocket',

View File

@ -13,15 +13,13 @@ router.get('/readdir', async (req, res) => {
}
// 获取实际的文件系统路径
const actualPath = getActualLogPath(dirPath);
const actualPath = await getActualLogPath(dirPath);
// 安全检查
if (!actualPath) {
return res.status(403).json({ error: '无权访问该目录' });
}
//console.log('读取目录:', actualPath);
// 检查目录是否存在
try {
const stats = await fs.stat(actualPath);
@ -34,7 +32,6 @@ router.get('/readdir', async (req, res) => {
try {
// 确保XNCore下有log目录
await fs.mkdir(actualPath, { recursive: true });
console.log('创建日志目录:', actualPath);
} catch (mkdirError) {
console.error('创建日志目录失败:', mkdirError);
return res.status(500).json({ error: '创建日志目录失败' });
@ -74,15 +71,13 @@ router.get('/stat', async (req, res) => {
}
// 获取实际的文件系统路径
const actualPath = getActualLogPath(filePath);
const actualPath = await getActualLogPath(filePath);
// 安全检查
if (!actualPath) {
return res.status(403).json({ error: '无权访问该文件' });
}
//console.log('获取文件状态:', actualPath);
// 获取文件状态
const stats = await fs.stat(actualPath);
@ -121,7 +116,7 @@ router.get('/readFile', async (req, res) => {
}
// 获取实际的文件系统路径
const actualPath = getActualLogPath(filePath);
const actualPath = await getActualLogPath(filePath);
// 安全检查
if (!actualPath) {
@ -133,8 +128,6 @@ router.get('/readFile', async (req, res) => {
return res.status(403).json({ error: '只能访问日志文件' });
}
//console.log('读取文件:', actualPath);
// 获取文件状态
const stats = await fs.stat(actualPath);

View File

@ -1,339 +0,0 @@
const express = require('express');
const router = express.Router();
const path = require('path');
const fs = require('fs').promises;
const { getModelPath, isPathSafe, ensureDirectoryExists, validateXml } = require('../utils/file-utils');
// 获取模型文件列表
router.get('/model-files', async (req, res) => {
try {
const modelDir = getModelPath();
// 检查XNCore是否设置
if (!modelDir) {
console.error('XNCore环境变量未设置无法获取模型目录');
return res.status(500).json({
error: 'XNCore环境变量未设置',
message: '无法获取模型目录'
});
}
// 确保目录存在
await ensureDirectoryExists(modelDir);
// 读取目录内容
const files = await fs.readdir(modelDir);
// 过滤出.mcfg文件
const modelFiles = [];
for (const file of files) {
const ext = path.extname(file).toLowerCase();
if (ext === '.mcfg') {
const filePath = path.join(modelDir, file);
const stats = await fs.stat(filePath);
if (stats.isFile()) {
modelFiles.push({
name: file,
path: filePath,
size: stats.size,
mtime: stats.mtime
});
}
}
}
res.json(modelFiles);
} catch (error) {
console.error('获取模型文件列表失败:', error);
res.status(500).json({ error: '获取模型文件列表失败', message: error.message });
}
});
// 获取模型文件内容
router.get('/model-file-content', async (req, res) => {
try {
const filePath = req.query.path;
if (!filePath) {
return res.status(400).json({ error: '缺少路径参数' });
}
// 安全检查 - 确保只能访问Models目录下的文件
const modelDir = getModelPath();
if (!isPathSafe(filePath, modelDir)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 检查文件后缀,只允许.mcfg
const ext = path.extname(filePath).toLowerCase();
if (ext !== '.mcfg') {
return res.status(403).json({ error: '只能访问模型文件(.mcfg)' });
}
// 检查文件是否存在
try {
await fs.access(filePath);
} catch (error) {
if (error.code === 'ENOENT') {
// 如果文件不存在,尝试创建空文件
try {
// 从filePath中获取模型名称不含扩展名
const modelName = path.basename(filePath, '.mcfg');
// 获取当前日期时间
const now = new Date();
const dateStr = now.toISOString().split('T')[0]; // 如 2023-04-15
const timeStr = now.toTimeString().split(' ')[0]; // 如 10:30:45
const basicXml = `<?xml version="1.0" encoding="UTF-8"?>
<Model>
<n>${modelName}</n>
<Description>模型描述</Description>
<Author>用户</Author>
<Version>1.0.0</Version>
<CreateTime>${dateStr} ${timeStr}</CreateTime>
<ChangeTime>${dateStr} ${timeStr}</ChangeTime>
<Node>0-0</Node>
<Priority>99</Priority>
<MathLib></MathLib>
<CommandList></CommandList>
</Model>`;
await fs.writeFile(filePath, basicXml, 'utf-8');
// 返回基本内容
return res.send(basicXml);
} catch (writeError) {
console.error('创建模型文件失败:', writeError);
return res.status(500).json({ error: '创建文件失败', message: writeError.message });
}
} else {
throw error;
}
}
// 获取文件状态
const stats = await fs.stat(filePath);
// 检查是否为文件
if (!stats.isFile()) {
return res.status(400).json({ error: '请求的路径不是文件' });
}
// 大文件检查
const MAX_FILE_SIZE = 1024 * 1024; // 1MB限制
if (stats.size > MAX_FILE_SIZE) {
return res.status(413).json({ error: '文件过大,无法读取' });
}
// 读取文件内容
const content = await fs.readFile(filePath, 'utf-8');
// 设置响应头
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
// 返回文件内容
res.send(content);
} catch (error) {
console.error('读取模型文件内容失败:', error);
res.status(500).json({ error: '读取模型文件内容失败', message: error.message });
}
});
// 保存模型文件内容
router.post('/save-model-file', async (req, res) => {
try {
const { path: filePath, content } = req.body;
if (!filePath || content === undefined) {
return res.status(400).json({ error: '缺少必要参数' });
}
// 安全检查 - 确保只能访问Models目录下的文件
const modelDir = getModelPath();
if (!isPathSafe(filePath, modelDir)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 检查文件后缀,只允许.mcfg
const ext = path.extname(filePath).toLowerCase();
if (ext !== '.mcfg') {
return res.status(403).json({ error: '只能修改模型文件(.mcfg)' });
}
// 确保目录存在
await ensureDirectoryExists(path.dirname(filePath));
// 检查XML格式是否有效
if (content.trim()) { // 只有当内容不为空时才检查
const validation = await validateXml(content);
if (validation !== true) {
return res.status(400).json(validation);
}
}
// 写入文件内容
await fs.writeFile(filePath, content, 'utf-8');
res.json({ success: true, message: '文件保存成功' });
} catch (error) {
console.error('保存模型文件内容失败:', error);
res.status(500).json({ error: '保存模型文件内容失败', message: error.message });
}
});
// 创建新模型配置文件
router.post('/create-model-file', async (req, res) => {
try {
const { fileName } = req.body;
if (!fileName) {
return res.status(400).json({ error: '缺少文件名参数' });
}
// 获取Models目录
const modelDir = getModelPath();
if (!modelDir) {
return res.status(500).json({ error: 'XNCore环境变量未设置' });
}
// 设置文件路径
const filePath = path.join(modelDir, fileName);
// 检查文件后缀,只允许.mcfg
const ext = path.extname(fileName).toLowerCase();
if (ext !== '.mcfg') {
return res.status(403).json({ error: '只能创建模型文件(.mcfg)' });
}
// 确保目录存在
await ensureDirectoryExists(modelDir);
// 检查文件是否已存在
try {
await fs.access(filePath);
// 文件已存在
return res.status(409).json({ error: '文件已存在' });
} catch (error) {
// 文件不存在,可以继续创建
if (error.code !== 'ENOENT') {
throw error;
}
}
// 从fileName中获取模型名称不含扩展名
const modelName = path.basename(fileName, '.mcfg');
// 获取当前日期时间
const now = new Date();
const dateStr = now.toISOString().split('T')[0]; // 如 2023-04-15
const timeStr = now.toTimeString().split(' ')[0]; // 如 10:30:45
// 创建基本的XML模板与XNAerodynamics.mcfg结构一致
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
<Model>
<n>${modelName}</n>
<Description>模型描述</Description>
<Author>用户</Author>
<Version>1.0.0</Version>
<CreateTime>${dateStr} ${timeStr}</CreateTime>
<ChangeTime>${dateStr} ${timeStr}</ChangeTime>
<Node>0-0</Node>
<Priority>99</Priority>
<MathLib></MathLib>
<CommandList></CommandList>
</Model>`;
// 写入文件内容
await fs.writeFile(filePath, xmlContent, 'utf-8');
res.status(201).json({
success: true,
message: '文件创建成功',
path: filePath,
content: xmlContent
});
} catch (error) {
console.error('创建模型文件失败:', error);
res.status(500).json({ error: '创建模型文件失败', message: error.message });
}
});
// 另存为模型文件
router.post('/save-model-as', async (req, res) => {
try {
const { fileName, content, currentFile, overwrite } = req.body;
if (!fileName || content === undefined) {
return res.status(400).json({ error: '缺少必要参数' });
}
// 获取Models目录
const modelDir = getModelPath();
if (!modelDir) {
return res.status(500).json({ error: 'XNCore环境变量未设置' });
}
// 设置目标文件路径
const targetPath = path.join(modelDir, fileName);
// 检查文件后缀,只允许.mcfg
const ext = path.extname(fileName).toLowerCase();
if (ext !== '.mcfg') {
return res.status(403).json({ error: '只能保存模型文件(.mcfg)' });
}
// 确保目录存在
await ensureDirectoryExists(path.dirname(targetPath));
// 如果路径与当前文件相同,直接保存
if (currentFile && targetPath === currentFile) {
await fs.writeFile(targetPath, content, 'utf-8');
return res.json({
success: true,
message: '文件保存成功',
path: targetPath
});
}
// 检查文件是否已存在
try {
await fs.access(targetPath);
// 文件已存在,但没有覆盖标记
if (!overwrite) {
return res.status(409).json({
error: '文件已存在',
code: 'FILE_EXISTS'
});
}
} catch (error) {
// 文件不存在,可以直接创建
if (error.code !== 'ENOENT') {
throw error;
}
}
// 检查XML格式是否有效
if (content.trim()) {
const validation = await validateXml(content);
if (validation !== true) {
return res.status(400).json(validation);
}
}
// 写入文件内容
await fs.writeFile(targetPath, content, 'utf-8');
res.json({
success: true,
message: '文件保存成功',
path: targetPath
});
} catch (error) {
console.error('另存为模型文件失败:', error);
res.status(500).json({ error: '另存为模型文件失败', message: error.message });
}
});
module.exports = router;

View File

@ -43,7 +43,7 @@ router.get('/chapter-models/:chapterId', (req, res) => {
router.get('/model-versions/:className', (req, res) => {
try {
const className = req.params.className;
const { planeName, confID } = req.query;
const { planeName } = req.query;
if (!className) {
return res.status(400).json({ error: '模型类名不能为空' });
@ -51,11 +51,8 @@ router.get('/model-versions/:className', (req, res) => {
if (!planeName) {
return res.status(400).json({ error: '飞机名称不能为空' });
}
if (!confID) {
return res.status(400).json({ error: '配置ID不能为空' });
}
const versions = getModelVersionsByClassName(className, planeName, confID);
const versions = getModelVersionsByClassName(className, planeName);
res.json(versions);
} catch (error) {
console.error(`获取模型版本失败: ${error.message}`);

View File

@ -1,335 +0,0 @@
const express = require('express');
const router = express.Router();
const path = require('path');
const fs = require('fs').promises;
const { getServicePath, isPathSafe, ensureDirectoryExists, validateXml } = require('../utils/file-utils');
// 获取服务文件列表
router.get('/service-files', async (req, res) => {
try {
const serviceDir = getServicePath();
// 检查XNCore是否设置
if (!serviceDir) {
console.error('XNCore环境变量未设置无法获取服务目录');
return res.status(500).json({
error: 'XNCore环境变量未设置',
message: '无法获取服务目录'
});
}
// 确保目录存在
await ensureDirectoryExists(serviceDir);
// 读取目录内容
const files = await fs.readdir(serviceDir);
// 过滤出.scfg文件
const serviceFiles = [];
for (const file of files) {
const ext = path.extname(file).toLowerCase();
if (ext === '.scfg') {
const filePath = path.join(serviceDir, file);
const stats = await fs.stat(filePath);
if (stats.isFile()) {
serviceFiles.push({
name: file,
path: filePath,
size: stats.size,
mtime: stats.mtime
});
}
}
}
res.json(serviceFiles);
} catch (error) {
console.error('获取服务文件列表失败:', error);
res.status(500).json({ error: '获取服务文件列表失败', message: error.message });
}
});
// 获取服务文件内容
router.get('/service-file-content', async (req, res) => {
try {
const filePath = req.query.path;
if (!filePath) {
return res.status(400).json({ error: '缺少路径参数' });
}
// 安全检查 - 确保只能访问Services目录下的文件
const serviceDir = getServicePath();
if (!isPathSafe(filePath, serviceDir)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 检查文件后缀,只允许.scfg
const ext = path.extname(filePath).toLowerCase();
if (ext !== '.scfg') {
return res.status(403).json({ error: '只能访问服务文件(.scfg)' });
}
// 检查文件是否存在
try {
await fs.access(filePath);
} catch (error) {
if (error.code === 'ENOENT') {
// 如果文件不存在,尝试创建空文件
try {
// 从filePath中获取服务名称不含扩展名
const serviceName = path.basename(filePath, '.scfg');
// 获取当前日期时间
const now = new Date();
const dateStr = now.toISOString().split('T')[0]; // 如 2023-04-15
const timeStr = now.toTimeString().split(' ')[0]; // 如 10:30:45
const basicXml = `<?xml version="1.0" encoding="UTF-8"?>
<Service>
<n>${serviceName}</n>
<Description>服务描述</Description>
<Author></Author>
<Version>1.0.0</Version>
<CreateTime>${dateStr} ${timeStr}</CreateTime>
<ChangeTime>${dateStr} ${timeStr}</ChangeTime>
<CommandList>
</CommandList>
</Service>`;
await fs.writeFile(filePath, basicXml, 'utf-8');
// 返回基本内容
return res.send(basicXml);
} catch (writeError) {
console.error('创建服务文件失败:', writeError);
return res.status(500).json({ error: '创建文件失败', message: writeError.message });
}
} else {
throw error;
}
}
// 获取文件状态
const stats = await fs.stat(filePath);
// 检查是否为文件
if (!stats.isFile()) {
return res.status(400).json({ error: '请求的路径不是文件' });
}
// 大文件检查
const MAX_FILE_SIZE = 1024 * 1024; // 1MB限制
if (stats.size > MAX_FILE_SIZE) {
return res.status(413).json({ error: '文件过大,无法读取' });
}
// 读取文件内容
const content = await fs.readFile(filePath, 'utf-8');
// 设置响应头
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
// 返回文件内容
res.send(content);
} catch (error) {
console.error('读取服务文件内容失败:', error);
res.status(500).json({ error: '读取服务文件内容失败', message: error.message });
}
});
// 保存服务文件内容
router.post('/save-service-file', async (req, res) => {
try {
const { path: filePath, content } = req.body;
if (!filePath || content === undefined) {
return res.status(400).json({ error: '缺少必要参数' });
}
// 安全检查 - 确保只能访问Services目录下的文件
const serviceDir = getServicePath();
if (!isPathSafe(filePath, serviceDir)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 检查文件后缀,只允许.scfg
const ext = path.extname(filePath).toLowerCase();
if (ext !== '.scfg') {
return res.status(403).json({ error: '只能修改服务文件(.scfg)' });
}
// 确保目录存在
await ensureDirectoryExists(path.dirname(filePath));
// 检查XML格式是否有效
if (content.trim()) { // 只有当内容不为空时才检查
const validation = await validateXml(content);
if (validation !== true) {
return res.status(400).json(validation);
}
}
// 写入文件内容
await fs.writeFile(filePath, content, 'utf-8');
res.json({ success: true, message: '文件保存成功' });
} catch (error) {
console.error('保存服务文件内容失败:', error);
res.status(500).json({ error: '保存服务文件内容失败', message: error.message });
}
});
// 创建新服务配置文件
router.post('/create-service-file', async (req, res) => {
try {
const { fileName } = req.body;
if (!fileName) {
return res.status(400).json({ error: '缺少文件名参数' });
}
// 获取Services目录
const serviceDir = getServicePath();
if (!serviceDir) {
return res.status(500).json({ error: 'XNCore环境变量未设置' });
}
// 设置文件路径
const filePath = path.join(serviceDir, fileName);
// 检查文件后缀,只允许.scfg
const ext = path.extname(fileName).toLowerCase();
if (ext !== '.scfg') {
return res.status(403).json({ error: '只能创建服务文件(.scfg)' });
}
// 确保目录存在
await ensureDirectoryExists(serviceDir);
// 检查文件是否已存在
try {
await fs.access(filePath);
// 文件已存在
return res.status(409).json({ error: '文件已存在' });
} catch (error) {
// 文件不存在,可以继续创建
if (error.code !== 'ENOENT') {
throw error;
}
}
// 从fileName中获取服务名称不含扩展名
const serviceName = path.basename(fileName, '.scfg');
// 获取当前日期时间
const now = new Date();
const dateStr = now.toISOString().split('T')[0]; // 如 2023-04-15
const timeStr = now.toTimeString().split(' ')[0]; // 如 10:30:45
// 创建基本的XML模板
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
<Service>
<n>${serviceName}</n>
<Description>服务描述</Description>
<Author></Author>
<Version>1.0.0</Version>
<CreateTime>${dateStr} ${timeStr}</CreateTime>
<ChangeTime>${dateStr} ${timeStr}</ChangeTime>
<CommandList>
</CommandList>
</Service>`;
// 写入文件内容
await fs.writeFile(filePath, xmlContent, 'utf-8');
res.status(201).json({
success: true,
message: '文件创建成功',
path: filePath,
content: xmlContent
});
} catch (error) {
console.error('创建服务文件失败:', error);
res.status(500).json({ error: '创建服务文件失败', message: error.message });
}
});
// 另存为服务文件
router.post('/save-service-as', async (req, res) => {
try {
const { fileName, content, currentFile, overwrite } = req.body;
if (!fileName || content === undefined) {
return res.status(400).json({ error: '缺少必要参数' });
}
// 获取Services目录
const serviceDir = getServicePath();
if (!serviceDir) {
return res.status(500).json({ error: 'XNCore环境变量未设置' });
}
// 设置目标文件路径
const targetPath = path.join(serviceDir, fileName);
// 检查文件后缀,只允许.scfg
const ext = path.extname(fileName).toLowerCase();
if (ext !== '.scfg') {
return res.status(403).json({ error: '只能保存服务文件(.scfg)' });
}
// 确保目录存在
await ensureDirectoryExists(path.dirname(targetPath));
// 如果路径与当前文件相同,直接保存
if (currentFile && targetPath === currentFile) {
await fs.writeFile(targetPath, content, 'utf-8');
return res.json({
success: true,
message: '文件保存成功',
path: targetPath
});
}
// 检查文件是否已存在
try {
await fs.access(targetPath);
// 文件已存在,但没有覆盖标记
if (!overwrite) {
return res.status(409).json({
error: '文件已存在',
code: 'FILE_EXISTS'
});
}
} catch (error) {
// 文件不存在,可以直接创建
if (error.code !== 'ENOENT') {
throw error;
}
}
// 检查XML格式是否有效
if (content.trim()) {
const validation = await validateXml(content);
if (validation !== true) {
return res.status(400).json(validation);
}
}
// 写入文件内容
await fs.writeFile(targetPath, content, 'utf-8');
res.json({
success: true,
message: '文件保存成功',
path: targetPath
});
} catch (error) {
console.error('另存为服务文件失败:', error);
res.status(500).json({ error: '另存为服务文件失败', message: error.message });
}
});
module.exports = router;

View File

@ -12,8 +12,6 @@ const versionRoutes = require('./routes/versions');
const filesystemRoutes = require('./routes/filesystem');
const systemInfoRoutes = require('./routes/system-info');
const scenarioRoutes = require('./routes/scenario-config');
const modelRoutes = require('./routes/model-config');
const serviceRoutes = require('./routes/service-config');
const serviceApiRoutes = require('./routes/service-dev');
const idlRoutes = require('./routes/idl');
const projectModelRoutes = require('./routes/project-model');
@ -84,8 +82,6 @@ app.use('/api', versionRoutes);
app.use('/api/filesystem', filesystemRoutes);
app.use('/api', systemInfoRoutes);
app.use('/api', scenarioRoutes);
app.use('/api', modelRoutes);
app.use('/api', serviceRoutes);
app.use('/api', serviceApiRoutes);
app.use('/api/idl', idlRoutes);
app.use('/api', projectModelRoutes);

View File

@ -1,5 +1,6 @@
const path = require('path');
const fs = require('fs').promises;
const fs = require('fs'); // 添加同步方法支持
const fsPromises = require('fs').promises; // 重命名为fsPromises以区分
const Database = require('better-sqlite3');
// 数据库连接管理
@ -77,17 +78,35 @@ function getServicePath() {
}
// 日志目录路径处理
function getActualLogPath(requestedPath) {
async function getActualLogPath(requestedPath) {
const xnCorePath = getXNCorePath();
const currentDir = process.cwd();
// 如果请求的路径是/log开头将它放在XNCore下
// 如果请求的路径是/log开头
if (requestedPath.startsWith('/log')) {
return path.join(xnCorePath, requestedPath);
// 首先尝试在XNCore下查找
const xnCoreLogPath = path.join(xnCorePath, requestedPath);
if (fs.existsSync(xnCoreLogPath)) {
return xnCoreLogPath;
}
// 如果XNCore下不存在则尝试在当前目录下查找
const currentLogPath = path.join(currentDir, requestedPath);
if (fs.existsSync(currentLogPath)) {
return currentLogPath;
}
// 如果都不存在默认使用XNCore下的路径
return xnCoreLogPath;
}
// 如果已经是绝对路径并且在XNCore下
else if (requestedPath.startsWith(xnCorePath)) {
return requestedPath;
}
// 如果已经是绝对路径并且在当前目录下
else if (requestedPath.startsWith(currentDir)) {
return requestedPath;
}
// 默认不允许访问其他路径
else {
return null;
@ -111,11 +130,11 @@ function isPathSafe(filePath, allowedDir) {
// 确保目录存在,如不存在则创建
async function ensureDirectoryExists(dirPath) {
try {
await fs.access(dirPath);
await fsPromises.access(dirPath);
} catch (error) {
if (error.code === 'ENOENT') {
try {
await fs.mkdir(dirPath, { recursive: true });
await fsPromises.mkdir(dirPath, { recursive: true });
return true;
} catch (mkdirError) {
console.error('创建目录失败:', mkdirError);

View File

@ -45,7 +45,7 @@ function getModelsByChapterId(chapterId, planeName) {
}
// 根据ClassName查询XNModelsVersion表中的模型版本
function getModelVersionsByClassName(className, planeName, confID) {
function getModelVersionsByClassName(className, planeName) {
try {
// 验证必填参数
if (!className) {
@ -54,9 +54,6 @@ function getModelVersionsByClassName(className, planeName, confID) {
if (!planeName) {
throw new Error('PlaneName 是必填参数');
}
if (!confID) {
throw new Error('ConfID 是必填参数');
}
const db = getDBConnection(true);
@ -67,11 +64,11 @@ function getModelVersionsByClassName(className, planeName, confID) {
DataPackagePath, DataPackageHeaderPath, DataPackageEntryPoint, DataPackageInterfaceName,
ConfID, CmdList
FROM 'XNModelsVersion'
WHERE ClassName = ? AND PlaneName = ? AND ConfID = ?
WHERE ClassName = ? AND PlaneName = ?
ORDER BY Version DESC
`;
const versions = db.prepare(query).all(className, planeName, confID);
const versions = db.prepare(query).all(className, planeName);
return versions;
} catch (error) {