V0.32.2.250620_alpha:模型开发页面添加了结构体的对应功能

This commit is contained in:
jinchao 2025-06-20 16:57:50 +08:00
parent 129e1459ca
commit ba9b024cd9
6 changed files with 452 additions and 31 deletions

Binary file not shown.

View File

@ -310,7 +310,7 @@ class InterfaceConfig extends HTMLElement {
throw new Error('请先选择构型');
}
const response = await fetch(`/api/interface/list?systemName=XNSim&confID=${selection.configurationId}`);
const response = await fetch(`/api/interface/list?confID=${selection.configurationId}`);
if (!response.ok) {
throw new Error('获取数据失败');
}

View File

@ -962,25 +962,56 @@ class ModelDevelopment extends HTMLElement {
// 创建结构体参数标题
const structHeader = document.createElement('div');
structHeader.style.cssText = 'margin-bottom: 15px;';
structHeader.innerHTML = '<label style="font-weight: 500; color: #333;">结构体参数</label>';
structHeader.innerHTML = '<label style="font-weight: 500; color: #333;">结构体对应关系</label>';
// 创建结构体参数网格布局
const structGrid = document.createElement('div');
structGrid.style.cssText = 'display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px;';
structGrid.style.cssText = 'display: grid; grid-template-columns: auto repeat(3, 1fr); gap: 20px;';
// 添加三个结构体输入框
// 添加六个下拉框,按类型分组
structGrid.innerHTML = `
<div class="form-group">
<label for="inputStruct">输入结构体 (InputStruct)</label>
<textarea id="inputStruct" name="InputStruct" placeholder="输入结构体JSON格式" title="模型的输入参数结构体JSON格式" style="min-height: 80px; resize: vertical;">${this.currentVersion.InputStruct || ''}</textarea>
<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="form-group">
<label for="outputStruct">输出结构体 (OutputStruct)</label>
<textarea id="outputStruct" name="OutputStruct" placeholder="输出结构体JSON格式" title="模型的输出参数结构体JSON格式" style="min-height: 80px; resize: vertical;">${this.currentVersion.OutputStruct || ''}</textarea>
<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="form-group">
<label for="heartStruct">心跳结构体 (HeartStruct)</label>
<textarea id="heartStruct" name="HeartStruct" placeholder="心跳结构体JSON格式" title="模型的心跳参数结构体JSON格式" style="min-height: 80px; resize: vertical;">${this.currentVersion.HeartStruct || ''}</textarea>
<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>
`;
@ -1025,8 +1056,8 @@ class ModelDevelopment extends HTMLElement {
// 组装表单
form.appendChild(basicInfoSection);
form.appendChild(advancedSection);
form.appendChild(cmdListSection);
form.appendChild(structSection);
form.appendChild(cmdListSection);
form.appendChild(formActions);
formContainer.appendChild(form);
container.appendChild(formContainer);
@ -1071,6 +1102,9 @@ class ModelDevelopment extends HTMLElement {
this.updateRunNodeOptions();
});
}
// 填充数据库中结构体下拉框
this.populateDatabaseStructDropdowns();
}, 0);
// 添加表单提交事件
@ -1151,6 +1185,17 @@ class ModelDevelopment extends HTMLElement {
}
});
// 构建结构体映射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,
@ -1169,9 +1214,18 @@ class ModelDevelopment extends HTMLElement {
DataPackageHeaderName: form.querySelector('#dataPackageHeaderName').value,
DataPackageEntryPoint: form.querySelector('#dataPackageEntryPoint').value,
DataPackageInterfaceName: form.querySelector('#dataPackageInterfaceName').value,
InputStruct: form.querySelector('#inputStruct').value,
OutputStruct: form.querySelector('#outputStruct').value,
HeartStruct: form.querySelector('#heartStruct').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
@ -2223,6 +2277,197 @@ class ModelDevelopment extends HTMLElement {
}
}
/**
* 填充数据库中结构体下拉框
*/
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);
}
}
}
/**
* 上传数据包模型
*/
@ -2350,6 +2595,18 @@ class ModelDevelopment extends HTMLElement {
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 || '上传失败');

