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 = `
`; const visualEditor = this.shadowRoot.querySelector('#visualEditor'); if (visualEditor) { this.renderVisualEditor(visualEditor, this.xmlDoc); // 恢复编辑状态标记 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: 未找到文件选择器'); } } else { console.log('ServiceConfig: 没有选中的文件或XML内容'); } }, 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() { try { const response = await fetch('/api/service-files'); if (!response.ok) { throw new Error(`服务器响应错误: ${response.status}`); } this.serviceFiles = await response.json(); this.updateFileSelector(); } catch (error) { console.error('加载服务文件失败:', error); // 如果请求失败,可能是API不存在,等待一段时间后重试 const retryLoadFiles = async () => { try { console.log('尝试重新加载服务文件列表...'); const retryResponse = await fetch('/api/service-files'); if (retryResponse.ok) { this.serviceFiles = await retryResponse.json(); this.updateFileSelector(); } else { console.error('重试加载服务文件失败'); } } catch (retryError) { console.error('重试加载服务文件失败:', retryError); } }; // 延迟3秒后重试 setTimeout(retryLoadFiles, 3000); } } updateFileSelector() { const fileSelector = this.shadowRoot.getElementById('fileSelector'); if (!fileSelector) return; // 清空现有选项 fileSelector.innerHTML = ''; // 按修改时间排序,最新的在前面 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) { 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(); this.selectedFile = filePath; this.xmlContent = content; // 解析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解析错误'); } this.xmlDoc = xmlDoc; // 特殊处理CommandList元素,确保命令以属性方式存储 this.ensureCommandListFormat(xmlDoc); } catch (parseError) { console.error('XML解析错误:', parseError); // 如果内容为空或解析失败,创建一个基本的XML文档 if (!content.trim()) { const basicXml = ` `; xmlDoc = parser.parseFromString(basicXml, 'application/xml'); this.xmlDoc = xmlDoc; this.xmlContent = basicXml; } else { // 显示错误信息 const configContent = this.shadowRoot.querySelector('.config-content'); configContent.innerHTML = `

XML解析错误

文件内容不是有效的XML格式。

${this.escapeHtml(content)}
`; return; } } this.updateFileContent(); this.resetEditState(); } catch (error) { console.error('加载文件内容失败:', error); const configContent = this.shadowRoot.querySelector('.config-content'); configContent.innerHTML = `

加载失败

${error.message}

`; } } updateFileContent() { const contentArea = this.shadowRoot.querySelector('#contentArea'); if (!this.xmlDoc || !this.selectedFile) { contentArea.innerHTML = `
请选择一个服务配置文件查看内容
`; return; } contentArea.innerHTML = `
`; const visualEditor = this.shadowRoot.querySelector('#visualEditor'); this.renderVisualEditor(visualEditor, this.xmlDoc); } render() { this.shadowRoot.innerHTML = `
服务配置文件:
请选择一个服务配置文件查看内容
`; } setupEventListeners() { // 文件选择器 const fileSelector = this.shadowRoot.getElementById('fileSelector'); fileSelector.addEventListener('change', (e) => { const filePath = e.target.value; if (filePath) { this.loadFileContent(filePath); } }); // 刷新文件列表 const refreshButton = this.shadowRoot.getElementById('refreshFiles'); refreshButton.addEventListener('click', () => { refreshButton.classList.add('refreshing'); this.loadServiceFiles().finally(() => { setTimeout(() => { refreshButton.classList.remove('refreshing'); }, 500); }); }); // 新建配置 const newButton = this.shadowRoot.getElementById('newConfig'); newButton.addEventListener('click', () => { this.showNewConfigDialog(); }); // 保存配置 const saveButton = this.shadowRoot.getElementById('saveConfig'); saveButton.addEventListener('click', async () => { if (!this.selectedFile) { alert('请先选择一个文件或创建新文件'); return; } try { // 确保获取当前可视化编辑器的所有值更新到XML文档 this.updateXmlFromVisualEditor(); // 保存文件 await this.saveFileContent(this.selectedFile, this.xmlContent); this.resetEditState(); // 更新保存按钮状态 saveButton.classList.remove('modified'); alert('保存成功'); } catch (error) { console.error('保存出错:', error); alert('保存失败: ' + error.message); } }); // 另存为 const saveAsButton = this.shadowRoot.getElementById('saveAsConfig'); saveAsButton.addEventListener('click', () => { if (!this.xmlContent) { alert('没有内容可保存'); return; } this.showSaveAsDialog(); }); } // 标记编辑状态 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 = '保存'; } } // HTML转义 escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // 确保CommandList元素符合特定格式 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 updateXmlFromVisualEditor() { if (!this.xmlDoc) return; // 处理基本信息 const rootElement = this.xmlDoc.documentElement; const visualEditor = this.shadowRoot.querySelector('#visualEditor'); if (visualEditor) { // 获取输入字段的值 const nameInput = visualEditor.querySelector('#serviceName'); const descInput = visualEditor.querySelector('#serviceDesc'); const authorInput = visualEditor.querySelector('#serviceAuthor'); const versionInput = visualEditor.querySelector('#serviceVersion'); const createTimeInput = visualEditor.querySelector('#serviceCreateTime'); const changeTimeInput = visualEditor.querySelector('#serviceChangeTime'); // 处理基本信息元素 this.updateElementIfExists(rootElement, 'Name', nameInput ? nameInput.value : ''); this.updateElementIfExists(rootElement, 'Description', descInput ? descInput.value : ''); this.updateElementIfExists(rootElement, 'Author', authorInput ? authorInput.value : ''); this.updateElementIfExists(rootElement, 'Version', versionInput ? versionInput.value : '1.0.0'); // 处理CreateTime元素 this.updateElementIfExists(rootElement, 'CreateTime', createTimeInput ? createTimeInput.value : ''); // 处理ChangeTime元素 - 总是设置为当前时间 const now = new Date(); const dateStr = now.toISOString().split('T')[0]; const timeStr = now.toTimeString().split(' ')[0]; const datetimeStr = `${dateStr} ${timeStr}`; this.updateElementIfExists(rootElement, 'ChangeTime', datetimeStr); // 更新界面上的修改时间 if (changeTimeInput) { changeTimeInput.value = datetimeStr; } // 确保CommandList元素存在 this.ensureCommandListFormat(this.xmlDoc); // 更新其他元素 visualEditor.querySelectorAll('.element-input-row input').forEach(input => { if (input.dataset.elementPath) { const parts = input.dataset.elementPath.split('.'); let current = this.xmlDoc; let lastElement = null; let lastPart = ''; // 遍历路径找到或创建元素 for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (!current) break; if (i === parts.length - 1) { lastPart = part; lastElement = current; current = current.querySelector(part); if (!current && lastElement) { current = this.xmlDoc.createElement(part); lastElement.appendChild(current); } } else { current = current.querySelector(part); } } if (current) { current.textContent = input.value; } } }); } // 重新序列化XML内容 const serializer = new XMLSerializer(); this.xmlContent = this.formatXml(serializer.serializeToString(this.xmlDoc)); this.markEdited(); } // 更新元素,如果不存在则创建 updateElementIfExists(parentElement, elementName, value) { if (!parentElement) return; let element = parentElement.querySelector(elementName); if (!element) { element = this.xmlDoc.createElement(elementName); parentElement.appendChild(element); } element.textContent = value; return element; } // 格式化XML formatXml(xml) { // 定义缩进层级 const indent = (level) => { // 确保level不为负数 const safeLevel = Math.max(0, level); return ' '.repeat(safeLevel); }; // 使用正则表达式格式化XML let formatted = ''; let level = 0; // 使用更安全的方式分割和处理XML // 不使用正则表达式,避免可能破坏元素内容 const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xml, 'application/xml'); // 使用XMLSerializer获取格式化的XML const serializer = new XMLSerializer(); const prettyXml = this.formatNode(xmlDoc, 0); return prettyXml; } // 递归处理XML节点 formatNode(node, level) { if (!node) return ''; let result = ''; const indentStr = ' '.repeat(level); // 处理不同类型的节点 switch (node.nodeType) { case Node.DOCUMENT_NODE: // 文档节点,处理子节点 result = '\n'; for (let i = 0; i < node.childNodes.length; i++) { result += this.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 += this.formatNode(node.childNodes[i], level + 1); } result += indentStr; } else { // 只有文本内容,不换行 result += textContent.trim(); } result += '\n'; } break; case Node.TEXT_NODE: // 文本节点,去除空白文本节点 const text = node.nodeValue.trim(); if (text) { result += text; } break; case Node.COMMENT_NODE: // 注释节点 result += indentStr + '\n'; break; default: // 其他类型节点 break; } return result; } // 文件保存功能 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; } } // 创建新配置 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}`); } const result = await response.json(); // 重新加载文件列表 await this.loadServiceFiles(); // 加载新创建的文件 await this.loadFileContent(result.path); return result; } catch (error) { console.error('创建新配置文件失败:', error); throw error; } } // 显示新建配置对话框 showNewConfigDialog() { // 创建模态框 const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'newConfigModal'; modal.innerHTML = ` `; 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'; } try { await this.createNewConfig(fileName); closeModal(); } catch (error) { alert('创建失败: ' + error.message); } }); // 处理回车键 fileNameInput.addEventListener('keyup', (e) => { if (e.key === 'Enter') { confirmBtn.click(); } }); // 打开后聚焦输入框 setTimeout(() => { fileNameInput.focus(); }, 100); } // 显示另存为对话框 showSaveAsDialog() { // 创建模态框 const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'saveAsModal'; modal.innerHTML = ` `; 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 (this.selectedFile) { const fileName = this.selectedFile.split('/').pop(); fileNameInput.value = fileName; } 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'; } try { const response = await fetch('/api/save-service-as', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName, content: this.xmlContent, currentFile: this.selectedFile }) }); if (!response.ok) { const errorData = await response.json(); if (errorData.code === 'FILE_EXISTS') { if (confirm('文件已存在,是否覆盖?')) { // 尝试带覆盖标志重新保存 const retryResponse = await fetch('/api/save-service-as', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName, content: this.xmlContent, currentFile: this.selectedFile, overwrite: true }) }); if (!retryResponse.ok) { const retryErrorData = await retryResponse.json(); throw new Error(retryErrorData.error || `保存失败: ${retryResponse.status}`); } const result = await retryResponse.json(); this.selectedFile = result.path; this.resetEditState(); await this.loadServiceFiles(); closeModal(); } return; } throw new Error(errorData.error || `保存失败: ${response.status}`); } const result = await response.json(); this.selectedFile = result.path; this.resetEditState(); await this.loadServiceFiles(); closeModal(); } catch (error) { alert('保存失败: ' + error.message); } }); // 处理回车键 fileNameInput.addEventListener('keyup', (e) => { if (e.key === 'Enter') { confirmBtn.click(); } }); // 打开后聚焦输入框 setTimeout(() => { fileNameInput.focus(); fileNameInput.select(); // 全选文本,方便修改 }, 100); } // 可视化编辑器渲染 renderVisualEditor(container, xmlDoc) { if (!xmlDoc || !container) return; 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') { // 创建基本信息部分 const basicInfoSection = document.createElement('div'); basicInfoSection.className = 'section'; const basicInfoHeader = document.createElement('div'); basicInfoHeader.className = 'section-header'; basicInfoHeader.innerHTML = '
基本信息
'; 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')) { const now = new Date(); createTimeElement.textContent = `${now.toISOString().split('T')[0]} ${now.toTimeString().split(' ')[0]}`; rootElement.appendChild(createTimeElement); } if (!rootElement.querySelector('ChangeTime')) { const now = new Date(); changeTimeElement.textContent = `${now.toISOString().split('T')[0]} ${now.toTimeString().split(' ')[0]}`; rootElement.appendChild(changeTimeElement); } // 创建基本信息表单 const basicInfoForm = document.createElement('div'); basicInfoForm.className = 'form-container'; // 服务名称和描述在同一行 const nameDescContainer = document.createElement('div'); nameDescContainer.className = 'inline-form'; nameDescContainer.innerHTML = `
`; basicInfoForm.appendChild(nameDescContainer); // 其他信息以两列布局 const otherInfoContainer = document.createElement('div'); otherInfoContainer.className = 'two-column-form'; otherInfoContainer.style.marginTop = '15px'; otherInfoContainer.innerHTML = `
`; basicInfoForm.appendChild(otherInfoContainer); basicInfoSection.appendChild(basicInfoForm); // 添加事件监听 basicInfoSection.addEventListener('change', (e) => { if (e.target.id === 'serviceName') { nameElement.textContent = e.target.value; // 直接更新XML内容和状态 const serializer = new XMLSerializer(); this.xmlContent = this.formatXml(serializer.serializeToString(this.xmlDoc)); this.markEdited(); } else if (e.target.id === 'serviceDesc') { descElement.textContent = e.target.value; // 直接更新XML内容和状态 const serializer = new XMLSerializer(); this.xmlContent = this.formatXml(serializer.serializeToString(this.xmlDoc)); this.markEdited(); } else if (e.target.id === 'serviceAuthor') { authorElement.textContent = e.target.value; // 直接更新XML内容和状态 const serializer = new XMLSerializer(); this.xmlContent = this.formatXml(serializer.serializeToString(this.xmlDoc)); this.markEdited(); } else if (e.target.id === 'serviceVersion') { versionElement.textContent = e.target.value; // 直接更新XML内容和状态 const serializer = new XMLSerializer(); this.xmlContent = this.formatXml(serializer.serializeToString(this.xmlDoc)); this.markEdited(); } else if (e.target.id === 'serviceCreateTime') { createTimeElement.textContent = e.target.value; // 直接更新XML内容和状态 const serializer = new XMLSerializer(); this.xmlContent = this.formatXml(serializer.serializeToString(this.xmlDoc)); this.markEdited(); } else if (e.target.id === 'serviceChangeTime') { changeTimeElement.textContent = e.target.value; // 直接更新XML内容和状态 const serializer = new XMLSerializer(); this.xmlContent = this.formatXml(serializer.serializeToString(this.xmlDoc)); this.markEdited(); } }); // 添加日期时间选择器事件 basicInfoSection.querySelector('#createTimeBtn').addEventListener('click', () => { const input = basicInfoSection.querySelector('#serviceCreateTime'); this.showDateTimeDialog(input); }); basicInfoSection.querySelector('#changeTimeBtn').addEventListener('click', () => { const input = basicInfoSection.querySelector('#serviceChangeTime'); this.showDateTimeDialog(input); }); // 添加刷新按钮事件 basicInfoSection.querySelector('#refreshCreateTimeBtn').addEventListener('click', () => { const input = basicInfoSection.querySelector('#serviceCreateTime'); const now = new Date(); const dateStr = now.toISOString().split('T')[0]; const timeStr = now.toTimeString().split(' ')[0]; const datetimeStr = `${dateStr} ${timeStr}`; input.value = datetimeStr; createTimeElement.textContent = datetimeStr; // 直接更新XML内容和状态 const serializer = new XMLSerializer(); this.xmlContent = this.formatXml(serializer.serializeToString(this.xmlDoc)); this.markEdited(); }); basicInfoSection.querySelector('#refreshChangeTimeBtn').addEventListener('click', () => { const input = basicInfoSection.querySelector('#serviceChangeTime'); const now = new Date(); const dateStr = now.toISOString().split('T')[0]; const timeStr = now.toTimeString().split(' ')[0]; const datetimeStr = `${dateStr} ${timeStr}`; input.value = datetimeStr; changeTimeElement.textContent = datetimeStr; // 直接更新XML内容和状态 const serializer = new XMLSerializer(); this.xmlContent = this.formatXml(serializer.serializeToString(this.xmlDoc)); this.markEdited(); }); // 添加到可视化编辑器 visualEditor.appendChild(basicInfoSection); // 创建命令列表部分 const commandsSection = document.createElement('div'); commandsSection.className = 'section'; const commandsHeader = document.createElement('div'); commandsHeader.className = 'section-header'; commandsHeader.innerHTML = `
指令列表
`; 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 = ` 名称 调用 描述 操作 `; // 创建表格内容 const commandsTableBody = commandsTable.querySelector('#commandsTableBody'); // 获取所有命令 const commandElements = commandListElement.querySelectorAll('Command'); if (commandElements.length === 0) { // 如果没有命令,显示空行 const emptyRow = document.createElement('tr'); emptyRow.innerHTML = '暂无指令'; 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 = ` ${this.escapeHtml(name)} ${this.escapeHtml(call)} ${this.escapeHtml(description)} `; commandsTableBody.appendChild(row); }); // 添加编辑和删除事件 commandsTableBody.querySelectorAll('.edit-btn').forEach(btn => { btn.addEventListener('click', () => { const index = parseInt(btn.dataset.index); const command = commandElements[index]; this.showEditCommandDialog(command, index); }); }); 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); // 直接更新XML内容和状态 const serializer = new XMLSerializer(); const xmlString = serializer.serializeToString(this.xmlDoc); this.xmlContent = this.formatXml(xmlString); this.markEdited(); // 移除表格行 const row = commandsTableBody.querySelector(`tr[data-index="${index}"]`); if (row) { row.remove(); // 更新其余行的索引 commandsTableBody.querySelectorAll('tr').forEach((row, newIndex) => { row.dataset.index = newIndex; row.querySelectorAll('button').forEach(button => { button.dataset.index = newIndex; }); }); // 如果没有命令了,添加空行提示 if (commandsTableBody.children.length === 0) { const emptyRow = document.createElement('tr'); emptyRow.innerHTML = '暂无指令'; commandsTableBody.appendChild(emptyRow); } } } }); }); } // 添加"添加指令"按钮事件 commandsSection.querySelector('#addCommandBtn').addEventListener('click', () => { this.showAddCommandDialog(commandListElement); }); // 添加表格 commandsSection.appendChild(commandsTable); // 添加到可视化编辑器 visualEditor.appendChild(commandsSection); // 创建其他设置部分(包括UDP) const otherSettingsSection = document.createElement('div'); otherSettingsSection.className = 'section'; const otherSettingsHeader = document.createElement('div'); otherSettingsHeader.className = 'section-header'; otherSettingsHeader.innerHTML = `
其他设置
如需添加参数,请手动编辑配置文件 `; otherSettingsSection.appendChild(otherSettingsHeader); // 添加额外的CSS样式 const otherSettingsStyle = document.createElement('style'); otherSettingsStyle.textContent = ` .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; } `; otherSettingsSection.appendChild(otherSettingsStyle); // 创建其他设置内容 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); // 直接更新XML内容和状态 const serializer = new XMLSerializer(); const xmlString = serializer.serializeToString(this.xmlDoc); this.xmlContent = this.formatXml(xmlString); this.markEdited(); // 移除元素框 if (elementBox.parentNode) { elementBox.parentNode.removeChild(elementBox); } // 如果没有其他元素了,显示"没有其他设置项"提示 const otherSettingsContainer = this.shadowRoot.querySelector('.other-settings-container'); 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}`); // 出错时重新渲染整个编辑器以恢复状态 this.renderVisualEditor(container, this.xmlDoc); } } }); 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; // 直接更新XML内容和状态 const serializer = new XMLSerializer(); this.xmlContent = this.formatXml(serializer.serializeToString(this.xmlDoc)); this.markEdited(); }); 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); // 直接更新XML内容和状态 const serializer = new XMLSerializer(); const xmlString = serializer.serializeToString(this.xmlDoc); this.xmlContent = this.formatXml(xmlString); this.markEdited(); // 移除元素框 if (elementBox.parentNode) { elementBox.parentNode.removeChild(elementBox); } // 如果没有其他元素了,显示"没有其他设置项"提示 const otherSettingsContainer = this.shadowRoot.querySelector('.other-settings-container'); 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}`); // 出错时重新渲染整个编辑器以恢复状态 this.renderVisualEditor(container, this.xmlDoc); } } }); inputRow.appendChild(deleteBtn); elementContent.appendChild(inputRow); } // 将元素内容添加到元素框 elementBox.appendChild(elementContent); // 将元素框添加到容器 containerElement.appendChild(elementBox); }); }; // 获取元素的路径,用于唯一标识 const getElementPath = (element) => { const path = []; let current = element; while (current && current !== this.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); // 添加到可视化编辑器 visualEditor.appendChild(otherSettingsSection); } else { // 不是Service根元素,显示错误信息 visualEditor.innerHTML = `
无法编辑:XML文档的根元素不是Service。 请确保XML文档的根元素是Service。
`; } container.appendChild(style); container.appendChild(visualEditor); } // 显示日期时间选择对话框 showDateTimeDialog(inputElement) { // 解析当前日期和时间 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 += ``; }); // 生成年份选项 let yearOptions = ''; const currentYear = new Date().getFullYear(); for (let y = currentYear - 10; y <= currentYear + 10; y++) { yearOptions += ``; } // 生成日历表格 let calendarRows = ''; let dayCount = 1; // 添加表头 calendarRows += ''; ['日', '一', '二', '三', '四', '五', '六'].forEach(dayName => { calendarRows += `${dayName}`; }); calendarRows += ''; // 计算行数 const totalCells = firstDay + daysInMonth; const rowCount = Math.ceil(totalCells / 7); // 添加日期行 for (let i = 0; i < rowCount; i++) { calendarRows += ''; for (let j = 0; j < 7; j++) { if ((i === 0 && j < firstDay) || dayCount > daysInMonth) { calendarRows += ''; } else { const isToday = dayCount === day; calendarRows += `${dayCount}`; dayCount++; } } calendarRows += ''; } modal.innerHTML = ` `; // 添加样式 const style = document.createElement('style'); style.textContent = ` .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; } `; // 添加到DOM document.body.appendChild(style); document.body.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); this.showDateTimeDialog(inputElement); }; calendarMonth.addEventListener('change', updateCalendar); calendarYear.addEventListener('change', updateCalendar); const closeModal = () => { if (this.shadowRoot.contains(modal)) { this.shadowRoot.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'; // 更新输入框值 inputElement.value = `${formattedDate} ${time}`; // 触发更改事件 const changeEvent = new Event('change', { bubbles: true }); inputElement.dispatchEvent(changeEvent); closeModal(); }); // 点击模态窗口外部关闭 modal.addEventListener('click', (event) => { if (event.target === modal) { closeModal(); } }); } // 显示添加指令对话框 showAddCommandDialog(commandListElement) { // 创建模态框 const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'addCommandModal'; modal.innerHTML = ` `; // 添加到 shadowRoot 而不是 document.body this.shadowRoot.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 (this.shadowRoot.contains(modal)) { this.shadowRoot.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 = this.xmlDoc.createElement('Command'); newCommand.setAttribute('Name', name); newCommand.setAttribute('Call', call); newCommand.setAttribute('Description', description || `${name}描述`); // 添加到命令列表 commandListElement.appendChild(newCommand); // 直接更新XML内容和状态 const serializer = new XMLSerializer(); const xmlString = serializer.serializeToString(this.xmlDoc); this.xmlContent = this.formatXml(xmlString); this.markEdited(); // 重新渲染可视化编辑器 this.updateFileContent(); closeModal(); } catch (error) { console.error('添加指令失败:', error); alert('添加指令失败: ' + error.message); } }); // 点击模态窗口外部关闭 modal.addEventListener('click', (event) => { if (event.target === modal) { closeModal(); } }); } // 显示编辑指令对话框 showEditCommandDialog(commandElement, index) { // 获取当前命令属性 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 = ` `; // 添加到 shadowRoot 而不是 document.body this.shadowRoot.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 (this.shadowRoot.contains(modal)) { this.shadowRoot.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}描述`); // 直接更新XML内容和状态 const serializer = new XMLSerializer(); const xmlString = serializer.serializeToString(this.xmlDoc); this.xmlContent = this.formatXml(xmlString); this.markEdited(); // 重新渲染可视化编辑器 this.updateFileContent(); closeModal(); } catch (error) { console.error('编辑指令失败:', error); alert('编辑指令失败: ' + error.message); } }); // 点击模态窗口外部关闭 modal.addEventListener('click', (event) => { if (event.target === modal) { closeModal(); } }); } } customElements.define('service-config', ServiceConfig);