2837 lines
106 KiB
JavaScript
2837 lines
106 KiB
JavaScript
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 = '<option value="">-- 选择模型配置文件 --</option>';
|
||
|
||
// 按修改时间排序,最新的在前面
|
||
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 = `<?xml version="1.0" encoding="UTF-8"?>
|
||
<Model>
|
||
<Properties>
|
||
<!-- 模型属性 -->
|
||
</Properties>
|
||
<Params>
|
||
<!-- 模型参数 -->
|
||
</Params>
|
||
</Model>`;
|
||
|
||
xmlDoc = parser.parseFromString(basicXml, 'application/xml');
|
||
this.xmlDoc = xmlDoc;
|
||
this.xmlContent = basicXml;
|
||
} else {
|
||
// 显示错误信息
|
||
const configContent = this.shadowRoot.querySelector('.config-content');
|
||
configContent.innerHTML = `
|
||
<div class="error-message">
|
||
<h3>XML解析错误</h3>
|
||
<p>文件内容不是有效的XML格式。</p>
|
||
<pre>${this.escapeHtml(content)}</pre>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
}
|
||
|
||
this.updateFileContent();
|
||
this.resetEditState();
|
||
} catch (error) {
|
||
console.error('加载文件内容失败:', error);
|
||
const configContent = this.shadowRoot.querySelector('.config-content');
|
||
configContent.innerHTML = `
|
||
<div class="error-message">
|
||
<h3>加载失败</h3>
|
||
<p>${error.message}</p>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
updateFileContent() {
|
||
const contentArea = this.shadowRoot.querySelector('#contentArea');
|
||
|
||
if (!this.xmlDoc || !this.selectedFile) {
|
||
contentArea.innerHTML = `<div class="no-file-selected">请选择一个模型配置文件查看内容</div>`;
|
||
return;
|
||
}
|
||
|
||
contentArea.innerHTML = `
|
||
<div class="editor-container">
|
||
<div class="editor-panel active" id="visualEditor"></div>
|
||
</div>
|
||
`;
|
||
|
||
const visualEditor = this.shadowRoot.querySelector('#visualEditor');
|
||
this.renderVisualEditor(visualEditor, this.xmlDoc);
|
||
}
|
||
|
||
render() {
|
||
this.shadowRoot.innerHTML = `
|
||
<style>
|
||
:host {
|
||
display: block;
|
||
height: 100%;
|
||
overflow: auto;
|
||
padding: 16px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.config-container {
|
||
background-color: white;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
padding: 16px;
|
||
height: 100%;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.config-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
}
|
||
|
||
.file-selector-header {
|
||
flex-grow: 1;
|
||
margin-right: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.file-selector-label {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-right: 10px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.file-selector-header select {
|
||
flex-grow: 1;
|
||
padding: 6px;
|
||
border-radius: 4px;
|
||
border: 1px solid #e0e0e0;
|
||
appearance: none;
|
||
padding-right: 30px;
|
||
background-image: url('assets/icons/png/chevron-down_b.png');
|
||
background-repeat: no-repeat;
|
||
background-position: right 8px center;
|
||
background-size: 14px;
|
||
}
|
||
|
||
.refresh-button {
|
||
width: 28px;
|
||
height: 28px;
|
||
cursor: pointer;
|
||
margin-left: 8px;
|
||
border: none;
|
||
background-color: transparent;
|
||
background-size: contain;
|
||
background-repeat: no-repeat;
|
||
background-position: center;
|
||
background-image: url('assets/icons/png/refresh_b.png');
|
||
transition: transform 0.3s ease;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.refresh-button:hover {
|
||
opacity: 1;
|
||
}
|
||
|
||
.refresh-button.refreshing {
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.action-button {
|
||
background-color: #7986E7;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
padding: 6px 12px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: background-color 0.3s;
|
||
}
|
||
|
||
.action-button.save-button.modified {
|
||
background-color: #E77979;
|
||
}
|
||
|
||
.action-button.save-button.modified:hover {
|
||
background-color: #D66868;
|
||
}
|
||
|
||
.action-button:hover {
|
||
background-color: #6875D6;
|
||
}
|
||
|
||
.action-button.secondary {
|
||
background-color: #f0f0f0;
|
||
color: #333;
|
||
}
|
||
|
||
.action-button.secondary:hover {
|
||
background-color: #e0e0e0;
|
||
}
|
||
|
||
.config-content {
|
||
padding: 0;
|
||
flex-grow: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: auto;
|
||
}
|
||
|
||
.editor-container {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.editor-panel {
|
||
flex: 1;
|
||
height: 100%;
|
||
overflow: auto;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 4px;
|
||
padding: 12px;
|
||
background-color: #f9f9f9;
|
||
}
|
||
|
||
.editor-panel.active {
|
||
display: block;
|
||
}
|
||
|
||
.section {
|
||
margin-bottom: 20px;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 4px;
|
||
padding: 10px;
|
||
background-color: white;
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
padding-bottom: 5px;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
}
|
||
|
||
.section-title {
|
||
font-weight: bold;
|
||
color: #555;
|
||
}
|
||
|
||
.property-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.property-table th, .property-table td {
|
||
text-align: left;
|
||
padding: 8px;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
}
|
||
|
||
.property-table th {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.button-sm {
|
||
padding: 2px 8px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.modal {
|
||
display: none;
|
||
position: fixed;
|
||
z-index: 1000;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: auto;
|
||
background-color: rgba(0,0,0,0.4);
|
||
}
|
||
|
||
.modal-content {
|
||
background-color: #fefefe;
|
||
margin: 10% auto;
|
||
padding: 20px;
|
||
border: 1px solid #888;
|
||
width: 50%;
|
||
max-width: 500px;
|
||
border-radius: 5px;
|
||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.modal-title {
|
||
font-weight: bold;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.close {
|
||
color: #aaa;
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.close:hover {
|
||
color: black;
|
||
}
|
||
|
||
.modal-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.form-control {
|
||
width: 100%;
|
||
padding: 8px;
|
||
box-sizing: border-box;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.error-message {
|
||
color: #d32f2f;
|
||
background-color: #ffebee;
|
||
padding: 10px;
|
||
border-radius: 4px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.no-file-selected {
|
||
color: #888;
|
||
font-style: italic;
|
||
text-align: center;
|
||
margin-top: 80px;
|
||
}
|
||
|
||
/* 命令表格相关样式 */
|
||
.command-table-container {
|
||
margin-top: 10px;
|
||
width: 100%;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.command-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.command-table th,
|
||
.command-table td {
|
||
padding: 8px;
|
||
text-align: left;
|
||
border: 1px solid #ddd;
|
||
}
|
||
|
||
.command-table th {
|
||
background-color: #f5f5f5;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.command-table tr:nth-child(even) {
|
||
background-color: #f9f9f9;
|
||
}
|
||
|
||
.command-table tr:hover {
|
||
background-color: #f1f1f1;
|
||
}
|
||
|
||
/* 命令表格单元格和行样式 */
|
||
.command-cell {
|
||
padding: 8px;
|
||
text-align: left;
|
||
border: 1px solid #ddd;
|
||
vertical-align: middle;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.command-row {
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.action-cell {
|
||
text-align: center;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.command-actions {
|
||
display: flex;
|
||
gap: 5px;
|
||
justify-content: center;
|
||
}
|
||
|
||
.command-action-button {
|
||
padding: 3px 8px;
|
||
font-size: 12px;
|
||
border: none;
|
||
border-radius: 3px;
|
||
cursor: pointer;
|
||
transition: background-color 0.2s;
|
||
color: white;
|
||
}
|
||
|
||
.edit-button {
|
||
background-color: #7986E7;
|
||
}
|
||
|
||
.edit-button:hover {
|
||
background-color: #6875D6;
|
||
}
|
||
|
||
.delete-button {
|
||
background-color: #E77979;
|
||
}
|
||
|
||
.delete-button:hover {
|
||
background-color: #D66868;
|
||
}
|
||
|
||
/* 输入容器样式 */
|
||
.input-container {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
/* 图标按钮样式 */
|
||
.icon-button {
|
||
background-color: transparent;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
width: 28px;
|
||
height: 28px;
|
||
margin-left: 8px;
|
||
background-size: contain;
|
||
background-repeat: no-repeat;
|
||
background-position: center;
|
||
opacity: 0.7;
|
||
transition: opacity 0.2s;
|
||
}
|
||
|
||
.icon-button:hover {
|
||
opacity: 1;
|
||
}
|
||
|
||
.calendar-button {
|
||
background-image: url('assets/icons/png/calendar_b.png');
|
||
width: 24px;
|
||
height: 24px;
|
||
background-size: 16px;
|
||
}
|
||
|
||
.refresh-button-sm {
|
||
background-image: url('assets/icons/png/refresh_b.png');
|
||
width: 24px;
|
||
height: 24px;
|
||
background-size: 16px;
|
||
}
|
||
|
||
.calendar-day.selected {
|
||
background-color: #7986E7;
|
||
color: white;
|
||
}
|
||
|
||
/* 其它设置部分样式 */
|
||
.other-settings-container {
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.element-row {
|
||
display: flex;
|
||
margin-bottom: 8px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.element-box {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
background-color: #f9f9f9;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
padding: 8px;
|
||
width: 100%;
|
||
}
|
||
|
||
.toggle-button {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
margin-right: 5px;
|
||
font-size: 14px;
|
||
color: #666;
|
||
width: 20px;
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.toggle-spacer {
|
||
width: 20px;
|
||
}
|
||
|
||
.element-name {
|
||
font-weight: 500;
|
||
margin-right: 15px;
|
||
padding: 5px 0;
|
||
color: #333;
|
||
min-width: 100px;
|
||
}
|
||
|
||
.element-value-container {
|
||
flex: 1;
|
||
display: flex;
|
||
}
|
||
|
||
.element-value-input {
|
||
flex: 1;
|
||
padding: 5px 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.element-actions {
|
||
display: flex;
|
||
gap: 5px;
|
||
margin-left: auto;
|
||
}
|
||
|
||
.element-action-button {
|
||
padding: 3px 8px;
|
||
font-size: 12px;
|
||
border: none;
|
||
border-radius: 3px;
|
||
background-color: #7986E7;
|
||
color: white;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.element-action-button:hover {
|
||
background-color: #6875D6;
|
||
}
|
||
|
||
.form-info {
|
||
margin-top: 15px;
|
||
padding: 10px;
|
||
background-color: #f5f5f5;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.form-info-text {
|
||
color: #666;
|
||
margin: 0;
|
||
font-size: 14px;
|
||
}
|
||
</style>
|
||
<div class="config-container">
|
||
<div class="config-header">
|
||
<div class="file-selector-header">
|
||
<div class="file-selector-label">模型配置文件:</div>
|
||
<select id="fileSelector">
|
||
<option value="">-- 请选择模型配置文件 --</option>
|
||
</select>
|
||
<button class="refresh-button" id="refreshFiles" title="刷新文件列表"></button>
|
||
</div>
|
||
<div class="action-buttons">
|
||
<button class="action-button" id="newConfig">新建</button>
|
||
<button class="action-button save-button" id="saveConfig">保存</button>
|
||
<button class="action-button" id="saveAsConfig">另存为</button>
|
||
</div>
|
||
</div>
|
||
<div class="config-content">
|
||
<div id="contentArea">
|
||
<div class="no-file-selected">请选择一个模型配置文件查看内容</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
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 = `<div class="error-message">
|
||
无法编辑:XML文档的根元素不是Model。
|
||
请确保XML文档的根元素是Model。
|
||
</div>`;
|
||
}
|
||
|
||
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 += `<option value="${idx}" ${idx === month ? 'selected' : ''}>${name}</option>`;
|
||
});
|
||
|
||
// 生成年份选项
|
||
let yearOptions = '';
|
||
const currentYear = new Date().getFullYear();
|
||
for (let y = currentYear - 10; y <= currentYear + 10; y++) {
|
||
yearOptions += `<option value="${y}" ${y === year ? 'selected' : ''}>${y}</option>`;
|
||
}
|
||
|
||
// 生成日历表格
|
||
let calendarRows = '';
|
||
let dayCount = 1;
|
||
|
||
// 添加表头
|
||
calendarRows += '<tr>';
|
||
['日', '一', '二', '三', '四', '五', '六'].forEach(dayName => {
|
||
calendarRows += `<th>${dayName}</th>`;
|
||
});
|
||
calendarRows += '</tr>';
|
||
|
||
// 计算行数
|
||
const totalCells = firstDay + daysInMonth;
|
||
const rowCount = Math.ceil(totalCells / 7);
|
||
|
||
// 添加日期行
|
||
for (let i = 0; i < rowCount; i++) {
|
||
calendarRows += '<tr>';
|
||
for (let j = 0; j < 7; j++) {
|
||
if ((i === 0 && j < firstDay) || dayCount > daysInMonth) {
|
||
calendarRows += '<td></td>';
|
||
} else {
|
||
const isToday = dayCount === day;
|
||
calendarRows += `<td class="calendar-day ${isToday ? 'selected' : ''}" data-day="${dayCount}">${dayCount}</td>`;
|
||
dayCount++;
|
||
}
|
||
}
|
||
calendarRows += '</tr>';
|
||
}
|
||
|
||
modal.innerHTML = `
|
||
<div class="modal-content" style="max-width: 400px;">
|
||
<div class="modal-header">
|
||
<div class="modal-title">选择日期和时间</div>
|
||
<span class="close">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="calendar-container">
|
||
<div class="calendar-header">
|
||
<select id="calendarMonth">${monthOptions}</select>
|
||
<select id="calendarYear">${yearOptions}</select>
|
||
</div>
|
||
<table class="calendar-table">
|
||
<thead>
|
||
${calendarRows}
|
||
</thead>
|
||
</table>
|
||
</div>
|
||
<div class="time-container" style="margin-top: 15px;">
|
||
<label for="timeInput">时间:</label>
|
||
<input type="time" id="timeInput" step="1" value="${currentTime}" style="width: 100%; padding: 8px;">
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button id="cancelDateTime" class="action-button secondary">取消</button>
|
||
<button id="confirmDateTime" class="action-button">确定</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// 添加样式
|
||
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 = `
|
||
<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';
|
||
|
||
// 渲染所有元素
|
||
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 = `
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<div class="modal-title">添加元素</div>
|
||
<span class="close">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label for="elementName">元素名称</label>
|
||
<input type="text" id="elementName" class="form-control" placeholder="输入元素名称" />
|
||
<div id="elementNameError" class="error-message" style="display: none; font-size: 12px; margin-top: 5px;"></div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="elementValue">元素值 <small>(如果需要)</small></label>
|
||
<input type="text" id="elementValue" class="form-control" placeholder="输入元素值" />
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button id="cancelAddElement" class="action-button secondary">取消</button>
|
||
<button id="confirmAddElement" class="action-button">添加</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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 = `
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<div class="modal-title">编辑元素</div>
|
||
<span class="close">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label for="elementPath">元素路径</label>
|
||
<input type="text" id="elementPath" class="form-control" value="${elementPath}" readonly />
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="elementValue">元素值</label>
|
||
<input type="text" id="elementValue" class="form-control" value="${this.escapeHtml(element.textContent)}" placeholder="输入元素值" />
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button id="cancelEditElement" class="action-button secondary">取消</button>
|
||
<button id="confirmEditElement" class="action-button">保存</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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 = `
|
||
<label for="commandListTable">命令列表</label>
|
||
<div id="commandListContainer" class="command-table-container">
|
||
<table id="commandListTable" class="command-table">
|
||
<thead>
|
||
<tr>
|
||
<th>名称</th>
|
||
<th>调用</th>
|
||
<th>描述</th>
|
||
<th style="width: 100px;">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td colspan="4" style="text-align: center;">
|
||
<button id="addCommandButton" class="action-button">
|
||
添加命令
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
|
||
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 = `
|
||
<label for="commandListTable">命令列表</label>
|
||
<div id="commandListContainer" class="command-table-container">
|
||
<table id="commandListTable" class="command-table">
|
||
<thead>
|
||
<tr>
|
||
<th>名称</th>
|
||
<th>调用</th>
|
||
<th>描述</th>
|
||
<th style="width: 100px;">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<!-- 命令会在这里动态添加 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
|
||
container.appendChild(commandListSection);
|
||
|
||
const tableBody = commandListSection.querySelector('tbody');
|
||
|
||
// 获取命令列表
|
||
const commandElements = commandListElement.querySelectorAll('Command');
|
||
|
||
// 如果没有命令,添加"添加命令"按钮
|
||
if (commandElements.length === 0) {
|
||
tableBody.innerHTML = `
|
||
<tr>
|
||
<td colspan="4" style="text-align: center;">
|
||
<button id="addCommandButton" class="action-button">
|
||
添加命令
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
|
||
// 添加命令事件监听
|
||
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 = `
|
||
<td class="command-cell">${this.escapeHtml(name)}</td>
|
||
<td class="command-cell">${this.escapeHtml(call)}</td>
|
||
<td class="command-cell">${this.escapeHtml(description)}</td>
|
||
<td class="action-cell">
|
||
<div class="command-actions">
|
||
<button class="command-action-button edit-button">编辑</button>
|
||
<button class="command-action-button delete-button">删除</button>
|
||
</div>
|
||
</td>
|
||
`;
|
||
|
||
// 编辑按钮事件
|
||
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 = `
|
||
<td colspan="4" style="text-align: center;">
|
||
<button id="addCommandButton" class="action-button">
|
||
添加命令
|
||
</button>
|
||
</td>
|
||
`;
|
||
|
||
// 添加命令事件监听
|
||
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, """)
|
||
.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 = `
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<div class="modal-title">创建新模型配置</div>
|
||
<span class="close">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label for="newFileName">文件名</label>
|
||
<input type="text" id="newFileName" class="form-control" placeholder="输入文件名 (例如: mymodel.mcfg)" />
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button id="cancelNewConfig" class="action-button secondary">取消</button>
|
||
<button id="confirmNewConfig" class="action-button">创建</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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 = `
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<div class="modal-title">另存为</div>
|
||
<span class="close">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label for="saveAsFileName">文件名</label>
|
||
<input type="text" id="saveAsFileName" class="form-control" placeholder="输入文件名 (例如: mymodel.mcfg)" />
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button id="cancelSaveAs" class="action-button secondary">取消</button>
|
||
<button id="confirmSaveAs" class="action-button">保存</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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 = `
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<div class="modal-title">添加命令</div>
|
||
<span class="close">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label for="commandName">命令名称</label>
|
||
<input type="text" id="commandName" class="form-control" placeholder="输入命令名称" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="commandCall">命令调用</label>
|
||
<input type="text" id="commandCall" class="form-control" placeholder="输入命令调用" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="commandDescription">命令描述</label>
|
||
<input type="text" id="commandDescription" class="form-control" placeholder="输入命令描述" />
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button id="cancelAddCommand" class="action-button secondary">取消</button>
|
||
<button id="confirmAddCommand" class="action-button">添加</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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 = `
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<div class="modal-title">编辑命令</div>
|
||
<span class="close">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label for="commandName">命令名称</label>
|
||
<input type="text" id="commandName" class="form-control" value="${this.escapeHtml(name)}" placeholder="输入命令名称" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="commandCall">命令调用</label>
|
||
<input type="text" id="commandCall" class="form-control" value="${this.escapeHtml(call)}" placeholder="输入命令调用" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="commandDescription">命令描述</label>
|
||
<input type="text" id="commandDescription" class="form-control" value="${this.escapeHtml(description)}" placeholder="输入命令描述" />
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button id="cancelEditCommand" class="action-button secondary">取消</button>
|
||
<button id="confirmEditCommand" class="action-button">保存</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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 = `
|
||
<h3>命令列表</h3>
|
||
<button id="addCommandBtn" class="action-button">添加命令</button>
|
||
`;
|
||
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 = `
|
||
<tr>
|
||
<th>命令名称</th>
|
||
<th>命令调用</th>
|
||
<th>描述</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
`;
|
||
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 = '<td colspan="4" style="text-align: center;">没有命令记录</td>';
|
||
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 = `
|
||
<td class="command-cell">${this.escapeHtml(name)}</td>
|
||
<td class="command-cell">${this.escapeHtml(call)}</td>
|
||
<td class="command-cell">${this.escapeHtml(description)}</td>
|
||
<td class="command-actions">
|
||
<button class="action-button edit-command">编辑</button>
|
||
<button class="action-button delete-command">删除</button>
|
||
</td>
|
||
`;
|
||
|
||
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 = '<td colspan="4" style="text-align: center;">没有命令记录</td>';
|
||
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);
|