View File

@ -488,14 +488,6 @@ router.get('/download', async (req, res) => {
// 上传数据包文件夹
router.post('/upload-package', packageUpload.array('files'), async (req, res) => {
try {
console.log('接收到的请求体:', req.body);
console.log('接收到的文件:', req.files ? req.files.map(f => f.originalname) : '无文件');
console.log('文件详细信息:', req.files ? req.files.map(f => ({
originalname: f.originalname,
webkitRelativePath: f.webkitRelativePath,
fieldname: f.fieldname
})) : '无文件');
const { confName } = req.body;
const { folderName } = req.body; // 从前端获取文件夹名称
@ -697,6 +689,23 @@ router.post('/upload-package', packageUpload.array('files'), async (req, res) =>
// 不阻止上传流程,只记录警告
}
let memberNames = [];
// 如果获取到了入口点函数信息,尝试从头文件中查找对应的结构体定义
if (entryPointInfo && entryPointInfo.paramType) {
try {
const headerFile = uploadedFiles.find(file => file.type === 'header');
if (headerFile) {
const headerFilePath = path.join(targetPackagePath, headerFile.path);
const structMemberNames = await findStructDefinition(headerFilePath, entryPointInfo.paramType);
if (structMemberNames) {
memberNames = structMemberNames;
}
}
} catch (error) {
console.warn('查找结构体定义失败:', error.message);
}
}
// 返回上传结果
res.json({
success: true,
@ -705,7 +714,8 @@ router.post('/upload-package', packageUpload.array('files'), async (req, res) =>
headerFile: path.basename(fileTypes.headerFiles[0].originalname),
libraryFile: path.basename(fileTypes.libraryFiles[0].originalname),
entryPoint: entryPointInfo ? entryPointInfo.symbolName : null,
paramType: entryPointInfo ? entryPointInfo.paramType : null
paramType: entryPointInfo ? entryPointInfo.paramType : null,
memberNames: memberNames
});
} catch (error) {
@ -765,7 +775,6 @@ async function getEntryPointInfo(libraryPath) {
paramType: paramType
};
console.log('动态库入口点信息:', entryPoint);
return entryPoint;
}
}
@ -778,4 +787,157 @@ async function getEntryPointInfo(libraryPath) {
}
}
module.exports = router;
/**
* 查找指定结构体的定义并返回其成员信息
* @param {string} headerFilePath - 头文件路径
* @param {string} structName - 要查找的结构体名称
* @returns {Promise<Array<string>>} 结构体成员信息数组格式为"type memberName"
*/
async function findStructDefinition(headerFilePath, structName) {
try {
// 检查文件是否存在
await fsPromises.access(headerFilePath);
// 读取头文件内容
const content = await fsPromises.readFile(headerFilePath, 'utf8');
// 移除注释,避免干扰解析
const contentWithoutComments = content
// 移除单行注释
.replace(/\/\/.*$/gm, '')
// 移除多行注释
.replace(/\/\*[\s\S]*?\*\//g, '');
// 构建查找结构体定义的正则表达式
// 匹配以下格式:
// struct StructName {
// struct StructName{
// typedef struct StructName {
// typedef struct StructName{
const structPattern = new RegExp(
`(?:typedef\\s+)?struct\\s+${structName}\\s*\\{([\\s\\S]*?)\\}\\s*;?`,
'g'
);
const memberNames = [];
let match;
while ((match = structPattern.exec(contentWithoutComments)) !== null) {
const structBody = match[1];
// 按行分割结构体内容
const lines = structBody.split('\n');
for (const line of lines) {
const trimmedLine = line.trim();
// 跳过空行和只包含分号的行
if (!trimmedLine || trimmedLine === ';') {
continue;
}
// 匹配成员声明
// 基础类型: int, double, float, char, short, long, unsigned, etc.
// 结构体指针: struct xxx_S*
// 结构体: struct xxx_S
const memberRegex = /^(?:const\s+)?(?:struct\s+([A-Za-z_][A-Za-z0-9_]*)\s*)?(?:unsigned\s+)?(?:long\s+)?(?:int\s+)?(?:char\s+)?(?:short\s+)?(?:float\s+)?(?:double\s+)?(?:void\s*)?(?:[A-Za-z_][A-Za-z0-9_]*\s*)?(\*?)\s*([A-Za-z_][A-Za-z0-9_]*)\s*(?:\[[^\]]*\])?\s*;?\s*$/;
const memberMatch = trimmedLine.match(memberRegex);
if (memberMatch) {
const structType = memberMatch[1]; // 结构体类型名
const isPointer = memberMatch[2]; // 是否为指针
const memberName = memberMatch[3]; // 成员名
// 构建类型信息
let type = '';
if (structType) {
// 结构体类型
type = `struct ${structType}${isPointer ? '*' : ''}`;
} else {
// 基础类型,需要从原始行中提取
const typeMatch = trimmedLine.match(/^(?:const\s+)?(?:unsigned\s+)?(?:long\s+)?(?:int\s+)?(?:char\s+)?(?:short\s+)?(?:float\s+)?(?:double\s+)?(?:void\s*)?(?:[A-Za-z_][A-Za-z0-9_]*\s*)?/);
if (typeMatch) {
type = typeMatch[0].trim() + (isPointer ? '*' : '');
} else {
type = 'unknown';
}
}
// 拼接类型和成员名
const memberString = `${type} ${memberName}`;
memberNames.push(memberString);
}
}
}
return memberNames;
} catch (error) {
console.warn(`查找结构体 ${structName} 定义失败:`, error.message);
return [];
}
}
/**
* 根据构型名头文件路径和结构体名获取结构体成员信息
*/
router.post('/get-struct-members', async (req, res) => {
try {
const { confName, headerFilePath, structName } = req.body;
if (!confName) {
return res.status(400).json({
success: false,
message: '未提供构型名称'
});
}
if (!headerFilePath) {
return res.status(400).json({
success: false,
message: '未提供头文件路径'
});
}
if (!structName) {
return res.status(400).json({
success: false,
message: '未提供结构体名称'
});
}
// 获取数据包路径
const { getPackagesPath } = require('../utils/file-utils');
const packagesPath = getPackagesPath(confName);
if (!packagesPath) {
return res.status(400).json({
success: false,
message: '无法获取数据包路径'
});
}
// 组装完整的头文件路径
const fullHeaderFilePath = path.join(packagesPath, headerFilePath);
// 调用findStructDefinition函数获取结构体成员信息
const memberNames = await findStructDefinition(fullHeaderFilePath, structName);
res.json({
success: true,
confName: confName,
structName: structName,
headerFilePath: headerFilePath,
fullHeaderFilePath: fullHeaderFilePath,
memberNames: memberNames,
count: memberNames.length
});
} catch (error) {
console.error('获取结构体成员信息失败:', error);
res.status(500).json({
success: false,
message: '获取结构体成员信息失败: ' + error.message
});
}
});
module.exports = router;

View File

@ -30,11 +30,11 @@ async function ensureDataDirectory() {
// 获取接口列表
router.get('/list', async (req, res) => {
try {
const { systemName, confID } = req.query;
const { confID } = req.query;
if (!confID) {
return res.status(400).json({ error: 'ConfID 是必填字段' });
}
const interfaces = await getDataInterfaces(systemName, confID);
const interfaces = await getDataInterfaces(confID);
res.json(interfaces);
} catch (error) {
console.error('获取接口列表失败:', error);

View File

@ -1,7 +1,7 @@
const { getDBConnection } = require('./file-utils');
// 获取接口列表
function getDataInterfaces(systemName = 'XNSim', confID) {
function getDataInterfaces(confID) {
try {
if (!confID) {
throw new Error('ConfID 是必填字段');
@ -10,6 +10,8 @@ function getDataInterfaces(systemName = 'XNSim', confID) {
const db = getDBConnection(true);
const tableName = `DataInterface_${confID}`;
const systemName = 'XNSim'
// 查询所有接口
const query = `