class ModelConfig extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.selectedFile = null; this.xmlContent = ''; this.xmlDoc = null; this.isEdited = false; this.modelFiles = []; } async connectedCallback() { this.render(); await this.loadModelFiles(); this.setupEventListeners(); } async loadModelFiles() { try { const response = await fetch('/api/model-files'); if (!response.ok) { throw new Error(`服务器响应错误: ${response.status}`); } this.modelFiles = await response.json(); this.updateFileSelector(); } catch (error) { console.error('加载模型文件失败:', error); // 如果请求失败,可能是API不存在,等待一段时间后重试 const retryLoadFiles = async () => { try { console.log('尝试重新加载模型文件列表...'); const retryResponse = await fetch('/api/model-files'); if (retryResponse.ok) { this.modelFiles = 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.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 response = await fetch(`/api/model-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.loadModelFiles().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 { await this.saveFileContent(this.selectedFile, this.xmlContent); this.resetEditState(); // 更新保存按钮状态 saveButton.classList.remove('modified'); } catch (error) { alert('保存失败: ' + error.message); } }); // 另存为 const saveAsButton = this.shadowRoot.getElementById('saveAsConfig'); saveAsButton.addEventListener('click', () => { if (!this.xmlContent) { alert('没有内容可保存'); return; } this.showSaveAsDialog(); }); } 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); // 渲染基本信息部分 this.renderBasicInfoSection(visualEditor, rootElement); // 渲染高级设置部分 this.renderAdvancedSection(visualEditor, rootElement); } else { // 不是Model根元素,显示错误信息 visualEditor.innerHTML = `
无法编辑:XML文档的根元素不是Model。 请确保XML文档的根元素是Model。
`; } 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(); // 标记已编辑 }); }); } renderBasicInfoSection(container, rootElement) { 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', () => { this.updateElementValue(field.name, input.value); }); 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', () => { this.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; this.updateElementValue(field.name, datetimeStr); this.markEdited(); // 确保触发修改标志 }); inputContainer.appendChild(datetimeButton); inputContainer.appendChild(refreshButton); } // 组装行 row.appendChild(label); row.appendChild(inputContainer); twoColumnForm.appendChild(row); }); section.appendChild(twoColumnForm); container.appendChild(section); } // 显示日期时间选择对话框 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 = ` .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; } `; // 添加到DOM this.shadowRoot.appendChild(style); this.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); this.showDateTimeDialog(inputElement); }; calendarMonth.addEventListener('change', updateCalendar); calendarYear.addEventListener('change', updateCalendar); const closeModal = () => { modal.style.display = 'none'; this.shadowRoot.removeChild(modal); if (style.parentNode) { this.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}`; // 触发更改事件 this.updateElementValue(inputElement.dataset.field, inputElement.value); this.markEdited(); // 确保触发修改标志 closeModal(); }); } renderAdvancedSection(container, rootElement) { 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值 this.updateElementValue('Node', `${freqGroupValue}-${nodeIndex}`); } }; // 初始化节点选项 updateNodeOptions(freqGroup); // 添加运行频率下拉框的变更事件 freqGroupSelect.addEventListener('change', () => { const newFreqGroup = parseInt(freqGroupSelect.value); updateNodeOptions(newFreqGroup); // 更新XML值 this.updateElementValue('Node', `${newFreqGroup}-${nodeIndexSelect.value}`); }); // 添加节点索引下拉框的变更事件 nodeIndexSelect.addEventListener('change', () => { // 更新XML值 this.updateElementValue('Node', `${freqGroupSelect.value}-${nodeIndexSelect.value}`); }); // 添加到容器 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', () => { this.updateElementValue('Priority', priorityInput.value); }); 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', () => { this.updateElementValue('MathLib', mathLibInput.value); }); mathLibContainer.appendChild(mathLibInput); // 组装行 mathLibRow.appendChild(mathLibLabel); mathLibRow.appendChild(mathLibContainer); form.appendChild(mathLibRow); // 添加命令列表表格 this.renderCommandListTable(form, rootElement); section.appendChild(form); container.appendChild(section); // 添加其它设置部分 this.renderOtherSettingsSection(container, rootElement); } // 渲染其它设置部分 renderOtherSettingsSection(container, rootElement) { const section = document.createElement('div'); section.className = 'settings-section other-settings'; const header = document.createElement('div'); header.className = 'section-header'; header.innerHTML = `

其他设置

如需添加参数,请手动编辑配置文件
`; section.appendChild(header); // 创建元素容器 const elementsContainer = document.createElement('div'); elementsContainer.id = 'otherSettingsContainer'; elementsContainer.className = 'elements-container'; // 渲染所有元素 this.renderElements(elementsContainer, rootElement, '/' + rootElement.nodeName); section.appendChild(elementsContainer); container.appendChild(section); } // 递归渲染XML元素 renderElements(container, parentElement, parentPath, level = 0) { // 过滤出需要在其他设置中显示的元素 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; this.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', () => { this.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'; // 子元素缩进 // 递归渲染子元素 this.renderElements(childContainer, element, elementPath, level + 1); container.appendChild(childContainer); } }); } // 显示添加元素对话框 showAddElementDialog(parentElement, parentPath) { // 创建模态框 const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'addElementModal'; modal.innerHTML = ` `; this.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 = this.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'; this.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 (!this.isValidElementName(name)) { alert('无效的元素名称。元素名称必须以字母或下划线开头,不能包含空格或特殊字符。'); return; } // 添加元素 this.addElement(parentElement, name, value); // 重新渲染元素列表 const container = this.shadowRoot.querySelector('#otherSettingsContainer'); if (container) { container.innerHTML = ''; this.renderElements(container, this.xmlDoc.documentElement, '/' + this.xmlDoc.documentElement.nodeName); } closeModal(); }); } // 验证元素名称是否符合XML规范 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; } // 显示编辑元素对话框 showEditElementDialog(element, elementPath) { // 创建模态框 const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'editElementModal'; modal.innerHTML = ` `; this.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'; this.shadowRoot.removeChild(modal); }; closeBtn.addEventListener('click', closeModal); cancelBtn.addEventListener('click', closeModal); confirmBtn.addEventListener('click', () => { const value = valueInput.value; // 更新元素值 this.updateElementByPath(elementPath, value); // 更新UI中的值 const inputElement = this.shadowRoot.querySelector(`input[data-path="${elementPath}"]`); if (inputElement) { inputElement.value = value; } closeModal(); }); } // 根据路径更新元素值 updateElementByPath(path, value) { if (!this.xmlDoc) return; try { // 分割路径 const pathParts = path.split('/').filter(p => p); let currentElement = this.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; // 更新XML内容 this.updateXmlFromVisualEditor(); this.markEdited(); } catch (error) { console.error('更新元素失败:', error); alert('更新元素失败: ' + error.message); } } // 添加新元素 addElement(parentElement, name, value) { if (!this.xmlDoc) return; try { // 创建新元素 const newElement = this.xmlDoc.createElement(name); // 设置值 if (value) { newElement.textContent = value; } // 添加到父元素 parentElement.appendChild(newElement); // 更新XML内容 this.updateXmlFromVisualEditor(); this.markEdited(); } catch (error) { console.error('添加元素失败:', error); alert('添加元素失败: ' + error.message); } } // 删除元素 deleteElement(element, elementPath) { if (!this.xmlDoc) return; // 确认删除 if (!confirm(`确定要删除元素 ${element.nodeName} 吗?此操作不可撤销。`)) { return; } try { // 删除元素 element.parentNode.removeChild(element); // 更新XML内容 this.updateXmlFromVisualEditor(); this.markEdited(); // 重新渲染元素列表 const container = this.shadowRoot.querySelector('#otherSettingsContainer'); if (container) { container.innerHTML = ''; this.renderElements(container, this.xmlDoc.documentElement, '/' + this.xmlDoc.documentElement.nodeName); } } catch (error) { console.error('删除元素失败:', error); alert('删除元素失败: ' + error.message); } } // 更新元素值 updateElementValue(elementName, value) { if (!this.xmlDoc) return; const root = this.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 = this.xmlDoc.createElement(elementName); root.appendChild(element); } // 更新值 element.textContent = value; // 更新XML内容 const modifiedXml = new XMLSerializer().serializeToString(this.xmlDoc); this.xmlContent = modifiedXml; // 标记已编辑 this.markEdited(); } // 渲染命令列表表格 renderCommandListTable(container, rootElement) { // 查找CommandList元素 const commandListElement = rootElement.querySelector('CommandList'); if (!commandListElement) { // 创建CommandList部分 const commandListSection = document.createElement('div'); commandListSection.className = 'form-group'; commandListSection.innerHTML = `
名称 调用 描述 操作
`; container.appendChild(commandListSection); // 添加命令事件监听 const addCommandButton = commandListSection.querySelector('#addCommandButton'); addCommandButton.addEventListener('click', () => { this.showAddCommandDialog(rootElement); }); return; } // 创建CommandList部分 const commandListSection = document.createElement('div'); commandListSection.className = 'form-group'; commandListSection.innerHTML = `
名称 调用 描述 操作
`; container.appendChild(commandListSection); const tableBody = commandListSection.querySelector('tbody'); // 获取命令列表 const commandElements = commandListElement.querySelectorAll('Command'); // 如果没有命令,添加"添加命令"按钮 if (commandElements.length === 0) { tableBody.innerHTML = ` `; // 添加命令事件监听 const addCommandButton = tableBody.querySelector('#addCommandButton'); addCommandButton.addEventListener('click', () => { this.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 = ` ${this.escapeHtml(name)} ${this.escapeHtml(call)} ${this.escapeHtml(description)}
`; // 编辑按钮事件 row.querySelector('.edit-button').addEventListener('click', () => { this.showEditCommandDialog(command, index); }); // 删除按钮事件 row.querySelector('.delete-button').addEventListener('click', () => { this.deleteCommand(command, index); }); tableBody.appendChild(row); }); // 添加"添加命令"按钮行 const addRow = document.createElement('tr'); addRow.innerHTML = ` `; // 添加命令事件监听 addRow.querySelector('#addCommandButton').addEventListener('click', () => { this.showAddCommandDialog(rootElement); }); tableBody.appendChild(addRow); } 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'); } } 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 } } escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } async 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; } } async createNewConfig(fileName) { try { const response = await fetch('/api/create-model-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.loadModelFiles(); // 加载新创建的文件 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 = ` `; this.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'; this.shadowRoot.removeChild(modal); }; closeBtn.addEventListener('click', closeModal); cancelBtn.addEventListener('click', closeModal); confirmBtn.addEventListener('click', async () => { const fileName = modal.querySelector('#newFileName').value.trim(); if (!fileName) { alert('文件名不能为空'); return; } // 检查文件扩展名 if (!fileName.endsWith('.mcfg')) { alert('文件名必须以 .mcfg 结尾'); return; } try { await this.createNewConfig(fileName); closeModal(); } catch (error) { if (error.message.includes('文件已存在')) { if (confirm('文件已存在,是否覆盖?')) { try { await this.createNewConfig(fileName, true); closeModal(); } catch (retryError) { alert('创建文件失败: ' + retryError.message); } } } else { alert('创建文件失败: ' + error.message); } } }); } showSaveAsDialog() { // 创建模态框 const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'saveAsModal'; modal.innerHTML = ` `; this.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'; this.shadowRoot.removeChild(modal); }; closeBtn.addEventListener('click', closeModal); cancelBtn.addEventListener('click', closeModal); confirmBtn.addEventListener('click', async () => { const fileName = modal.querySelector('#saveAsFileName').value.trim(); if (!fileName) { alert('文件名不能为空'); return; } // 检查文件扩展名 if (!fileName.endsWith('.mcfg')) { alert('文件名必须以 .mcfg 结尾'); return; } try { const response = await fetch('/api/save-model-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-model-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.loadModelFiles(); closeModal(); } return; } throw new Error(errorData.error || `保存失败: ${response.status}`); } const result = await response.json(); this.selectedFile = result.path; this.resetEditState(); await this.loadModelFiles(); closeModal(); } catch (error) { console.error('另存为失败:', error); alert('另存为失败: ' + error.message); } }); } // 显示添加命令对话框 showAddCommandDialog(rootElement) { // 创建模态框 const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'addCommandModal'; modal.innerHTML = ` `; this.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'; this.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 = this.xmlDoc.createElement('CommandList'); rootElement.appendChild(commandListElement); } // 创建新Command元素 const commandElement = this.xmlDoc.createElement('Command'); commandElement.setAttribute('Name', name); commandElement.setAttribute('Call', call); commandElement.setAttribute('Description', description || `${name}描述`); // 添加到CommandList commandListElement.appendChild(commandElement); // 更新XML内容 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); } closeModal(); } catch (error) { console.error('添加命令失败:', error); alert('添加命令失败: ' + error.message); } }); } // 显示编辑命令对话框 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 = ` `; this.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'; this.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}描述`); // 更新XML内容 this.updateXmlFromVisualEditor(); this.markEdited(); // 更新表格行 const row = this.shadowRoot.querySelector(`.command-row[data-index="${index}"]`); if (row) { row.querySelectorAll('.command-cell')[0].textContent = newName; row.querySelectorAll('.command-cell')[1].textContent = newCall; row.querySelectorAll('.command-cell')[2].textContent = newDescription || `${newName}描述`; } else { // 如果找不到行,重新渲染整个编辑器 const visualEditor = this.shadowRoot.querySelector('#visualEditor'); if (visualEditor) { visualEditor.innerHTML = ''; this.renderVisualEditor(visualEditor, this.xmlDoc); } } closeModal(); } catch (error) { console.error('编辑命令失败:', error); alert('编辑命令失败: ' + error.message); } }); } // 删除命令 deleteCommand(commandElement, index) { if (!confirm('确定要删除此命令吗?此操作不可撤销。')) { return; } try { // 从DOM中移除命令元素 commandElement.parentNode.removeChild(commandElement); // 更新XML内容 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); } } } catch (error) { console.error('删除命令失败:', error); alert('删除命令失败: ' + error.message); } } // 从可视化编辑器更新XML updateXmlFromVisualEditor() { if (!this.xmlDoc) return; const modifiedXml = new XMLSerializer().serializeToString(this.xmlDoc); this.xmlContent = modifiedXml; } // 渲染命令列表表格 renderCommandList(container, commandListElement, rootElement) { // 创建命令列表容器 const commandListContainer = document.createElement('div'); commandListContainer.id = 'commandListContainer'; commandListContainer.className = 'section'; // 创建标题和添加按钮 const header = document.createElement('div'); header.className = 'section-header'; header.innerHTML = `

