XNSim/XNSimPortal/components/model-development.js
2025-07-08 13:45:48 +08:00

3015 lines
129 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

class ModelDevelopment extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.chapters = [];
this.currentModels = [];
this.currentModelVersions = [];
this.currentVersion = null;
this.currentView = 'chapters'; // 'chapters', 'models', 'versions', 'versionForm'
this.currentChapter = null;
this.currentModel = null;
this.isEditMode = false;
// 初始化DOM结构
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
padding: 20px;
color: #333;
box-sizing: border-box;
height: calc(100vh - 40px); /* 限制总高度 */
overflow: hidden; /* 防止整体滚动 */
}
*, *:before, *:after {
box-sizing: inherit;
}
.page-title {
color: #5c6bc0;
margin-bottom: 20px;
font-weight: 500;
font-size: 24px;
}
.subtitle {
font-size: 16px;
color: #666;
font-weight: normal;
margin-left: 10px;
}
.model-dev-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
max-height: calc(100vh - 200px); /* 进一步减小容器高度 */
overflow-y: auto;
padding-right: 10px;
padding-top: 10px; /* 添加上边距,解决悬停时上边界问题 */
padding-bottom: 30px; /* 添加底部边距,确保最后一行完全可见 */
width: 100%;
margin-bottom: 50px; /* 大幅增加底部边距,确保滚动条箭头完全可见 */
}
.chapter-card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
cursor: pointer;
height: 160px; /* 增加高度以容纳更多内容 */
display: flex;
flex-direction: column;
overflow: hidden; /* 防止内容溢出 */
}
.chapter-card:hover {
transform: translateY(-3px); /* 减小上移距离,防止超出可见区域 */
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
}
.chapter-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 10px;
color: #333;
}
.chapter-subtitle {
font-size: 16px;
color: #666;
}
.model-card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: transform 0.3s ease;
cursor: pointer;
height: 160px; /* 与章节卡片保持一致 */
display: flex;
flex-direction: column;
overflow: hidden; /* 防止内容溢出 */
}
.model-card:hover {
transform: translateY(-3px);
}
.model-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
color: #333;
}
.model-subtitle {
font-size: 15px;
color: #555;
margin-bottom: 12px;
}
.model-description {
font-size: 14px;
color: #666;
margin-bottom: 12px;
}
.model-class {
font-size: 13px;
color: #888;
font-style: italic;
}
.version-card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: transform 0.3s ease;
cursor: pointer;
height: 100%;
display: flex;
flex-direction: column;
}
.version-card:hover {
transform: translateY(-3px);
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12);
}
.version-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.version-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.version-number {
font-size: 15px;
font-weight: 500;
color: #5c6bc0;
padding: 3px 8px;
background: #eef0ff;
border-radius: 4px;
}
.version-details {
display: grid;
grid-template-columns: 1fr;
gap: 6px;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #eee;
}
.version-detail {
font-size: 14px;
color: #666;
}
.label {
font-weight: 500;
color: #555;
margin-right: 5px;
}
.version-description {
font-size: 14px;
color: #666;
line-height: 1.5;
flex-grow: 1;
}
.back-button {
grid-column: 1 / -1;
cursor: pointer;
color: #667eea;
font-weight: 500;
margin-bottom: 15px;
}
.back-button:hover {
text-decoration: underline;
}
.add-version-button {
background-color: #667eea;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
margin-bottom: 20px;
}
.add-version-button:hover {
background-color: #5c6bc0;
}
.models-grid, .versions-grid {
display: grid;
grid-column: 1 / -1;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
grid-auto-rows: 1fr; /* 确保每行高度一致 */
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-group textarea {
min-height: 100px;
resize: vertical;
}
.form-actions {
margin-top: 20px;
text-align: right;
}
.save-button {
background-color: #667eea;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 14px;
cursor: pointer;
}
.save-button:hover {
background-color: #5c6bc0;
}
.version-form {
grid-column: 1 / -1;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
width: 100%;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
grid-column: 1 / -1;
}
.error-state {
text-align: center;
padding: 40px;
color: #e74c3c;
grid-column: 1 / -1;
}
.retry-btn {
background: #667eea;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
font-size: 14px;
margin-top: 15px;
}
.empty-state {
text-align: center;
padding: 40px;
color: #666;
grid-column: 1 / -1;
}
/* 新建版本卡片样式 */
.new-version-card {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
background-color: #f8fafc;
border: 1px solid #cbd5e1;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: all 0.3s ease;
}
.new-version-card:hover {
background-color: #f1f5f9;
border-color: #94a3b8;
transform: translateY(-3px);
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12);
}
.new-version-icon {
display: flex;
justify-content: center;
align-items: center;
width: 50px;
height: 50px;
background-color: #eef2ff;
border-radius: 50%;
margin-bottom: 12px;
}
.new-version-icon svg {
width: 25px;
height: 25px;
fill: #6366f1;
}
.new-version-title {
font-size: 16px;
font-weight: 600;
color: #4f46e5;
margin-bottom: 5px;
}
.new-version-description {
font-size: 13px;
color: #64748b;
line-height: 1.4;
margin-bottom: 0;
}
</style>
<div class="model-development">
<h2 class="page-title">ATA章节</h2>
<div class="model-dev-container">
<div class="loading">正在加载数据...</div>
</div>
</div>
`;
// 开始初始化数据
this.init();
}
async init() {
try {
const response = await fetch('/api/ata-chapters');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
this.chapters = await response.json();
this.renderChapters();
} catch (error) {
console.error('初始化模型集成页面失败:', error);
this.renderError('加载ATA章节失败请稍后重试。');
}
}
async fetchModels(chapterId) {
try {
const savedSelection = localStorage.getItem('xnsim-selection');
const selection = savedSelection ? JSON.parse(savedSelection) : {};
const plane = selection.plane;
if (!plane) {
throw new Error('请先选择机型!');
}
const response = await fetch(`/api/chapter-models/${chapterId}?planeName=${encodeURIComponent(plane)}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
this.currentModels = await response.json();
this.currentChapter = this.chapters.find(chapter => chapter.ID === chapterId);
this.renderModels();
} catch (error) {
console.error(`获取章节${chapterId}的模型数据失败:`, error);
this.renderError(error.message);
}
}
async fetchModelVersions(className, modelName) {
try {
const savedSelection = localStorage.getItem('xnsim-selection');
const selection = savedSelection ? JSON.parse(savedSelection) : {};
const plane = selection.plane;
const confID = selection.configurationId;
if (!plane) {
throw new Error('请先选择机型!');
}
if (!confID) {
throw new Error('请先选择构型!');
}
const response = await fetch(`/api/model-versions/${encodeURIComponent(className)}?planeName=${encodeURIComponent(plane)}&confID=${encodeURIComponent(confID)}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
this.currentModelVersions = await response.json();
this.currentModel = { className, name: modelName };
this.renderModelVersions();
} catch (error) {
console.error(`获取模型${className}的版本数据失败:`, error);
this.renderError(error.message);
}
}
async saveModelVersion(versionData) {
try {
const response = await fetch('/api/model-versions', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(versionData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('保存模型版本失败:', error);
throw error;
}
}
renderChapters() {
this.currentView = 'chapters';
// 重新初始化Shadow DOM如果是首次初始化的情况
if (!this.shadowRoot.querySelector('.model-development')) {
this.initBasicStructure();
console.log('重新初始化DOM结构');
} else {
// 如果已经有DOM结构只更新标题
const title = this.shadowRoot.querySelector('.page-title');
if (title) {
title.textContent = 'ATA章节';
}
}
const container = this.shadowRoot.querySelector('.model-dev-container');
if (!container) {
console.error('找不到容器元素');
return;
}
container.innerHTML = '';
if (this.chapters.length === 0) {
const emptyState = document.createElement('div');
emptyState.className = 'empty-state';
emptyState.textContent = '未找到ATA章节数据';
container.appendChild(emptyState);
} else {
// 创建章节容器并添加底部边距
const chaptersGrid = document.createElement('div');
chaptersGrid.className = 'chapters-grid';
chaptersGrid.style.cssText = 'display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; grid-column: 1 / -1; width: 100%; padding-bottom: 40px;';
// 添加从后端获取的章节卡片
this.chapters.forEach((chapter, index) => {
const card = document.createElement('div');
card.className = 'chapter-card';
// 设置样式以确保卡片一致性
card.style.height = '160px';
card.style.display = 'flex';
card.style.flexDirection = 'column';
card.style.overflow = 'hidden';
// 确保最后一行的卡片有足够的下边距
if (index >= this.chapters.length - (this.chapters.length % 3 || 3)) {
card.style.marginBottom = '20px';
}
card.innerHTML = `
<div class="chapter-title">${chapter.ID} - ${chapter.Name}</div>
<div class="chapter-subtitle">${chapter.Name_CN || '无中文名称'}</div>
`;
card.addEventListener('click', () => this.fetchModels(chapter.ID));
chaptersGrid.appendChild(card);
});
container.appendChild(chaptersGrid);
}
}
renderModels() {
this.currentView = 'models';
// 确保DOM结构存在
if (!this.shadowRoot.querySelector('.model-development')) {
this.initBasicStructure();
console.log('重新初始化DOM结构');
}
// 更新标题为章节信息
const title = this.shadowRoot.querySelector('.page-title');
if (title) {
title.innerHTML = `${this.currentChapter.ID} - ${this.currentChapter.Name} <span class="subtitle">${this.currentChapter.Name_CN || ''}</span>`;
}
const container = this.shadowRoot.querySelector('.model-dev-container');
if (!container) {
console.error('找不到容器元素');
return;
}
container.innerHTML = '';
// 添加返回按钮
const backButton = document.createElement('div');
backButton.className = 'back-button';
backButton.innerHTML = `<span>← 返回章节列表</span>`;
backButton.addEventListener('click', () => this.renderChapters());
container.appendChild(backButton);
if (this.currentModels.length === 0) {
const emptyState = document.createElement('div');
emptyState.className = 'empty-state';
emptyState.textContent = '该章节下没有模型';
container.appendChild(emptyState);
} else {
// 创建模型卡片容器
const modelsGrid = document.createElement('div');
modelsGrid.className = 'models-grid';
modelsGrid.style.paddingBottom = '40px';
modelsGrid.style.marginBottom = '20px'; /* 添加底部边距,确保滚动条箭头完全可见 */
// 添加从后端获取的模型卡片
this.currentModels.forEach((model, index) => {
const card = document.createElement('div');
card.className = 'model-card';
// 设置样式以确保卡片一致性
card.style.height = '160px';
card.style.display = 'flex';
card.style.flexDirection = 'column';
card.style.overflow = 'hidden';
// 确保最后一行的卡片有足够的下边距
if (index >= this.currentModels.length - (this.currentModels.length % 3 || 3)) {
card.style.marginBottom = '20px';
}
card.innerHTML = `
<div class="model-title">${model.ModelName}</div>
<div class="model-subtitle">${model.ModelName_CN || '无中文名称'}</div>
<div class="model-description">${model.Description || '无描述'}</div>
<div class="model-class">类名: ${model.ClassName}</div>
`;
card.addEventListener('click', () => this.fetchModelVersions(model.ClassName, model.ModelName));
modelsGrid.appendChild(card);
});
container.appendChild(modelsGrid);
}
}
renderModelVersions() {
this.currentView = 'versions';
// 更新标题为"ATA章节号/模型名称"格式
const title = this.shadowRoot.querySelector('.page-title');
title.textContent = `${this.currentChapter.ID} / ${this.currentModel.name}`;
const container = this.shadowRoot.querySelector('.model-dev-container');
container.innerHTML = '';
// 添加返回按钮
const backButton = document.createElement('div');
backButton.className = 'back-button';
backButton.innerHTML = `<span>← 返回模型列表</span>`;
backButton.addEventListener('click', () => this.fetchModels(this.currentChapter.ID));
container.appendChild(backButton);
// 创建版本卡片容器
const versionsGrid = document.createElement('div');
versionsGrid.className = 'versions-grid';
// 添加新建模型版本卡片(始终是第一个)
const newVersionCard = document.createElement('div');
newVersionCard.className = 'version-card new-version-card';
newVersionCard.style.height = '100%'; // 确保高度与其他卡片一致
newVersionCard.innerHTML = `
<div class="new-version-icon">
<svg viewBox="0 0 24 24">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>
</div>
<div class="new-version-title">新建模型版本</div>
<div class="new-version-description" style="margin-bottom: 0;">为${this.currentModel.name}创建新的版本</div>
`;
newVersionCard.addEventListener('click', () => this.createNewVersion());
versionsGrid.appendChild(newVersionCard);
if (this.currentModelVersions.length === 0) {
const emptyState = document.createElement('div');
emptyState.className = 'empty-state';
emptyState.textContent = '该模型没有版本信息,点击"新建模型版本"创建第一个版本';
versionsGrid.appendChild(emptyState);
} else {
// 对版本进行排序 - 按版本号从高到低排序
const sortedVersions = [...this.currentModelVersions].sort((a, b) => {
return this.compareVersions(b.Version, a.Version);
});
// 添加从后端获取的版本卡片
sortedVersions.forEach((version, index) => {
const card = document.createElement('div');
card.className = 'version-card';
card.style.height = '100%'; // 设置统一高度
// 确保最后一行的卡片有足够的下边距
if (index >= sortedVersions.length - (sortedVersions.length % 3 || 3)) {
card.style.marginBottom = '20px';
}
card.innerHTML = `
<div class="version-header">
<div class="version-title">${version.Name || this.currentModel.name}</div>
<div class="version-number">v${version.Version || '未知版本'}</div>
</div>
<div class="version-details">
<div class="version-detail"><span class="label">作者:</span> ${version.Author || '未知'}</div>
<div class="version-detail"><span class="label">创建时间:</span> ${version.CreatTime || '未知'}</div>
<div class="version-detail"><span class="label">修改时间:</span> ${version.ChangeTime || '未知'}</div>
</div>
<div class="version-description">${version.Description || '无描述'}</div>
`;
card.addEventListener('click', () => this.showVersionEditor(version));
versionsGrid.appendChild(card);
});
}
// 添加额外的下边距,确保最后一行完全可见
versionsGrid.style.paddingBottom = '40px';
versionsGrid.style.marginBottom = '20px'; /* 添加底部边距,确保滚动条箭头完全可见 */
container.appendChild(versionsGrid);
}
showVersionEditor(versionData) {
this.currentView = 'versionEditor';
// 确保 CmdList 是数组
if (versionData.CmdList) {
try {
// 如果 CmdList 是字符串,尝试解析它
if (typeof versionData.CmdList === 'string') {
versionData.CmdList = JSON.parse(versionData.CmdList);
}
// 确保 CmdList 是数组
if (!Array.isArray(versionData.CmdList)) {
versionData.CmdList = [];
}
} catch (e) {
console.error('解析 CmdList 失败:', e);
versionData.CmdList = [];
}
} else {
versionData.CmdList = [];
}
this.currentVersion = versionData;
this.isEditMode = versionData && versionData.Version ? true : false;
const container = this.shadowRoot.querySelector('.model-dev-container');
if (!container) {
console.error('找不到容器元素');
return;
}
// 更新标题
const title = this.shadowRoot.querySelector('.page-title');
if (title) {
title.textContent = this.isEditMode
? `编辑版本: ${versionData.Name} (v${versionData.Version})`
: `${this.currentModel.name} 创建新版本`;
}
container.innerHTML = '';
// 添加工具栏按钮
const toolbarDiv = document.createElement('div');
toolbarDiv.className = 'editor-toolbar';
toolbarDiv.style.cssText = 'display: flex; justify-content: space-between; margin-bottom: 20px; grid-column: 1 / -1; width: 100%;';
// 添加返回按钮
const backButton = document.createElement('div');
backButton.className = 'back-button';
backButton.innerHTML = `<span>← 返回版本列表</span>`;
backButton.style.cssText = 'margin: 0; cursor: pointer; color: #667eea; font-weight: 500;';
backButton.addEventListener('click', () => {
this.fetchModelVersions(this.currentModel.className, this.currentModel.name);
});
// 添加按钮组
const actionsDiv = document.createElement('div');
actionsDiv.className = 'toolbar-actions';
actionsDiv.style.cssText = 'display: flex; gap: 10px;';
// 创建各种按钮
const buttonConfigs = [
{
text: '刷新',
color: '#38a169',
action: async () => {
try {
// 保存当前版本ID用于刷新后重新定位
const versionId = this.currentVersion.Version;
// 先刷新版本列表
await this.fetchModelVersions(this.currentModel.className, this.currentModel.name);
// 然后找到当前编辑的版本并重新打开
if (versionId) {
const refreshedVersion = this.currentModelVersions.find(v => v.Version === versionId);
if (refreshedVersion) {
this.showVersionEditor(refreshedVersion);
} else {
alert('未能找到当前版本,可能已被删除');
// 如果找不到当前版本,回到版本列表
this.renderModelVersions();
}
}
} catch (error) {
console.error('刷新版本数据失败:', error);
alert(`刷新失败: ${error.message}`);
}
}
},
{
text: '保存',
color: '#3182ce',
action: () => {
try {
// 获取表单元素
const form = this.shadowRoot.querySelector('#versionEditorForm');
if (form) {
// 触发提交事件
form.dispatchEvent(new Event('submit'));
} else {
console.error('找不到表单元素');
alert('保存失败: 找不到表单元素');
}
} catch (error) {
console.error('保存时发生错误:', error);
alert(`保存失败: ${error.message}`);
}
}
},
{
text: '数据包模型上传',
color: '#dd6b20',
action: () => this.uploadDataPackage()
},
{
text: '模板代码生成',
color: '#805ad5',
action: () => this.generateTemplateCode()
},
{
text: '模板代码下载',
color: '#3182ce',
action: () => this.downloadTemplateCode()
},
{
text: '集成代码上传',
color: '#38a169',
action: () => this.uploadWrapperCode()
},
{
text: '模型编译发布',
color: '#e53e3e',
action: () => this.compileAndPublishModel()
}
];
buttonConfigs.forEach(config => {
const button = document.createElement('button');
button.className = 'toolbar-button';
button.textContent = config.text;
button.style.cssText = `background-color: ${config.color}; color: white; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer; font-weight: 500;`;
button.addEventListener('click', config.action);
actionsDiv.appendChild(button);
});
toolbarDiv.appendChild(backButton);
toolbarDiv.appendChild(actionsDiv);
container.appendChild(toolbarDiv);
// 创建表单容器 - 使用网格布局
const formContainer = document.createElement('div');
formContainer.className = 'form-container';
formContainer.style.cssText = 'display: grid; grid-template-columns: 1fr 1fr; gap: 20px; grid-column: 1 / -1; width: 100%;';
// 创建表单
const form = document.createElement('form');
form.className = 'version-form';
form.id = 'versionEditorForm';
form.style.cssText = 'background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); padding: 20px; grid-column: 1 / -1;';
// 创建两列布局的基本信息部分
const basicInfoSection = document.createElement('div');
basicInfoSection.className = 'form-section basic-info';
basicInfoSection.style.cssText = 'display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;';
// 左列 - 基本信息
const leftColumn = document.createElement('div');
leftColumn.className = 'form-column';
leftColumn.innerHTML = `
<div class="form-group">
<label for="className">类名 (ClassName)*</label>
<input type="text" id="className" name="ClassName" value="${this.currentVersion.ClassName || ''}" readonly title="模型类名,不可修改" required>
</div>
<div class="form-group">
<label for="name">名称 (Name)*</label>
<input type="text" id="name" name="Name" value="${this.currentVersion.Name || ''}" readonly title="模型名称,不可修改" required>
</div>
<div class="form-group">
<label for="version">版本 (Version)*</label>
<input type="text" id="version" name="Version" value="${this.currentVersion.Version || '1.0.0'}" pattern="[0-9.]+" title="版本号只能包含数字和小数点例如1.0.0" required>
</div>
`;
// 右列 - 描述和其他信息
const rightColumn = document.createElement('div');
rightColumn.className = 'form-column';
rightColumn.innerHTML = `
<div class="form-group">
<label for="description">描述 (Description)</label>
<input type="text" id="description" name="Description" value="${this.currentVersion.Description || ''}" title="模型版本的详细描述,包括主要功能和改进">
</div>
<div class="form-group">
<label for="author">作者 (Author)*</label>
<input type="text" id="author" name="Author" value="${this.currentVersion.Author || ''}" title="模型版本的作者姓名" required>
</div>
<div class="form-group">
<label for="changeTime">修改时间 (ChangeTime)*</label>
<div style="display: flex; gap: 8px;">
<input type="text" id="changeTime" name="ChangeTime" value="${this.currentVersion.ChangeTime || this.getCurrentDateTime()}" title="最后修改时间" style="flex: 1;" required>
<button type="button" id="dateTimePickerButton" style="background-color: #667eea; color: white; border: none; border-radius: 4px; width: 40px; display: flex; justify-content: center; align-items: center;">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
</button>
</div>
</div>
`;
basicInfoSection.appendChild(leftColumn);
basicInfoSection.appendChild(rightColumn);
// 重新设计的高级设置部分 - 使用网格布局,去掉标题
const advancedSection = document.createElement('div');
advancedSection.className = 'form-section advanced';
advancedSection.style.cssText = 'border-top: 1px solid #e2e8f0; padding-top: 20px; margin-top: 10px;';
// 创建网格容器
const gridLayout = document.createElement('div');
gridLayout.style.cssText = 'display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;';
// 添加左列输入框
const leftAdvancedColumn = document.createElement('div');
leftAdvancedColumn.innerHTML = `
<div class="form-group">
<label for="dataPackagePath">数据包路径 (DataPackagePath)</label>
<input type="text" id="dataPackagePath" name="DataPackagePath" value="${this.currentVersion.DataPackagePath || ''}" title="数据包文件的路径">
</div>
<div class="form-group">
<label for="dataPackageName">数据包名称 (DataPackageName)</label>
<input type="text" id="dataPackageName" name="DataPackageName" value="${this.currentVersion.DataPackageName || ''}" title="数据包的名称">
</div>
<div class="form-group">
<label for="dataPackageHeaderName">数据包头文件名称 (DataPackageHeaderName)</label>
<input type="text" id="dataPackageHeaderName" name="DataPackageHeaderName" value="${this.currentVersion.DataPackageHeaderName || ''}" title="数据包头文件的名称">
</div>
`;
// 添加右列输入框
const rightAdvancedColumn = document.createElement('div');
rightAdvancedColumn.innerHTML = `
<div class="form-group">
<label for="runFreqGroup">运行频率组 (RunFreqGroup)</label>
<select id="runFreqGroup" name="RunFreqGroup" title="模型的运行频率组">
<option value="0" ${this.currentVersion.RunFreqGroup === '0' ? 'selected' : ''}>基频 (0)</option>
<option value="1" ${this.currentVersion.RunFreqGroup === '1' ? 'selected' : ''}>半频 (1)</option>
<option value="2" ${this.currentVersion.RunFreqGroup === '2' ? 'selected' : ''}>1/4频 (2)</option>
<option value="3" ${this.currentVersion.RunFreqGroup === '3' ? 'selected' : ''}>1/8频 (3)</option>
<option value="4" ${this.currentVersion.RunFreqGroup === '4' ? 'selected' : ''}>1/16频 (4)</option>
<option value="5" ${this.currentVersion.RunFreqGroup === '5' ? 'selected' : ''}>1/32频 (5)</option>
</select>
</div>
<div class="form-group">
<label for="runNode">运行节点 (RunNode)</label>
<select id="runNode" name="RunNode" title="模型的运行节点">
<!-- 选项将由JavaScript动态生成 -->
</select>
</div>
<div class="form-group">
<label for="priority">优先级 (Priority)</label>
<input type="number" id="priority" name="Priority" value="${this.currentVersion.Priority || '0'}" min="0" max="99" title="模型运行的优先级范围0-99">
</div>
`;
// 第三行 - 添加剩余输入框
const bottomRow = document.createElement('div');
bottomRow.style.cssText = 'grid-column: span 2; display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;';
const bottomLeftColumn = document.createElement('div');
bottomLeftColumn.innerHTML = `
<div class="form-group">
<label for="dataPackageEntryPoint">数据包入口点 (DataPackageEntryPoint)</label>
<input type="text" id="dataPackageEntryPoint" name="DataPackageEntryPoint" value="${this.currentVersion.DataPackageEntryPoint || ''}" title="数据包的入口函数名称">
</div>
`;
const bottomRightColumn = document.createElement('div');
bottomRightColumn.innerHTML = `
<div class="form-group">
<label for="dataPackageInterfaceName">数据包接口参数名称 (DataPackageInterfaceName)</label>
<input type="text" id="dataPackageInterfaceName" name="DataPackageInterfaceName" value="${this.currentVersion.DataPackageInterfaceName || ''}" title="数据包接口参数的名称">
</div>
`;
bottomRow.appendChild(bottomLeftColumn);
bottomRow.appendChild(bottomRightColumn);
// 装配高级设置部分
gridLayout.appendChild(leftAdvancedColumn);
gridLayout.appendChild(rightAdvancedColumn);
advancedSection.appendChild(gridLayout);
advancedSection.appendChild(bottomRow);
// 添加指令列表参数表格
const cmdListSection = document.createElement('div');
cmdListSection.className = 'form-section cmd-list';
cmdListSection.style.cssText = 'border-top: 1px solid #e2e8f0; padding-top: 20px; margin-top: 20px;';
// 添加结构体参数部分
const structSection = document.createElement('div');
structSection.className = 'form-section struct-params';
structSection.style.cssText = 'border-top: 1px solid #e2e8f0; padding-top: 20px; margin-top: 20px;';
// 创建结构体参数标题
const structHeader = document.createElement('div');
structHeader.style.cssText = 'margin-bottom: 15px;';
structHeader.innerHTML = '<label style="font-weight: 500; color: #333;">结构体对应关系</label>';
// 创建结构体参数网格布局
const structGrid = document.createElement('div');
structGrid.style.cssText = 'display: grid; grid-template-columns: auto repeat(3, 1fr); gap: 20px;';
// 添加六个下拉框,按类型分组
structGrid.innerHTML = `
<div class="label-column" style="display: flex; flex-direction: column; justify-content: flex-start; gap: 30px; padding-top: 30px;">
<div style="font-size: 16px; color: #333; white-space: nowrap;">数据库中:</div>
<div style="font-size: 16px; color: #333; white-space: nowrap;">头文件中:</div>
</div>
<div class="struct-column">
<div class="struct-type-header" style="font-weight: 600; color: #333; margin-bottom: 10px; text-align: center;">输入</div>
<div class="form-group">
<select id="inputStructMapping1" name="InputStructMapping1" title="选择输入结构体对应关系1" style="width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<option value="">请选择...</option>
</select>
</div>
<div class="form-group">
<select id="inputStructMapping2" name="InputStructMapping2" title="选择输入结构体对应关系2" style="width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<option value="">请选择...</option>
</select>
</div>
</div>
<div class="struct-column">
<div class="struct-type-header" style="font-weight: 600; color: #333; margin-bottom: 10px; text-align: center;">输出</div>
<div class="form-group">
<select id="outputStructMapping1" name="OutputStructMapping1" title="选择输出结构体对应关系1" style="width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<option value="">请选择...</option>
</select>
</div>
<div class="form-group">
<select id="outputStructMapping2" name="OutputStructMapping2" title="选择输出结构体对应关系2" style="width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<option value="">请选择...</option>
</select>
</div>
</div>
<div class="struct-column">
<div class="struct-type-header" style="font-weight: 600; color: #333; margin-bottom: 10px; text-align: center;">心跳</div>
<div class="form-group">
<select id="heartStructMapping1" name="HeartStructMapping1" title="选择心跳结构体对应关系1" style="width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<option value="">请选择...</option>
</select>
</div>
<div class="form-group">
<select id="heartStructMapping2" name="HeartStructMapping2" title="选择心跳结构体对应关系2" style="width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<option value="">请选择...</option>
</select>
</div>
</div>
`;
structSection.appendChild(structHeader);
structSection.appendChild(structGrid);
// 创建表格标题和工具栏
const cmdListHeader = document.createElement('div');
cmdListHeader.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;';
cmdListHeader.innerHTML = `
<label>指令列表 (CmdList)</label>
<button type="button" id="addCmdBtn" style="background-color: #667eea; color: white; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer;">
添加指令
</button>
`;
// 创建表格
const cmdListTable = document.createElement('table');
cmdListTable.style.cssText = 'width: 100%; border-collapse: collapse; margin-top: 10px;';
cmdListTable.innerHTML = `
<thead>
<tr style="background-color: #f8fafc;">
<th style="padding: 8px; text-align: left; border: 1px solid #e2e8f0;">名称</th>
<th style="padding: 8px; text-align: left; border: 1px solid #e2e8f0;">描述</th>
<th style="padding: 8px; text-align: left; border: 1px solid #e2e8f0;">调用函数</th>
<th style="padding: 8px; text-align: center; border: 1px solid #e2e8f0; width: 80px;">操作</th>
</tr>
</thead>
<tbody id="cmdListBody">
${this.renderCmdListRows()}
</tbody>
`;
cmdListSection.appendChild(cmdListHeader);
cmdListSection.appendChild(cmdListTable);
// 表单操作按钮
const formActions = document.createElement('div');
formActions.className = 'form-actions';
formActions.style.cssText = 'margin-top: 30px; text-align: right; border-top: 1px solid #e2e8f0; padding-top: 20px;';
// 组装表单
form.appendChild(basicInfoSection);
form.appendChild(advancedSection);
form.appendChild(structSection);
form.appendChild(cmdListSection);
form.appendChild(formActions);
formContainer.appendChild(form);
container.appendChild(formContainer);
// 为添加指令按钮添加事件监听器
setTimeout(() => {
const addCmdBtn = this.shadowRoot.querySelector('#addCmdBtn');
if (addCmdBtn) {
addCmdBtn.addEventListener('click', () => {
this.addCmdListRow();
});
}
// 为删除按钮添加事件监听器
const deleteButtons = this.shadowRoot.querySelectorAll('.delete-cmd-btn');
deleteButtons.forEach(button => {
button.addEventListener('click', (e) => {
const index = parseInt(e.target.dataset.index);
this.deleteCmdListRow(index);
});
});
}, 0);
// 为日期时间按钮添加事件监听器
setTimeout(() => {
const dateTimePickerButton = this.shadowRoot.querySelector('#dateTimePickerButton');
const changeTimeInput = this.shadowRoot.querySelector('#changeTime');
if (dateTimePickerButton && changeTimeInput) {
dateTimePickerButton.addEventListener('click', () => {
this.showDateTimeDialog(changeTimeInput);
});
}
// 动态生成运行节点选项
this.updateRunNodeOptions();
// 当运行频率组变化时,更新运行节点选项
const runFreqGroup = this.shadowRoot.querySelector('#runFreqGroup');
if (runFreqGroup) {
runFreqGroup.addEventListener('change', () => {
this.updateRunNodeOptions();
});
}
// 填充数据库中结构体下拉框
this.populateDatabaseStructDropdowns();
}, 0);
// 添加表单提交事件
form.addEventListener('submit', async (e) => {
e.preventDefault();
try {
const savedSelection = localStorage.getItem('xnsim-selection');
const selection = savedSelection ? JSON.parse(savedSelection) : {};
// 获取工具栏中的保存按钮
const saveButton = this.shadowRoot.querySelector('.toolbar-button:nth-child(2)');
if (saveButton) {
const originalButtonText = saveButton.textContent;
saveButton.textContent = '保存中...';
saveButton.disabled = true;
}
// 验证版本号格式
const versionInput = form.querySelector('#version');
if (versionInput && !/^[0-9.]+$/.test(versionInput.value)) {
throw new Error('版本号只能包含数字和小数点');
}
// 验证优先级范围
const priorityInput = form.querySelector('#priority');
if (priorityInput) {
const priority = parseInt(priorityInput.value);
if (isNaN(priority) || priority < 0 || priority > 99) {
throw new Error('优先级必须是0-99之间的整数');
}
}
// 验证结构体字段的JSON格式
const inputStructInput = form.querySelector('#inputStruct');
if (inputStructInput && inputStructInput.value.trim() !== '') {
try {
JSON.parse(inputStructInput.value);
} catch (e) {
throw new Error('InputStruct 必须是有效的 JSON 格式');
}
}
const outputStructInput = form.querySelector('#outputStruct');
if (outputStructInput && outputStructInput.value.trim() !== '') {
try {
JSON.parse(outputStructInput.value);
} catch (e) {
throw new Error('OutputStruct 必须是有效的 JSON 格式');
}
}
const heartStructInput = form.querySelector('#heartStruct');
if (heartStructInput && heartStructInput.value.trim() !== '') {
try {
JSON.parse(heartStructInput.value);
} catch (e) {
throw new Error('HeartStruct 必须是有效的 JSON 格式');
}
}
// 收集指令列表数据
const cmdList = [];
const cmdRows = this.shadowRoot.querySelectorAll('#cmdListBody tr');
cmdRows.forEach(row => {
const nameInput = row.querySelector('input[name^="cmdList"][name$=".Name"]');
const descriptionInput = row.querySelector('input[name^="cmdList"][name$=".Description"]');
const callInput = row.querySelector('input[name^="cmdList"][name$=".Call"]');
if (nameInput && descriptionInput && callInput) {
// 确保每个字段都有值,如果没有则使用空字符串
const cmd = {
"Name" : nameInput.value.trim() || "",
"Description" : descriptionInput.value.trim() || "",
"Call" : callInput.value.trim() || ""
};
cmdList.push(cmd);
}
});
// 构建结构体映射JSON字符串
const buildStructMapping = (select1, select2) => {
const selectedOption1 = select1.selectedOptions[0];
const value2 = select2.value;
if (selectedOption1 && selectedOption1.dataset.fullname && value2) {
return JSON.stringify({ [selectedOption1.dataset.fullname]: value2 });
}
return '';
};
// 构建版本数据
const versionData = {
PlaneName: selection.plane,
ConfID: selection.configurationId,
ClassName: form.querySelector('#className').value,
Name: form.querySelector('#name').value,
Version: form.querySelector('#version').value,
Author: form.querySelector('#author').value,
Description: form.querySelector('#description').value,
ChangeTime: form.querySelector('#changeTime').value,
RunFreqGroup: form.querySelector('#runFreqGroup').value,
RunNode: form.querySelector('#runNode').value,
Priority: form.querySelector('#priority').value,
DataPackagePath: form.querySelector('#dataPackagePath').value,
DataPackageName: form.querySelector('#dataPackageName').value,
DataPackageHeaderName: form.querySelector('#dataPackageHeaderName').value,
DataPackageEntryPoint: form.querySelector('#dataPackageEntryPoint').value,
DataPackageInterfaceName: form.querySelector('#dataPackageInterfaceName').value,
InputStruct: buildStructMapping(
form.querySelector('#inputStructMapping1'),
form.querySelector('#inputStructMapping2')
),
OutputStruct: buildStructMapping(
form.querySelector('#outputStructMapping1'),
form.querySelector('#outputStructMapping2')
),
HeartStruct: buildStructMapping(
form.querySelector('#heartStructMapping1'),
form.querySelector('#heartStructMapping2')
),
CmdList: JSON.stringify(cmdList),
isUpdate: this.isEditMode,
originalVersion: this.isEditMode ? this.currentVersion.Version : null
};
// 验证必填字段
const requiredFields = ['PlaneName', 'ConfID', 'ClassName', 'Name', 'Version', 'Author', 'ChangeTime'];
for (const field of requiredFields) {
if (!versionData[field]) {
throw new Error(`${field}是必填字段`);
}
}
// 保存版本数据
const result = await this.saveModelVersion(versionData);
// 显示保存成功提示
alert(this.isEditMode ? '版本更新成功!' : '新版本创建成功!');
// 刷新版本列表
this.fetchModelVersions(this.currentModel.className, this.currentModel.name);
} catch (error) {
alert(`保存失败: ${error.message}`);
} finally {
// 恢复按钮状态
const saveButton = this.shadowRoot.querySelector('.toolbar-button:nth-child(2)');
if (saveButton) {
saveButton.textContent = this.isEditMode ? '保存修改' : '创建版本';
saveButton.disabled = false;
}
}
});
}
/**
* 更新运行节点选项
*/
updateRunNodeOptions() {
const runFreqGroupSelect = this.shadowRoot.querySelector('#runFreqGroup');
const runNodeSelect = this.shadowRoot.querySelector('#runNode');
if (!runFreqGroupSelect || !runNodeSelect) return;
const freqGroup = parseInt(runFreqGroupSelect.value) || 0;
const maxNode = Math.pow(2, freqGroup); // 2的运行频率组次方
// 清空现有选项
runNodeSelect.innerHTML = '';
// 添加新选项
for (let i = 0; i < maxNode; i++) {
const option = document.createElement('option');
option.value = i.toString();
option.textContent = i.toString();
if (this.currentVersion.RunNode === i.toString()) {
option.selected = true;
}
runNodeSelect.appendChild(option);
}
}
/**
* 显示日期时间选择对话框
* @param {HTMLInputElement} inputElement - 输入元素
*/
showDateTimeDialog(inputElement) {
// 获取当前系统时间
const now = new Date();
// 解析当前日期和时间
let currentDate = now;
let currentTime = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
// 如果输入框有值,尝试解析
if (inputElement.value && inputElement.value.trim() !== '') {
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]);
const parsedDate = new Date(year, month, day);
// 验证日期是否有效
if (!isNaN(parsedDate.getTime())) {
currentDate = parsedDate;
}
}
}
if (parts.length >= 2) {
// 验证时间格式
const timeRegex = /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/;
if (timeRegex.test(parts[1])) {
currentTime = parts[1];
}
}
} catch (error) {
console.error('解析日期时间失败:', error);
// 使用系统当前时间(已在初始化时设置)
}
}
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'dateTimeModal';
modal.style.cssText = 'position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4); display: flex; justify-content: center; align-items: center;';
// 获取年、月、日
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-60表示周日
// 生成月份选项
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>';
}
// 解析当前时间为时、分、秒
let hours = '00', minutes = '00', seconds = '00';
if (currentTime) {
const timeParts = currentTime.split(':');
if (timeParts.length >= 1) hours = timeParts[0].padStart(2, '0');
if (timeParts.length >= 2) minutes = timeParts[1].padStart(2, '0');
if (timeParts.length >= 3) seconds = timeParts[2].padStart(2, '0');
}
modal.innerHTML = `
<div class="modal-content" style="background-color: white; margin: auto; padding: 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); max-width: 400px; width: 100%;">
<div class="modal-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<div class="modal-title" style="font-size: 18px; font-weight: 600; color: #2d3748;">选择日期和时间</div>
<span class="close" style="font-size: 24px; color: #a0aec0; cursor: pointer;">&times;</span>
</div>
<div class="modal-body">
<div class="calendar-container" style="width: 100%;">
<div class="calendar-header" style="display: flex; justify-content: space-between; margin-bottom: 10px;">
<select id="calendarMonth" style="padding: 5px; border: 1px solid #e2e8f0; border-radius: 4px; flex: 1; margin-right: 10px;">${monthOptions}</select>
<select id="calendarYear" style="padding: 5px; border: 1px solid #e2e8f0; border-radius: 4px; flex: 1;">${yearOptions}</select>
</div>
<table class="calendar-table" style="width: 100%; border-collapse: collapse;">
<thead>
${calendarRows}
</thead>
</table>
</div>
<div class="time-container" style="margin-top: 15px;">
<label style="display: block; margin-bottom: 5px; color: #4a5568;">时间:</label>
<div style="display: flex; gap: 5px;">
<div style="flex: 1;">
<label for="hourInput" style="display: block; text-align: center; font-size: 12px; color: #718096;">时</label>
<input type="number" id="hourInput" min="0" max="23" value="${hours}" style="width: 100%; padding: 8px; border: 1px solid #e2e8f0; border-radius: 4px; text-align: center;">
</div>
<div style="display: flex; align-items: center; padding-top: 15px;">:</div>
<div style="flex: 1;">
<label for="minuteInput" style="display: block; text-align: center; font-size: 12px; color: #718096;">分</label>
<input type="number" id="minuteInput" min="0" max="59" value="${minutes}" style="width: 100%; padding: 8px; border: 1px solid #e2e8f0; border-radius: 4px; text-align: center;">
</div>
<div style="display: flex; align-items: center; padding-top: 15px;">:</div>
<div style="flex: 1;">
<label for="secondInput" style="display: block; text-align: center; font-size: 12px; color: #718096;">秒</label>
<input type="number" id="secondInput" min="0" max="59" value="${seconds}" style="width: 100%; padding: 8px; border: 1px solid #e2e8f0; border-radius: 4px; text-align: center;">
</div>
</div>
</div>
</div>
<div class="modal-footer" style="margin-top: 20px; text-align: right;">
<button id="cancelDateTime" style="background-color: #e2e8f0; color: #4a5568; border: none; border-radius: 4px; padding: 8px 16px; margin-right: 10px; cursor: pointer;">取消</button>
<button id="confirmDateTime" style="background-color: #4c6ef5; color: white; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer;">确定</button>
</div>
</div>
`;
// 添加样式
const style = document.createElement('style');
style.textContent = `
.calendar-day {
cursor: pointer;
padding: 8px;
text-align: center;
border: 1px solid #e2e8f0;
}
.calendar-day:hover {
background-color: #ebf4ff;
}
.calendar-day.selected {
background-color: #4c6ef5;
color: white;
}
.calendar-table th {
padding: 8px;
text-align: center;
border: 1px solid #e2e8f0;
color: #4a5568;
}
`;
// 获取shadowRoot
const shadowRoot = this.shadowRoot;
// 添加到DOM
shadowRoot.appendChild(style);
shadowRoot.appendChild(modal);
// 事件处理
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 hourInput = modal.querySelector('#hourInput');
const minuteInput = modal.querySelector('#minuteInput');
const secondInput = modal.querySelector('#secondInput');
// 验证时间输入
const validateTimeInput = (input, min, max) => {
let value = parseInt(input.value);
if (isNaN(value)) value = 0;
if (value < min) value = min;
if (value > max) value = max;
input.value = value.toString().padStart(2, '0');
};
// 添加输入验证
hourInput.addEventListener('change', () => validateTimeInput(hourInput, 0, 23));
minuteInput.addEventListener('change', () => validateTimeInput(minuteInput, 0, 59));
secondInput.addEventListener('change', () => validateTimeInput(secondInput, 0, 59));
// 选择日期事件
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);
// 更新当前日期
currentDate = new Date(selectedYear, selectedMonth, 1);
// 重新生成日历内容
const daysInMonth = new Date(selectedYear, selectedMonth + 1, 0).getDate();
const firstDay = new Date(selectedYear, selectedMonth, 1).getDay();
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 === currentDate.getDate();
calendarRows += `<td class="calendar-day ${isToday ? 'selected' : ''}" data-day="${dayCount}">${dayCount}</td>`;
dayCount++;
}
}
calendarRows += '</tr>';
}
// 更新日历表格内容
const calendarTable = modal.querySelector('.calendar-table');
calendarTable.innerHTML = calendarRows;
// 重新绑定日期选择事件
const calendarDays = modal.querySelectorAll('.calendar-day');
calendarDays.forEach(cell => {
cell.addEventListener('click', (e) => {
// 移除所有选中状态
calendarDays.forEach(day => day.classList.remove('selected'));
// 添加新选中状态
e.target.classList.add('selected');
});
});
};
calendarMonth.addEventListener('change', updateCalendar);
calendarYear.addEventListener('change', updateCalendar);
const closeModal = () => {
if (modal.parentNode === shadowRoot) {
shadowRoot.removeChild(modal);
}
if (style.parentNode === shadowRoot) {
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 hours = hourInput.value.padStart(2, '0');
const minutes = minuteInput.value.padStart(2, '0');
const seconds = secondInput.value.padStart(2, '0');
// 格式化日期
const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
// 格式化时间
const formattedTime = `${hours}:${minutes}:${seconds}`;
// 更新输入框值为标准格式
inputElement.value = `${formattedDate} ${formattedTime}`;
closeModal();
});
}
// 比较版本号的方法
compareVersions(versionA, versionB) {
if (!versionA) return -1;
if (!versionB) return 1;
const partsA = versionA.toString().split('.').map(Number);
const partsB = versionB.toString().split('.').map(Number);
// 确保两个数组长度相同,以便比较
const maxLength = Math.max(partsA.length, partsB.length);
for (let i = 0; i < maxLength; i++) {
// 处理长度不同的情况用0填充
const partA = i < partsA.length ? partsA[i] : 0;
const partB = i < partsB.length ? partsB[i] : 0;
if (partA > partB) return 1;
if (partA < partB) return -1;
}
return 0; // 版本号相同
}
renderError(message) {
// 确保DOM结构存在
if (!this.shadowRoot.querySelector('.model-development')) {
this.initBasicStructure();
console.log('重新初始化DOM结构');
}
const container = this.shadowRoot.querySelector('.model-dev-container');
if (!container) {
console.error('找不到容器元素');
return;
}
container.innerHTML = `
<div class="error-state">
<p>加载数据失败: ${message}</p>
<button class="retry-btn">重试</button>
</div>
`;
const retryBtn = this.shadowRoot.querySelector('.retry-btn');
if (retryBtn) {
retryBtn.addEventListener('click', () => {
if (this.currentView === 'chapters') {
this.init();
} else if (this.currentView === 'models' && this.currentChapter) {
this.fetchModels(this.currentChapter.ID);
} else if (this.currentView === 'versions' && this.currentModel) {
this.fetchModelVersions(this.currentModel.className, this.currentModel.name);
} else {
this.init();
}
});
}
}
// 初始化基本DOM结构的辅助方法
initBasicStructure() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
padding: 20px;
color: #333;
box-sizing: border-box;
height: calc(100vh - 40px); /* 限制总高度 */
overflow: hidden; /* 防止整体滚动 */
}
*, *:before, *:after {
box-sizing: inherit;
}
.page-title {
color: #5c6bc0;
margin-bottom: 20px;
font-weight: 500;
font-size: 24px;
}
.subtitle {
font-size: 16px;
color: #666;
font-weight: normal;
margin-left: 10px;
}
.model-dev-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
max-height: calc(100vh - 160px); /* 进一步减小容器高度,确保滚动条完全可见 */
overflow-y: auto;
padding-right: 10px;
padding-top: 10px; /* 添加上边距,解决悬停时上边界问题 */
padding-bottom: 30px; /* 添加底部边距,确保最后一行完全可见 */
width: 100%;
}
.chapter-card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
cursor: pointer;
height: 160px; /* 增加高度以容纳更多内容 */
display: flex;
flex-direction: column;
overflow: hidden; /* 防止内容溢出 */
}
.chapter-card:hover {
transform: translateY(-3px); /* 减小上移距离,防止超出可见区域 */
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
}
.chapter-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 10px;
color: #333;
}
.chapter-subtitle {
font-size: 16px;
color: #666;
}
.model-card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: transform 0.3s ease;
cursor: pointer;
height: 160px; /* 与章节卡片保持一致 */
display: flex;
flex-direction: column;
overflow: hidden; /* 防止内容溢出 */
}
.model-card:hover {
transform: translateY(-3px);
}
.model-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
color: #333;
}
.model-subtitle {
font-size: 15px;
color: #555;
margin-bottom: 12px;
}
.model-description {
font-size: 14px;
color: #666;
margin-bottom: 12px;
}
.model-class {
font-size: 13px;
color: #888;
font-style: italic;
}
.version-card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: transform 0.3s ease;
cursor: pointer;
height: 100%;
display: flex;
flex-direction: column;
}
.version-card:hover {
transform: translateY(-3px);
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12);
}
.version-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.version-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.version-number {
font-size: 15px;
font-weight: 500;
color: #5c6bc0;
padding: 3px 8px;
background: #eef0ff;
border-radius: 4px;
}
.version-details {
display: grid;
grid-template-columns: 1fr;
gap: 6px;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #eee;
}
.version-detail {
font-size: 14px;
color: #666;
}
.label {
font-weight: 500;
color: #555;
margin-right: 5px;
}
.version-description {
font-size: 14px;
color: #666;
line-height: 1.5;
flex-grow: 1;
}
/* 新建版本卡片样式 */
.new-version-card {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
background-color: #f8fafc;
border: 1px solid #cbd5e1;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: all 0.3s ease;
}
.new-version-card:hover {
background-color: #f1f5f9;
border-color: #94a3b8;
transform: translateY(-3px);
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12);
}
.new-version-icon {
display: flex;
justify-content: center;
align-items: center;
width: 50px;
height: 50px;
background-color: #eef2ff;
border-radius: 50%;
margin-bottom: 12px;
}
.new-version-icon svg {
width: 25px;
height: 25px;
fill: #6366f1;
}
.new-version-title {
font-size: 16px;
font-weight: 600;
color: #4f46e5;
margin-bottom: 5px;
}
.new-version-description {
font-size: 13px;
color: #64748b;
line-height: 1.4;
margin-bottom: 0;
}
.back-button {
grid-column: 1 / -1;
cursor: pointer;
color: #667eea;
font-weight: 500;
margin-bottom: 15px;
}
.back-button:hover {
text-decoration: underline;
}
.add-version-button {
background-color: #667eea;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
margin-bottom: 20px;
}
.add-version-button:hover {
background-color: #5c6bc0;
}
.models-grid, .versions-grid {
display: grid;
grid-column: 1 / -1;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
grid-auto-rows: 1fr; /* 确保每行高度一致 */
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-group textarea {
min-height: 100px;
resize: vertical;
}
.form-actions {
margin-top: 20px;
text-align: right;
}
.save-button {
background-color: #667eea;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 14px;
cursor: pointer;
}
.save-button:hover {
background-color: #5c6bc0;
}
.version-form {
grid-column: 1 / -1;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
width: 100%;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
grid-column: 1 / -1;
}
.error-state {
text-align: center;
padding: 40px;
color: #e74c3c;
grid-column: 1 / -1;
}
.retry-btn {
background: #667eea;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
font-size: 14px;
margin-top: 15px;
}
.empty-state {
text-align: center;
padding: 40px;
color: #666;
grid-column: 1 / -1;
}
</style>
<div class="model-development">
<h2 class="page-title">ATA章节</h2>
<div class="model-dev-container">
<div class="loading">正在加载数据...</div>
</div>
</div>
`;
}
// 组件被重新激活时调用
reactivate() {
if (this.currentView === 'chapters') {
this.init();
} else if (this.currentView === 'models' && this.currentChapter) {
this.fetchModels(this.currentChapter.ID);
} else if (this.currentView === 'versions' && this.currentModel) {
this.fetchModelVersions(this.currentModel.className, this.currentModel.name);
} else if (this.currentView === 'versionEditor' && this.currentVersion) {
this.showVersionEditor(this.currentVersion);
} else {
this.init();
}
}
// 创建新版本的方法
createNewVersion() {
// 创建一个新的版本对象,设置默认值
const newVersion = {
ClassName: this.currentModel.className,
Name: this.currentModel.name,
ModelName: this.currentModel.name,
Version: this.getNextVersionNumber(),
Author: '',
Description: '',
CreatTime: this.getCurrentDateTime(),
ChangeTime: this.getCurrentDateTime(),
RunFreqGroup: '0',
RunNode: '0',
Priority: '0',
DataPackagePath: '',
DataPackageName: '',
DataPackageHeaderName: '',
DataPackageEntryPoint: '',
DataPackageInterfaceName: '',
InputStruct: '',
OutputStruct: '',
HeartStruct: '',
CmdList: [] // 初始化为空数组
};
// 打开编辑界面
this.showVersionEditor(newVersion);
}
// 获取下一个版本号
getNextVersionNumber() {
if (!this.currentModelVersions || this.currentModelVersions.length === 0) {
return "1.0.0.0";
}
// 找出最高版本号
let highestVersion = null;
this.currentModelVersions.forEach(version => {
if (!version.Version) return;
if (!highestVersion || this.compareVersions(version.Version, highestVersion) > 0) {
highestVersion = version.Version;
}
});
if (!highestVersion) return "1.0.0.0";
// 解析版本号
const versionParts = highestVersion.toString().split('.').map(Number);
// 确保版本号有4位
while (versionParts.length < 4) {
versionParts.push(0);
}
// 增加最后一位
versionParts[versionParts.length - 1]++;
// 处理进位
for (let i = versionParts.length - 1; i > 0; i--) {
if (versionParts[i] > 9) {
versionParts[i] = 0;
versionParts[i - 1]++;
}
}
return versionParts.join('.');
}
// 获取当前日期时间格式化字符串
getCurrentDateTime() {
const now = new Date();
// 格式化日期YYYY-MM-DD
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const date = `${year}-${month}-${day}`;
// 格式化时间HH:MM:SS
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const time = `${hours}:${minutes}:${seconds}`;
return `${date} ${time}`;
}
/**
* 渲染指令列表行
* @returns {string} 表格行的HTML字符串
*/
renderCmdListRows() {
if (!this.currentVersion.CmdList || !Array.isArray(this.currentVersion.CmdList) || this.currentVersion.CmdList.length === 0) {
return '<tr><td colspan="4" style="text-align: center; padding: 20px; color: #718096;">暂无指令参数</td></tr>';
}
return this.currentVersion.CmdList.map((cmd, index) => `
<tr>
<td style="padding: 8px; border: 1px solid #e2e8f0;">
<input type="text" class="cmd-name" value="${cmd.Name || ''}" style="width: 100%; border: none; padding: 4px;">
</td>
<td style="padding: 8px; border: 1px solid #e2e8f0;">
<input type="text" class="cmd-description" value="${cmd.Description || ''}" style="width: 100%; border: none; padding: 4px;">
</td>
<td style="padding: 8px; border: 1px solid #e2e8f0;">
<input type="text" class="cmd-call" value="${cmd.Call || ''}" style="width: 100%; border: none; padding: 4px;">
</td>
<td style="padding: 8px; border: 1px solid #e2e8f0; text-align: center;">
<button type="button" class="delete-cmd" data-index="${index}" style="background-color: #e53e3e; color: white; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer;">
删除
</button>
</td>
</tr>
`).join('');
}
/**
* 添加新的指令行
*/
addCmdListRow() {
if (!this.currentVersion.CmdList) {
this.currentVersion.CmdList = [];
}
this.currentVersion.CmdList.push({
Name: '',
Description: '',
Call: ''
});
// 更新表格内容
const tbody = this.shadowRoot.querySelector('#cmdListBody');
if (tbody) {
// 如果当前显示的是"暂无指令参数"的提示,则清空表格
if (tbody.children.length === 1 && tbody.children[0].children.length === 1) {
tbody.innerHTML = '';
}
// 创建新的表格行
const newRow = document.createElement('tr');
// 创建指令名称输入框
const nameCell = document.createElement('td');
nameCell.style.cssText = 'padding: 8px; border: 1px solid #e2e8f0;';
const nameInput = document.createElement('input');
nameInput.type = 'text';
nameInput.className = 'cmd-name';
nameInput.style.cssText = 'width: 100%; border: none; padding: 4px;';
nameCell.appendChild(nameInput);
// 创建描述输入框
const descCell = document.createElement('td');
descCell.style.cssText = 'padding: 8px; border: 1px solid #e2e8f0;';
const descInput = document.createElement('input');
descInput.type = 'text';
descInput.className = 'cmd-description';
descInput.style.cssText = 'width: 100%; border: none; padding: 4px;';
descCell.appendChild(descInput);
// 创建调用函数输入框
const callCell = document.createElement('td');
callCell.style.cssText = 'padding: 8px; border: 1px solid #e2e8f0;';
const callInput = document.createElement('input');
callInput.type = 'text';
callInput.className = 'cmd-call';
callInput.style.cssText = 'width: 100%; border: none; padding: 4px;';
callCell.appendChild(callInput);
// 创建删除按钮
const actionCell = document.createElement('td');
actionCell.style.cssText = 'padding: 8px; border: 1px solid #e2e8f0; text-align: center;';
const deleteButton = document.createElement('button');
deleteButton.type = 'button';
deleteButton.className = 'delete-cmd';
deleteButton.dataset.index = (this.currentVersion.CmdList.length - 1).toString();
deleteButton.style.cssText = 'background-color: #e53e3e; color: white; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer;';
deleteButton.textContent = '删除';
deleteButton.addEventListener('click', (e) => {
const index = parseInt(e.target.dataset.index);
this.deleteCmdListRow(index);
});
actionCell.appendChild(deleteButton);
// 组装行
newRow.appendChild(nameCell);
newRow.appendChild(descCell);
newRow.appendChild(callCell);
newRow.appendChild(actionCell);
// 添加到表格
tbody.appendChild(newRow);
}
}
/**
* 删除指令行
* @param {number} index - 要删除的行索引
*/
deleteCmdListRow(index) {
if (this.currentVersion.CmdList && Array.isArray(this.currentVersion.CmdList)) {
this.currentVersion.CmdList.splice(index, 1);
// 更新表格内容
const tbody = this.shadowRoot.querySelector('#cmdListBody');
if (tbody) {
if (this.currentVersion.CmdList.length === 0) {
// 如果没有指令了,显示提示信息
tbody.innerHTML = '<tr><td colspan="4" style="text-align: center; padding: 20px; color: #718096;">暂无指令参数</td></tr>';
} else {
// 重新渲染所有行
tbody.innerHTML = this.renderCmdListRows();
// 重新绑定删除按钮事件
const deleteButtons = this.shadowRoot.querySelectorAll('.delete-cmd-btn');
deleteButtons.forEach(button => {
button.addEventListener('click', (e) => {
const index = parseInt(e.target.dataset.index);
this.deleteCmdListRow(index);
});
});
}
}
}
}
/**
* 填充数据库中结构体下拉框
*/
async populateDatabaseStructDropdowns() {
try {
const savedSelection = localStorage.getItem('xnsim-selection');
const selection = savedSelection ? JSON.parse(savedSelection) : {};
if (!selection.configurationId) {
console.warn('未找到构型ID无法获取结构体列表');
return;
}
const structResponse = await fetch(`/api/interface/list?confID=${selection.configurationId}`);
if (structResponse.ok) {
const structData = await structResponse.json();
// 以ModelStructName为key去重只保留第一个全称
const uniqueMap = {};
structData.forEach(item => {
if (!uniqueMap[item.ModelStructName]) {
uniqueMap[item.ModelStructName] = `${item.SystemName}::${item.PlaneName}::${item.ATAName}::${item.ModelStructName}`;
}
});
const structNames = Object.keys(uniqueMap);
// 获取下拉框元素
const inputStructMapping1 = this.shadowRoot.querySelector('#inputStructMapping1');
const outputStructMapping1 = this.shadowRoot.querySelector('#outputStructMapping1');
const heartStructMapping1 = this.shadowRoot.querySelector('#heartStructMapping1');
// 填充数据库中结构体下拉框
if (inputStructMapping1) {
inputStructMapping1.innerHTML = '<option value="">请选择...</option>' +
structNames.map(name => `<option value="${name}" data-fullname="${uniqueMap[name]}">${name}</option>`).join('');
}
if (outputStructMapping1) {
outputStructMapping1.innerHTML = '<option value="">请选择...</option>' +
structNames.map(name => `<option value="${name}" data-fullname="${uniqueMap[name]}">${name}</option>`).join('');
}
if (heartStructMapping1) {
heartStructMapping1.innerHTML = '<option value="">请选择...</option>' +
structNames.map(name => `<option value="${name}" data-fullname="${uniqueMap[name]}">${name}</option>`).join('');
}
// 解析数据库中存储的JSON字符串并自动选择正确的选项
this.selectStructMappingOptions();
// 检查数据包相关字段是否有值,如果有则获取结构体成员信息
const dataPackagePathInput = this.shadowRoot.querySelector('#dataPackagePath');
const dataPackageHeaderNameInput = this.shadowRoot.querySelector('#dataPackageHeaderName');
const dataPackageInterfaceNameInput = this.shadowRoot.querySelector('#dataPackageInterfaceName');
if (dataPackagePathInput && dataPackageHeaderNameInput && dataPackageInterfaceNameInput) {
const packagePath = dataPackagePathInput.value.trim();
const hearderName = dataPackageHeaderNameInput.value.trim();
const interfaceName = dataPackageInterfaceNameInput.value.trim();
if (packagePath && hearderName && interfaceName) {
const headerFilePath = packagePath + '/' + hearderName;
try {
const memberResponse = await fetch('/api/filesystem/get-struct-members', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
confName: selection.configurationName,
headerFilePath: headerFilePath,
structName: interfaceName
})
});
if (memberResponse.ok) {
const memberData = await memberResponse.json();
if (memberData.success && memberData.memberNames) {
// 获取头文件结构体下拉框
const inputStructMapping2 = this.shadowRoot.querySelector('#inputStructMapping2');
const outputStructMapping2 = this.shadowRoot.querySelector('#outputStructMapping2');
const heartStructMapping2 = this.shadowRoot.querySelector('#heartStructMapping2');
// 填充头文件中结构体下拉框
if (inputStructMapping2 && outputStructMapping2 && heartStructMapping2) {
const memberOptions = '<option value="">请选择...</option>' +
memberData.memberNames.map(member => `<option value="${member}">${member}</option>`).join('');
inputStructMapping2.innerHTML = memberOptions;
outputStructMapping2.innerHTML = memberOptions;
heartStructMapping2.innerHTML = memberOptions;
// 在填充头文件结构体下拉框后再次解析JSON并选择选项
this.selectStructMappingOptions();
}
}
} else {
console.warn('获取结构体成员信息失败:', memberResponse.status);
}
} catch (error) {
console.warn('获取结构体成员信息失败:', error);
}
}
}
} else {
console.warn('获取接口结构体列表失败:', structResponse.status);
}
} catch (error) {
console.warn('填充数据库结构体下拉框失败:', error);
}
}
/**
* 解析数据库中存储的JSON字符串并自动选择正确的选项
*/
selectStructMappingOptions() {
if (!this.currentVersion) return;
// 解析InputStruct
if (this.currentVersion.InputStruct) {
try {
const inputStructData = JSON.parse(this.currentVersion.InputStruct);
const inputStructKey = Object.keys(inputStructData)[0]; // 获取JSON对象的key
const inputStructValue = inputStructData[inputStructKey]; // 获取JSON对象的value
// 选择数据库结构体下拉框
const inputStructMapping1 = this.shadowRoot.querySelector('#inputStructMapping1');
if (inputStructMapping1) {
// 从全称中提取结构体名
const structName = inputStructKey.split('::').pop();
inputStructMapping1.value = structName;
}
// 选择头文件结构体下拉框
const inputStructMapping2 = this.shadowRoot.querySelector('#inputStructMapping2');
if (inputStructMapping2) {
inputStructMapping2.value = inputStructValue;
}
} catch (error) {
console.warn('解析InputStruct失败:', error);
}
}
// 解析OutputStruct
if (this.currentVersion.OutputStruct) {
try {
const outputStructData = JSON.parse(this.currentVersion.OutputStruct);
const outputStructKey = Object.keys(outputStructData)[0];
const outputStructValue = outputStructData[outputStructKey];
// 选择数据库结构体下拉框
const outputStructMapping1 = this.shadowRoot.querySelector('#outputStructMapping1');
if (outputStructMapping1) {
const structName = outputStructKey.split('::').pop();
outputStructMapping1.value = structName;
}
// 选择头文件结构体下拉框
const outputStructMapping2 = this.shadowRoot.querySelector('#outputStructMapping2');
if (outputStructMapping2) {
outputStructMapping2.value = outputStructValue;
}
} catch (error) {
console.warn('解析OutputStruct失败:', error);
}
}
// 解析HeartStruct
if (this.currentVersion.HeartStruct) {
try {
const heartStructData = JSON.parse(this.currentVersion.HeartStruct);
const heartStructKey = Object.keys(heartStructData)[0];
const heartStructValue = heartStructData[heartStructKey];
// 选择数据库结构体下拉框
const heartStructMapping1 = this.shadowRoot.querySelector('#heartStructMapping1');
if (heartStructMapping1) {
const structName = heartStructKey.split('::').pop();
heartStructMapping1.value = structName;
}
// 选择头文件结构体下拉框
const heartStructMapping2 = this.shadowRoot.querySelector('#heartStructMapping2');
if (heartStructMapping2) {
heartStructMapping2.value = heartStructValue;
}
} catch (error) {
console.warn('解析HeartStruct失败:', error);
}
}
}
/**
* 上传数据包模型
*/
async uploadDataPackage() {
try {
// 获取当前选择的构型信息
const savedSelection = localStorage.getItem('xnsim-selection');
const selection = savedSelection ? JSON.parse(savedSelection) : {};
if (!selection.configurationName) {
alert('请先选择构型!');
return;
}
// 创建文件输入元素
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.webkitdirectory = true;
fileInput.directory = true;
fileInput.multiple = true;
fileInput.style.display = 'none';
// 添加到DOM
document.body.appendChild(fileInput);
// 监听文件选择
fileInput.addEventListener('change', async (event) => {
try {
const files = Array.from(event.target.files);
if (files.length === 0) {
alert('请选择文件夹!');
return;
}
// 从第一个文件的webkitRelativePath中获取文件夹名称
let folderName = null;
if (files.length > 0 && files[0].webkitRelativePath) {
const pathParts = files[0].webkitRelativePath.split('/');
if (pathParts.length > 1) {
folderName = pathParts[0];
}
}
if (!folderName) {
alert('无法获取文件夹名称,请重新选择文件夹!');
return;
}
// 验证文件夹内容
const headerFiles = files.filter(file => file.name.toLowerCase().endsWith('.h'));
const libraryFiles = files.filter(file => file.name.toLowerCase().includes('.so'));
if (headerFiles.length !== 1) {
alert(`文件夹必须包含且仅包含一个.h文件当前包含 ${headerFiles.length} 个.h文件`);
return;
}
if (libraryFiles.length !== 1) {
alert(`文件夹必须包含且仅包含一个动态库文件,当前包含 ${libraryFiles.length} 个动态库文件`);
return;
}
if (files.length !== 2) {
alert(`文件夹只能包含一个.h文件和一个动态库文件当前包含 ${files.length} 个文件`);
return;
}
// 创建FormData
const formData = new FormData();
formData.append('confName', selection.configurationName);
formData.append('folderName', folderName);
// 添加所有文件
files.forEach(file => {
formData.append('files', file);
});
// 显示上传进度
const uploadButton = this.shadowRoot.querySelector('.toolbar-button:nth-child(3)');
if (uploadButton) {
const originalText = uploadButton.textContent;
uploadButton.textContent = '上传中...';
uploadButton.disabled = true;
}
// 发送上传请求
const response = await fetch('/api/filesystem/upload-package', {
method: 'POST',
body: formData
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || `上传失败: ${response.status}`);
}
const result = await response.json();
if (result.success) {
// 将返回的值填入对应的输入框
const dataPackagePathInput = this.shadowRoot.querySelector('#dataPackagePath');
const dataPackageNameInput = this.shadowRoot.querySelector('#dataPackageName');
const dataPackageHeaderNameInput = this.shadowRoot.querySelector('#dataPackageHeaderName');
const dataPackageEntryPointInput = this.shadowRoot.querySelector('#dataPackageEntryPoint');
const dataPackageInterfaceNameInput = this.shadowRoot.querySelector('#dataPackageInterfaceName');
if (dataPackagePathInput) {
dataPackagePathInput.value = result.packagePath;
}
if (dataPackageNameInput) {
dataPackageNameInput.value = result.libraryFile;
}
if (dataPackageHeaderNameInput) {
dataPackageHeaderNameInput.value = result.headerFile;
}
if (dataPackageEntryPointInput) {
dataPackageEntryPointInput.value = result.entryPoint;
}
if (dataPackageInterfaceNameInput) {
dataPackageInterfaceNameInput.value = result.paramType;
}
//将结构体名称填入对应下拉框的候选列表
const inputStructMapping2 = this.shadowRoot.querySelector('#inputStructMapping2');
const outputStructMapping2 = this.shadowRoot.querySelector('#outputStructMapping2');
const heartStructMapping2 = this.shadowRoot.querySelector('#heartStructMapping2');
// 填充头文件中结构体下拉框
if (inputStructMapping2 && outputStructMapping2 && heartStructMapping2) {
inputStructMapping2.innerHTML = '<option value="">请选择...</option>' + result.memberNames.map(name => `<option value="${name}">${name}</option>`).join('');
outputStructMapping2.innerHTML = '<option value="">请选择...</option>' + result.memberNames.map(name => `<option value="${name}">${name}</option>`).join('');
heartStructMapping2.innerHTML = '<option value="">请选择...</option>' + result.memberNames.map(name => `<option value="${name}">${name}</option>`).join('');
}
alert(`数据包上传成功!\n数据包路径: ${result.packagePath}\n头文件: ${result.headerFile}\n动态库文件: ${result.libraryFile}\n入口点: ${result.entryPoint}\n参数类型: ${result.paramType}`);
} else {
throw new Error(result.message || '上传失败');
}
} catch (error) {
console.error('数据包上传失败:', error);
alert(`数据包上传失败: ${error.message}`);
} finally {
// 恢复按钮状态
const uploadButton = this.shadowRoot.querySelector('.toolbar-button:nth-child(3)');
if (uploadButton) {
uploadButton.textContent = '数据包模型上传';
uploadButton.disabled = false;
}
// 清理文件输入元素
document.body.removeChild(fileInput);
}
});
// 触发文件选择对话框
fileInput.click();
} catch (error) {
console.error('数据包上传初始化失败:', error);
alert(`数据包上传失败: ${error.message}`);
}
}
/**
* 生成模板代码
*/
async generateTemplateCode() {
try {
// 获取当前选择的构型信息
const savedSelection = localStorage.getItem('xnsim-selection');
const selection = savedSelection ? JSON.parse(savedSelection) : {};
if (!selection.configurationName) {
alert('请先选择构型!');
return;
}
// 获取表单数据
const className = this.currentVersion.ClassName;
const version = this.currentVersion.Version;
const planeName = this.currentVersion.PlaneName;
if (!className || !version || !planeName) {
alert('缺少必要信息:类名、版本号或飞机名称!');
return;
}
// 显示生成进度
const generateButton = this.shadowRoot.querySelector('.toolbar-button:nth-child(4)');
if (generateButton) {
const originalText = generateButton.textContent;
generateButton.textContent = '生成中...';
generateButton.disabled = true;
}
// 调用后端API生成模板代码
const response = await fetch('/api/model-code-gen', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
className: className,
version: version,
planeName: planeName
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `生成失败: ${response.status}`);
}
const result = await response.json();
if (result.result && result.result.includes('成功')) {
alert('模板代码生成成功!');
} else {
throw new Error(result.result || '生成失败');
}
} catch (error) {
console.error('生成模板代码失败:', error);
alert(`生成模板代码失败: ${error.message}`);
} finally {
// 恢复按钮状态
const generateButton = this.shadowRoot.querySelector('.toolbar-button:nth-child(4)');
if (generateButton) {
generateButton.textContent = '模板代码生成';
generateButton.disabled = false;
}
}
}
/**
* 下载模板代码
*/
async downloadTemplateCode() {
try {
// 获取当前选择的构型信息
const savedSelection = localStorage.getItem('xnsim-selection');
const selection = savedSelection ? JSON.parse(savedSelection) : {};
if (!selection.configurationName) {
alert('请先选择构型!');
return;
}
// 获取表单数据
const className = this.currentVersion.ClassName;
const version = this.currentVersion.Version;
const planeName = this.currentVersion.PlaneName;
if (!className || !version || !planeName) {
alert('缺少必要信息:类名、版本号或飞机名称!');
return;
}
// 显示下载进度
const downloadButton = this.shadowRoot.querySelector('.toolbar-button:nth-child(5)');
if (downloadButton) {
const originalText = downloadButton.textContent;
downloadButton.textContent = '生成中...';
downloadButton.disabled = true;
}
// 第一步调用后端API生成压缩文件
const zipResponse = await fetch('/api/model-code-zip', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
className: className,
version: version,
planeName: planeName
})
});
if (!zipResponse.ok) {
const errorData = await zipResponse.json();
throw new Error(errorData.error || `生成压缩文件失败: ${zipResponse.status}`);
}
const zipResult = await zipResponse.json();
if (!zipResult.success || !zipResult.dstPath) {
throw new Error(zipResult.message || '生成压缩文件失败');
}
// 第二步:触发文件下载,直接使用绝对路径
const downloadUrl = `/api/filesystem/download-zip?filePath=${encodeURIComponent(zipResult.dstPath)}`;
window.open(downloadUrl);
alert('模板代码下载成功!');
} catch (error) {
console.error('下载模板代码失败:', error);
alert(`下载模板代码失败: ${error.message}`);
} finally {
// 恢复按钮状态
const downloadButton = this.shadowRoot.querySelector('.toolbar-button:nth-child(5)');
if (downloadButton) {
downloadButton.textContent = '模板代码下载';
downloadButton.disabled = false;
}
}
}
/**
* 上传集成代码
*/
async uploadWrapperCode() {
try {
// 获取当前选择的构型信息
const savedSelection = localStorage.getItem('xnsim-selection');
const selection = savedSelection ? JSON.parse(savedSelection) : {};
if (!selection.configurationName) {
alert('请先选择构型!');
return;
}
// 创建文件输入元素
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.zip';
fileInput.style.display = 'none';
// 添加到DOM
document.body.appendChild(fileInput);
// 监听文件选择
fileInput.addEventListener('change', async (event) => {
try {
const files = Array.from(event.target.files);
if (files.length === 0) {
alert('请选择ZIP文件');
return;
}
const file = files[0];
if (!file.name.toLowerCase().endsWith('.zip')) {
alert('请选择ZIP格式的文件');
return;
}
// 创建FormData
const formData = new FormData();
formData.append('confName', selection.configurationName);
formData.append('file', file);
// 显示上传进度
const uploadButton = this.shadowRoot.querySelector('.toolbar-button:nth-child(6)');
if (uploadButton) {
const originalText = uploadButton.textContent;
uploadButton.textContent = '上传中...';
uploadButton.disabled = true;
}
// 发送上传请求
const response = await fetch('/api/filesystem/upload-zip', {
method: 'POST',
body: formData
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || `上传失败: ${response.status}`);
}
const result = await response.json();
if (result.success) {
// 检查文件名是否符合 className_version.zip 格式
const fileName = result.file.name;
const expectedFileName = `${this.currentVersion.ClassName}_${this.currentVersion.Version}.zip`;
if (fileName === expectedFileName) {
// 文件名符合格式,自动调用解压
uploadButton.textContent = '解压中...';
try {
const unzipResponse = await fetch('/api/model-code-unzip', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
className: this.currentVersion.ClassName,
version: this.currentVersion.Version,
planeName: this.currentVersion.PlaneName,
srcPath: result.file.path
})
});
if (!unzipResponse.ok) {
const unzipErrorData = await unzipResponse.json();
throw new Error(unzipErrorData.error || `解压失败: ${unzipResponse.status}`);
}
const unzipResult = await unzipResponse.json();
if (unzipResult.result && unzipResult.result.includes('成功')) {
alert(`集成代码上传并解压成功!\n文件名: ${result.file.name}\n大小: ${(result.file.size / 1024 / 1024).toFixed(2)} MB\n解压状态: 成功`);
} else {
throw new Error(unzipResult.result || '解压失败');
}
} catch (unzipError) {
console.error('自动解压失败:', unzipError);
alert(`集成代码上传成功,但自动解压失败: ${unzipError.message}\n文件名: ${result.file.name}\n大小: ${(result.file.size / 1024 / 1024).toFixed(2)} MB`);
}
} else {
// 文件名不符合格式,只显示上传成功
alert(`集成代码上传成功!\n文件名: ${result.file.name}\n大小: ${(result.file.size / 1024 / 1024).toFixed(2)} MB\n注意: 文件名不符合 ${expectedFileName} 格式,未自动解压`);
}
} else {
throw new Error(result.message || '上传失败');
}
} catch (error) {
console.error('集成代码上传失败:', error);
alert(`集成代码上传失败: ${error.message}`);
} finally {
// 恢复按钮状态
const uploadButton = this.shadowRoot.querySelector('.toolbar-button:nth-child(6)');
if (uploadButton) {
uploadButton.textContent = '集成代码上传';
uploadButton.disabled = false;
}
// 清理文件输入元素
document.body.removeChild(fileInput);
}
});
// 触发文件选择对话框
fileInput.click();
} catch (error) {
console.error('集成代码上传初始化失败:', error);
alert(`集成代码上传失败: ${error.message}`);
}
}
/**
* 模型编译发布
*/
async compileAndPublishModel() {
try {
// 获取当前选择的构型信息
const savedSelection = localStorage.getItem('xnsim-selection');
const selection = savedSelection ? JSON.parse(savedSelection) : {};
if (!selection.configurationName) {
alert('请先选择构型!');
return;
}
// 获取表单数据
const className = this.currentVersion.ClassName;
const version = this.currentVersion.Version;
const planeName = this.currentVersion.PlaneName;
if (!className || !version || !planeName) {
alert('缺少必要信息:类名、版本号或飞机名称!');
return;
}
// 显示编译进度
const compileButton = this.shadowRoot.querySelector('.toolbar-button:nth-child(7)');
if (compileButton) {
const originalText = compileButton.textContent;
compileButton.textContent = '编译中...';
compileButton.disabled = true;
}
// 调用后端API编译模型
const response = await fetch('/api/model-code-compile', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
className: className,
version: version,
planeName: planeName
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `编译失败: ${response.status}`);
}
const result = await response.json();
if (result.result && result.result.includes('成功')) {
alert('模型编译发布成功!');
} else {
throw new Error(result.result || '编译失败');
}
} catch (error) {
console.error('模型编译发布失败:', error);
alert(`模型编译发布失败: ${error.message}`);
} finally {
// 恢复按钮状态
const compileButton = this.shadowRoot.querySelector('.toolbar-button:nth-child(7)');
if (compileButton) {
compileButton.textContent = '模型编译发布';
compileButton.disabled = false;
}
}
}
}
customElements.define('model-development', ModelDevelopment);