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 = `
`;
}
}
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);