命令列表

`; commandListContainer.appendChild(header); // 添加按钮事件 const addBtn = header.querySelector('#addCommandBtn'); addBtn.addEventListener('click', () => { this.showAddCommandDialog(rootElement); }); // 创建表格 const table = document.createElement('table'); table.className = 'command-table'; // 创建表头 const thead = document.createElement('thead'); thead.innerHTML = ` 命令名称 命令调用 描述 操作 `; 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 = '没有命令记录'; 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 = ` ${this.escapeHtml(name)} ${this.escapeHtml(call)} ${this.escapeHtml(description)} `; tbody.appendChild(row); // 添加编辑按钮事件 const editBtn = row.querySelector('.edit-command'); editBtn.addEventListener('click', () => { this.showEditCommandDialog(command, index); }); // 添加删除按钮事件 const deleteBtn = row.querySelector('.delete-command'); deleteBtn.addEventListener('click', () => { this.deleteCommand(command, index); }); }); } } else { // 如果没有命令列表元素,显示空行 const emptyRow = document.createElement('tr'); emptyRow.innerHTML = '没有命令记录'; tbody.appendChild(emptyRow); } table.appendChild(tbody); commandListContainer.appendChild(table); container.appendChild(commandListContainer); return commandListContainer; } // 确保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; } // 更新命令列表部分 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 = this.ensureCommandListFormat(this.xmlDoc); // 渲染命令列表 this.renderCommandList(visualEditorContainer, commandListElement, rootElement); } } customElements.define('model-config', ModelConfig);