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