593 lines
24 KiB
JavaScript
Raw Normal View History

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