2025-04-28 12:25:20 +08:00

593 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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);
}