diff --git a/Release/Configuration/C909_V1/Packages/ATA04_WeightBalance_2.0.14.6H_20241106/std_04_dll.h b/Release/Configuration/C909_V1/Packages/ATA04_WeightBalance_2.0.14.6H_20241106/std_04_dll.h
old mode 100755
new mode 100644
diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db
index 4d9be2d..d91ed91 100644
Binary files a/Release/database/XNSim.db and b/Release/database/XNSim.db differ
diff --git a/XNInterfaceGenServer/GenIDL.cpp b/XNInterfaceGenServer/GenIDL.cpp
index eb287d4..5ada630 100644
--- a/XNInterfaceGenServer/GenIDL.cpp
+++ b/XNInterfaceGenServer/GenIDL.cpp
@@ -11,7 +11,7 @@ std::string GenIDL::idlFilePath = "";
bool GenIDL::createConfigDirectory(const std::string &configName)
{
- std::string dirPath = GetXNCoreEnv() + "/IDL/" + configName;
+ std::string dirPath = GetXNCoreEnv() + "/Configuration/" + configName + "/IDL";
try {
fs::create_directories(dirPath);
GenIDL::idlFilePath = dirPath + "/" + configName + ".idl";
diff --git a/XNSimPortal/components/header-tools.js b/XNSimPortal/components/header-tools.js
index e7c4796..b4de32f 100644
--- a/XNSimPortal/components/header-tools.js
+++ b/XNSimPortal/components/header-tools.js
@@ -21,11 +21,18 @@ class HeaderTools extends HTMLElement {
return selectedOption ? selectedOption.dataset.domainId : '';
}
+ get selectedConfigurationName() {
+ const select = this.shadowRoot.getElementById('configurationSelect');
+ const selectedOption = select.options[select.selectedIndex];
+ return selectedOption ? selectedOption.textContent : '';
+ }
+
// 保存选择到localStorage
saveSelection() {
const selection = {
plane: this.selectedPlane,
configurationId: this.selectedConfiguration,
+ configurationName: this.selectedConfigurationName,
domainId: this.selectedDomain
};
localStorage.setItem('xnsim-selection', JSON.stringify(selection));
diff --git a/XNSimPortal/components/model-development.js b/XNSimPortal/components/model-development.js
index 9260149..ab09fb6 100644
--- a/XNSimPortal/components/model-development.js
+++ b/XNSimPortal/components/model-development.js
@@ -782,10 +782,12 @@ class ModelDevelopment extends HTMLElement {
}
}
},
- { text: '生成代码', color: '#805ad5', action: () => alert('生成代码功能即将上线') },
- { text: '编辑代码', color: '#d69e2e', action: () => alert('编辑代码功能即将上线') },
- { text: '模型编译', color: '#dd6b20', action: () => alert('模型编译功能即将上线') },
- { text: '模型提交', color: '#e53e3e', action: () => alert('模型提交功能即将上线') }
+ {
+ text: '数据包模型上传',
+ color: '#dd6b20',
+ action: () => this.uploadDataPackage()
+ },
+ { text: '自动封装', color: '#e53e3e', action: () => alert('自动封装功能即将上线') }
];
buttonConfigs.forEach(config => {
@@ -878,17 +880,17 @@ class ModelDevelopment extends HTMLElement {
// 添加左列输入框
const leftAdvancedColumn = document.createElement('div');
leftAdvancedColumn.innerHTML = `
-
-
-
-
-
-
+
+
+
+
+
+
`;
@@ -952,6 +954,39 @@ class ModelDevelopment extends HTMLElement {
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 = '';
+
+ // 创建结构体参数网格布局
+ const structGrid = document.createElement('div');
+ structGrid.style.cssText = 'display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px;';
+
+ // 添加三个结构体输入框
+ structGrid.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ 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;';
@@ -991,6 +1026,7 @@ class ModelDevelopment extends HTMLElement {
form.appendChild(basicInfoSection);
form.appendChild(advancedSection);
form.appendChild(cmdListSection);
+ form.appendChild(structSection);
form.appendChild(formActions);
formContainer.appendChild(form);
container.appendChild(formContainer);
@@ -1068,6 +1104,34 @@ class ModelDevelopment extends HTMLElement {
}
}
+ // 验证结构体字段的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');
@@ -1097,14 +1161,17 @@ class ModelDevelopment extends HTMLElement {
Author: form.querySelector('#author').value,
Description: form.querySelector('#description').value,
ChangeTime: form.querySelector('#changeTime').value,
- CodePath: form.querySelector('#codePath').value,
RunFreqGroup: form.querySelector('#runFreqGroup').value,
RunNode: form.querySelector('#runNode').value,
Priority: form.querySelector('#priority').value,
DataPackagePath: form.querySelector('#dataPackagePath').value,
- DataPackageHeaderPath: form.querySelector('#dataPackageHeaderPath').value,
+ DataPackageName: form.querySelector('#dataPackageName').value,
+ 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,
CmdList: JSON.stringify(cmdList),
isUpdate: this.isEditMode,
originalVersion: this.isEditMode ? this.currentVersion.Version : null
@@ -1940,14 +2007,17 @@ class ModelDevelopment extends HTMLElement {
Description: '',
CreatTime: this.getCurrentDateTime(),
ChangeTime: this.getCurrentDateTime(),
- CodePath: '',
RunFreqGroup: '0',
RunNode: '0',
Priority: '0',
DataPackagePath: '',
- DataPackageHeaderPath: '',
+ DataPackageName: '',
+ DataPackageHeaderName: '',
DataPackageEntryPoint: '',
DataPackageInterfaceName: '',
+ InputStruct: '',
+ OutputStruct: '',
+ HeartStruct: '',
CmdList: [] // 初始化为空数组
};
@@ -2152,6 +2222,163 @@ class ModelDevelopment extends HTMLElement {
}
}
}
+
+ /**
+ * 上传数据包模型
+ */
+ 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;
+ }
+
+ 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}`);
+ }
+ }
}
customElements.define('model-development', ModelDevelopment);
\ No newline at end of file
diff --git a/XNSimPortal/routes/filesystem.js b/XNSimPortal/routes/filesystem.js
index 051f992..c41b864 100644
--- a/XNSimPortal/routes/filesystem.js
+++ b/XNSimPortal/routes/filesystem.js
@@ -4,8 +4,12 @@ const fsPromises = require('fs').promises;
const fs = require('fs');
const path = require('path');
const multer = require('multer');
+const { exec } = require('child_process');
+const { promisify } = require('util');
const { getActualLogPath, getUploadPath, saveUploadedFile, isAllowedFileType } = require('../utils/file-utils');
+const execAsync = promisify(exec);
+
// 获取上传目录路径
const uploadPath = getUploadPath();
@@ -35,6 +39,17 @@ const fileFilter = (req, file, cb) => {
}
};
+// 数据包上传文件过滤器
+const packageFileFilter = (req, file, cb) => {
+ // 允许的文件类型:.h文件和包含.so的文件
+ const fileName = file.originalname.toLowerCase();
+ if (fileName.endsWith('.h') || fileName.includes('.so')) {
+ cb(null, true);
+ } else {
+ cb(new Error('数据包只能包含.h文件和动态库文件'));
+ }
+};
+
const upload = multer({
storage: storage,
fileFilter: fileFilter,
@@ -43,6 +58,15 @@ const upload = multer({
}
});
+// 数据包上传专用multer配置
+const packageUpload = multer({
+ storage: storage,
+ fileFilter: packageFileFilter,
+ limits: {
+ fileSize: 50 * 1024 * 1024 // 限制文件大小为50MB
+ }
+});
+
// 读取目录内容
router.get('/readdir', async (req, res) => {
try {
@@ -461,4 +485,297 @@ 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; // 从前端获取文件夹名称
+
+ if (!confName) {
+ return res.status(400).json({
+ success: false,
+ message: '未提供构型名称'
+ });
+ }
+
+ if (!req.files || req.files.length === 0) {
+ return res.status(400).json({
+ success: false,
+ message: '未提供文件'
+ });
+ }
+
+ // 验证是否为文件夹上传
+ const hasFolderStructure = req.files.some(file =>
+ file.originalname.includes('/') || file.originalname.includes('\\')
+ );
+
+ // 优先使用前端传递的文件夹名称,如果没有则从文件路径中提取
+ let packageName = null;
+
+ if (folderName) {
+ // 使用前端传递的文件夹名称
+ packageName = folderName;
+ } else if (hasFolderStructure) {
+ // 如果文件路径包含分隔符,从路径中提取
+ for (const file of req.files) {
+ const pathParts = file.originalname.replace(/\\/g, '/').split('/');
+ if (pathParts.length > 1) {
+ packageName = pathParts[0]; // 取第一级目录名作为数据包名称
+ break;
+ }
+ }
+ } else {
+ // 如果文件路径不包含分隔符,尝试从webkitRelativePath获取
+ if (req.files.length > 0 && req.files[0].webkitRelativePath) {
+ const pathParts = req.files[0].webkitRelativePath.split('/');
+ if (pathParts.length > 1) {
+ packageName = pathParts[0];
+ }
+ }
+
+ // 如果还是无法获取,使用默认名称
+ if (!packageName) {
+ packageName = 'uploaded_package_' + Date.now();
+ }
+ }
+
+ if (!packageName) {
+ return res.status(400).json({
+ success: false,
+ message: '无法从上传的文件中提取文件夹名称'
+ });
+ }
+
+ // 验证文件夹内容
+ const fileTypes = {
+ headerFiles: [], // .h文件
+ libraryFiles: [] // 动态库文件
+ };
+
+ req.files.forEach(file => {
+ const fileName = path.basename(file.originalname);
+ const ext = path.extname(fileName).toLowerCase();
+
+ if (ext === '.h') {
+ fileTypes.headerFiles.push(file);
+ } else if (fileName.toLowerCase().includes('.so')) {
+ fileTypes.libraryFiles.push(file);
+ }
+ });
+
+ // 验证文件数量
+ if (fileTypes.headerFiles.length !== 1) {
+ return res.status(400).json({
+ success: false,
+ message: `文件夹必须包含且仅包含一个.h文件,当前包含 ${fileTypes.headerFiles.length} 个.h文件`
+ });
+ }
+
+ if (fileTypes.libraryFiles.length !== 1) {
+ return res.status(400).json({
+ success: false,
+ message: `文件夹必须包含且仅包含一个动态库文件,当前包含 ${fileTypes.libraryFiles.length} 个动态库文件`
+ });
+ }
+
+ // 验证总文件数量
+ const totalFiles = fileTypes.headerFiles.length + fileTypes.libraryFiles.length;
+ if (totalFiles !== req.files.length) {
+ return res.status(400).json({
+ success: false,
+ message: `文件夹只能包含一个.h文件和一个动态库文件,当前包含 ${req.files.length} 个文件`
+ });
+ }
+
+ // 获取数据包路径
+ const { getPackagesPath } = require('../utils/file-utils');
+ const packagesPath = getPackagesPath(confName);
+
+ if (!packagesPath) {
+ return res.status(400).json({
+ success: false,
+ message: '无法获取数据包路径'
+ });
+ }
+
+ // 创建目标数据包目录
+ const targetPackagePath = path.join(packagesPath, packageName);
+
+ try {
+ await fsPromises.mkdir(targetPackagePath, { recursive: true });
+ } catch (error) {
+ if (error.code !== 'EEXIST') {
+ console.error('创建数据包目录失败:', error);
+ return res.status(500).json({
+ success: false,
+ message: '创建数据包目录失败: ' + error.message
+ });
+ }
+ }
+
+ const uploadedFiles = [];
+ const errors = [];
+
+ // 处理每个上传的文件
+ for (const file of req.files) {
+ try {
+ // 从文件的相对路径中提取目标路径
+ let targetPath;
+ if (file.originalname.includes('/') || file.originalname.includes('\\')) {
+ // 如果文件名包含路径分隔符,说明是文件夹上传
+ const relativePath = file.originalname.replace(/\\/g, '/');
+ targetPath = path.join(targetPackagePath, relativePath);
+ } else {
+ // 单个文件,直接放在数据包根目录
+ targetPath = path.join(targetPackagePath, file.originalname);
+ }
+
+ // 确保目标目录存在
+ const targetDir = path.dirname(targetPath);
+ try {
+ await fsPromises.mkdir(targetDir, { recursive: true });
+ } catch (mkdirError) {
+ if (mkdirError.code !== 'EEXIST') {
+ throw mkdirError;
+ }
+ }
+
+ // 移动文件到目标位置
+ await fsPromises.copyFile(file.path, targetPath);
+
+ // 删除临时文件
+ try {
+ await fsPromises.unlink(file.path);
+ } catch (unlinkError) {
+ console.warn('删除临时文件失败:', unlinkError);
+ }
+
+ // 获取文件信息
+ const stats = await fsPromises.stat(targetPath);
+ const fileName = path.basename(targetPath);
+ const ext = path.extname(fileName).toLowerCase();
+
+ uploadedFiles.push({
+ name: fileName,
+ path: path.relative(targetPackagePath, targetPath),
+ size: stats.size,
+ created: stats.birthtime,
+ modified: stats.mtime,
+ type: ext === '.h' ? 'header' : 'library'
+ });
+
+ } catch (fileError) {
+ console.error(`处理文件 ${file.originalname} 失败:`, fileError);
+ errors.push({
+ file: file.originalname,
+ error: fileError.message
+ });
+ }
+ }
+
+ // 对动态库执行nm -D命令获取入口点函数信息
+ let entryPointInfo = null;
+
+ try {
+ // 找到动态库文件路径
+ const libraryFile = uploadedFiles.find(file => file.type === 'library');
+ if (libraryFile) {
+ const libraryPath = path.join(targetPackagePath, libraryFile.path);
+ entryPointInfo = await getEntryPointInfo(libraryPath);
+ }
+ } catch (error) {
+ console.warn('获取动态库入口点信息失败:', error.message);
+ // 不阻止上传流程,只记录警告
+ }
+
+ // 返回上传结果
+ res.json({
+ success: true,
+ message: `成功上传数据包 ${packageName},包含 ${fileTypes.headerFiles.length} 个头文件和 ${fileTypes.libraryFiles.length} 个动态库文件`,
+ packagePath: packageName, // 相对于Packages目录的路径
+ headerFile: path.basename(fileTypes.headerFiles[0].originalname),
+ libraryFile: path.basename(fileTypes.libraryFiles[0].originalname),
+ entryPoint: entryPointInfo ? entryPointInfo.symbolName : null,
+ paramType: entryPointInfo ? entryPointInfo.paramType : null
+ });
+
+ } catch (error) {
+ console.error('数据包上传失败:', error);
+ res.status(500).json({
+ success: false,
+ message: '数据包上传失败: ' + error.message
+ });
+ }
+});
+
+/**
+ * 获取动态库入口点函数信息
+ * @param {string} libraryPath - 动态库文件路径
+ * @returns {Promise