完成构型配置页面
This commit is contained in:
parent
6b2158c26e
commit
7c5021958d
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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);
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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-6,0表示周日
|
||||
|
||||
// 生成月份选项
|
||||
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">×</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();
|
||||
});
|
||||
}
|
@ -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">×</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">×</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">×</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">×</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">×</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">×</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);
|
||||
}
|
||||
});
|
||||
}
|
@ -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;
|
||||
}
|
||||
`;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证元素名称是否符合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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
@ -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">×</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">×</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">×</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">×</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);
|
||||
}
|
||||
}
|
@ -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-6,0表示周日
|
||||
|
||||
// 生成月份选项
|
||||
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">×</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}`;
|
||||
}
|
||||
}
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
/**
|
||||
* XML工具类,包含XML处理相关的方法
|
||||
* @type {module}
|
||||
*/
|
||||
export class XmlUtils {
|
||||
/**
|
||||
* HTML转义
|
||||
*/
|
||||
static escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保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;
|
||||
}
|
||||
}
|
@ -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">
|
||||
接口配置
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
@ -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}`);
|
||||
|
@ -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;
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user