From cdb3c18361a09d9c7c51f7e481e56d6d9ddb33ef Mon Sep 17 00:00:00 2001 From: jinchao <383321154@qq.com> Date: Thu, 12 Jun 2025 16:42:43 +0800 Subject: [PATCH] =?UTF-8?q?V0.21.16.250612=5Falpha:=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=9B=91=E6=8E=A7=E7=9A=84CSV=E6=96=87=E4=BB=B6=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E5=9F=BA=E6=9C=AC=E5=8F=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 - Release/database/XNSim.db | Bin 1224704 -> 1224704 bytes XNMonitorServer/CSVDataInjectThread.cpp | 8 +- XNMonitorServer/XNMonitorInterface.cpp | 2 + XNMonitorServer/XNMonitorInterface.h | 8 + XNSimHtml/components/data-monitor.js | 422 +++++++++++++++++------- XNSimHtml/routes/DataMonitor.js | 2 +- 7 files changed, 316 insertions(+), 131 deletions(-) diff --git a/.gitignore b/.gitignore index 82c06ca..4fc4ad0 100644 --- a/.gitignore +++ b/.gitignore @@ -34,8 +34,3 @@ # CMakeBuild build/ - -#log -log/ -logs/ -Packages/ diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index 67fceec3842bd7b89667da772780acac2ce25c94..a0d80ed7311229ae09c21d5f97b2290c5811f0d8 100644 GIT binary patch delta 372 zcmZp8;MMTJYl1Z6l!-FVj8hsDS`!$zCNM2|&uq@SemeUHCTAwz_1nEZFeUR&-~E?~ zi^~>+L!%G_3o9ctD?<}K zLnFhe>4n_P^3!FxnYr4(@G}E33lOscF&hxG12G2>b58%l&n3Xa!kfszcbBhs|MJ_QYp> z%b(9`aSjf9Hm&{1?rqODuXx(J^y&Ov&*rRo+Q0t!loe0sta!4c_eDeViV4MS`m}B9lO5gfHTLwN8?=4X^WFW(#wDiYX6ChX$Z-KNHxTmxF)tAF S0Wm)i3jnd;b`CkA9}@w~gq$t_ delta 198 zcmZp8;MMTJYl1Z6#ECM_j1wCZS`!$zCNM2|&uqrKdpi3ECg<%wADEJPCkF6MU;meh zYx~i^Op67XTNZ>&Kf}jtt!idqVPa%%Zf2^Nlb>#6U}UOmV5Vzm7-C>#Wn^GwWT9tn zX%aQPkegY4x-2&{SNj)!W*}w(Vpbq#17da{<^W>O>0kJ{1h|;_I2icu@>TJ1Y*tkG h#oNvy#|6aPK+FTgAU+=u^8>K}5DRYSkQ4eb5diKaJZt~} diff --git a/XNMonitorServer/CSVDataInjectThread.cpp b/XNMonitorServer/CSVDataInjectThread.cpp index 179c4ec..3775055 100644 --- a/XNMonitorServer/CSVDataInjectThread.cpp +++ b/XNMonitorServer/CSVDataInjectThread.cpp @@ -122,14 +122,16 @@ void CSVDataInjectThread::threadFunc() { // 读取第一行数据 updateData(); + auto startTime = std::chrono::steady_clock::now(); while (m_running) { int64_t nextTime = m_nextExecuteTime; // 等待直到到达执行时间 - auto now = std::chrono::system_clock::now(); - auto targetTime = std::chrono::system_clock::from_time_t(nextTime / 1000) - + std::chrono::milliseconds(nextTime % 1000); + auto now = std::chrono::steady_clock::now(); + auto elapsed = + std::chrono::duration_cast(now - startTime).count(); + auto targetTime = startTime + std::chrono::milliseconds(nextTime); if (now < targetTime) { std::this_thread::sleep_until(targetTime); diff --git a/XNMonitorServer/XNMonitorInterface.cpp b/XNMonitorServer/XNMonitorInterface.cpp index 1f9706b..b51b93a 100644 --- a/XNMonitorServer/XNMonitorInterface.cpp +++ b/XNMonitorServer/XNMonitorInterface.cpp @@ -647,6 +647,7 @@ int XNMONITORSERVER_EXPORT XN_InjectDataInterfaceFromCsv(const char *structName, } return -1; } + std::cout << "CSV注入线程已启动" << std::endl; return 0; } @@ -662,6 +663,7 @@ int XN_StopCsvDataInject(char *infoMsg, int infoMsgSize) g_csvDataInjectThread->stop(); g_csvDataInjectThread.reset(); // 释放智能指针 + std::cout << "CSV注入线程已停止" << std::endl; return 0; } diff --git a/XNMonitorServer/XNMonitorInterface.h b/XNMonitorServer/XNMonitorInterface.h index 4ececd1..9b03bc9 100644 --- a/XNMonitorServer/XNMonitorInterface.h +++ b/XNMonitorServer/XNMonitorInterface.h @@ -220,6 +220,14 @@ extern "C" const int csvFilePathLen, char *infoMsg, int infoMsgSize); + /** + * @brief 获取csv数据注入状态 + * @param infoMsg 错误信息 + * @param infoMsgSize 错误信息大小 + * @return 0: 成功, -1: 失败 + */ + int XNMONITORSERVER_EXPORT XN_GetCsvDataInjectStatus(char *infoMsg, int infoMsgSize); + /** * @brief 停止csv数据注入 * @param infoMsg 错误信息 diff --git a/XNSimHtml/components/data-monitor.js b/XNSimHtml/components/data-monitor.js index 5fb368d..8fdb351 100644 --- a/XNSimHtml/components/data-monitor.js +++ b/XNSimHtml/components/data-monitor.js @@ -17,6 +17,13 @@ class DataMonitor extends HTMLElement { this.dataUpdateTimer = null; // 数据更新定时器 this.isActive = false; // 组件是否激活 this.chartWindows = new Map(); // 存储打开的图表窗口 + this.csvState = { // CSV 相关状态 + isInjecting: false, + fileName: '', + structNames: [], + filePath: '' // 添加文件路径 + }; + this.monitorStatus = 0; // 监控状态:0-未监控,1-监控中,2-错误 // 绑定方法 this.handleSearch = this.handleSearch.bind(this); @@ -66,25 +73,43 @@ class DataMonitor extends HTMLElement { }); // 初始状态设置为未监控 - const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); - const statusText = this.shadowRoot.getElementById('statusText'); - if (statusIndicator) { - statusIndicator.classList.remove('active', 'error'); - statusIndicator.classList.add('inactive'); - } - if (statusText) { - statusText.textContent = '未监控'; - } + this.monitorStatus = 0; + this.updateMonitorStatus(); } catch (error) { console.error('初始化组件失败:', error); - const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); - const statusText = this.shadowRoot.getElementById('statusText'); - if (statusIndicator) { - statusIndicator.classList.remove('active', 'inactive'); - statusIndicator.classList.add('error'); + this.monitorStatus = 2; + this.updateMonitorStatus(); + } + } + + updateMonitorStatus() { + const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); + const statusText = this.shadowRoot.getElementById('statusText'); + + if (statusIndicator) { + statusIndicator.classList.remove('active', 'inactive', 'error'); + switch (this.monitorStatus) { + case 1: + statusIndicator.classList.add('active'); + break; + case 2: + statusIndicator.classList.add('error'); + break; + default: + statusIndicator.classList.add('inactive'); } - if (statusText) { - statusText.textContent = '监控错误'; + } + + if (statusText) { + switch (this.monitorStatus) { + case 1: + statusText.textContent = '监控中'; + break; + case 2: + statusText.textContent = '监控错误'; + break; + default: + statusText.textContent = '未监控'; } } } @@ -270,6 +295,14 @@ class DataMonitor extends HTMLElement { `; tbody.appendChild(tr); + + // 如果CSV正在注入,禁用除绘图外的按钮 + if (this.csvState.isInjecting) { + const actionButtons = tr.querySelectorAll('.action-button:not(.plot)'); + actionButtons.forEach(button => { + button.disabled = true; + }); + } } } @@ -386,18 +419,9 @@ class DataMonitor extends HTMLElement { } const statusData = await statusResponse.json(); - // 更新状态显示 - const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); - const statusText = this.shadowRoot.getElementById('statusText'); - if (!statusData.isInitialized) { - if (statusIndicator) { - statusIndicator.classList.remove('active'); - statusIndicator.classList.add('inactive'); - } - if (statusText) { - statusText.textContent = '未监控'; - } + this.monitorStatus = 0; + this.updateMonitorStatus(); // 更新表格中所有数据的监控状态 this.tableData.forEach(row => { row.isMonitoring = false; @@ -413,13 +437,8 @@ class DataMonitor extends HTMLElement { } // DDS已初始化,更新状态显示 - if (statusIndicator) { - statusIndicator.classList.remove('inactive', 'error'); - statusIndicator.classList.add('active'); - } - if (statusText) { - statusText.textContent = '监控中'; - } + this.monitorStatus = 1; + this.updateMonitorStatus(); const groupedInterfaces = this.getGroupedInterfaces(); const allData = {}; // 存储所有结构体的数据 @@ -475,16 +494,8 @@ class DataMonitor extends HTMLElement { } catch (error) { console.error('数据更新失败:', error); this.stopDataUpdateTimer(); - const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); - const statusText = this.shadowRoot.getElementById('statusText'); - - if (statusIndicator) { - statusIndicator.classList.remove('active'); - statusIndicator.classList.add('error'); - } - if (statusText) { - statusText.textContent = '监控错误'; - } + this.monitorStatus = 2; + this.updateMonitorStatus(); this.tableData.forEach(row => { row.isMonitoring = false; @@ -551,8 +562,7 @@ class DataMonitor extends HTMLElement { */ async handlePlot(interfaceName, modelStructName) { // 检查监控状态 - const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); - if (!statusIndicator || !statusIndicator.classList.contains('active')) { + if (this.monitorStatus !== 1) { return; // 如果不在监控状态,直接返回 } @@ -714,10 +724,6 @@ class DataMonitor extends HTMLElement { } render() { - // 获取当前监控状态 - const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); - const isMonitoring = statusIndicator && statusIndicator.classList.contains('active'); - // 按ModelStructName分组 const groupedInterfaces = this.filteredInterfaces.reduce((groups, item) => { const group = groups[item.ModelStructName] || []; @@ -1415,8 +1421,6 @@ class DataMonitor extends HTMLElement { `; - const injectInputs = this.shadowRoot.querySelectorAll('.inject-input'); - // 搜索框事件 const searchInput = this.shadowRoot.querySelector('.search-input'); if (searchInput) { @@ -1466,69 +1470,71 @@ class DataMonitor extends HTMLElement { }); }); - // 添加注入值输入框的事件处理 - injectInputs.forEach(input => { + // 使用事件委托处理输入框事件 + table.addEventListener('input', (e) => { + const input = e.target.closest('.inject-input'); + if (!input) return; + + const value = input.value; + const interfaceName = input.dataset.interface; + const modelStructName = input.dataset.struct; - // 添加输入验证 - input.addEventListener('input', (e) => { - const value = e.target.value; - const interfaceName = e.target.dataset.interface; - const modelStructName = e.target.dataset.struct; - - // 检查接口类型 - const isArray = this.isInterfaceArray(interfaceName, modelStructName); - - // 只允许数字、小数点、负号和逗号 - const validValue = value.replace(/[^0-9.,-]/g, ''); - if (value !== validValue) { - e.target.value = validValue; - } - - // 验证格式 - const isValid = this.validateInjectValue(validValue, isArray, interfaceName, modelStructName); - e.target.classList.toggle('invalid', !isValid); + // 检查接口类型 + const isArray = this.isInterfaceArray(interfaceName, modelStructName); + + // 只允许数字、小数点、负号和逗号 + const validValue = value.replace(/[^0-9.,-]/g, ''); + if (value !== validValue) { + input.value = validValue; + } + + // 验证格式 + const isValid = this.validateInjectValue(validValue, isArray, interfaceName, modelStructName); + input.classList.toggle('invalid', !isValid); - // 实时更新表格数据 + // 实时更新表格数据 + const row = this.tableData.find(r => + r.InterfaceName === interfaceName && + r.ModelStructName === modelStructName + ); + if (row) { + row.InjectValue = validValue; + } + }); + + table.addEventListener('change', (e) => { + const input = e.target.closest('.inject-input'); + if (!input) return; + + const interfaceName = input.dataset.interface; + const modelStructName = input.dataset.struct; + const value = input.value; + + // 检查接口类型 + const isArray = this.isInterfaceArray(interfaceName, modelStructName); + + // 验证格式 + if (!this.validateInjectValue(value, isArray, interfaceName, modelStructName)) { + input.value = ''; + // 清空表格数据中的注入值 const row = this.tableData.find(r => r.InterfaceName === interfaceName && r.ModelStructName === modelStructName ); if (row) { - row.InjectValue = validValue; + row.InjectValue = ''; } - }); - - input.addEventListener('change', (e) => { - const interfaceName = e.target.dataset.interface; - const modelStructName = e.target.dataset.struct; - const value = e.target.value; - - // 检查接口类型 - const isArray = this.isInterfaceArray(interfaceName, modelStructName); - - // 验证格式 - if (!this.validateInjectValue(value, isArray, interfaceName, modelStructName)) { - e.target.value = ''; - // 清空表格数据中的注入值 - const row = this.tableData.find(r => - r.InterfaceName === interfaceName && - r.ModelStructName === modelStructName - ); - if (row) { - row.InjectValue = ''; - } - return; - } - - // 更新表格数据 - const row = this.tableData.find(r => - r.InterfaceName === interfaceName && - r.ModelStructName === modelStructName - ); - if (row) { - row.InjectValue = value; - } - }); + return; + } + + // 更新表格数据 + const row = this.tableData.find(r => + r.InterfaceName === interfaceName && + r.ModelStructName === modelStructName + ); + if (row) { + row.InjectValue = value; + } }); // 添加频率输入框的事件处理 @@ -1561,8 +1567,7 @@ class DataMonitor extends HTMLElement { const injectButton = e.target.closest('.action-button.inject-once'); if (injectButton) { // 检查监控状态 - const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); - if (!statusIndicator || !statusIndicator.classList.contains('active')) { + if (this.monitorStatus !== 1) { return; } @@ -1646,8 +1651,7 @@ class DataMonitor extends HTMLElement { const continuousInjectButton = e.target.closest('.action-button.inject-continuous'); if (continuousInjectButton) { // 检查监控状态 - const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); - if (!statusIndicator || !statusIndicator.classList.contains('active')) { + if (this.monitorStatus !== 1) { return; } @@ -1771,6 +1775,7 @@ class DataMonitor extends HTMLElement { // 添加CSV文件注入相关的事件监听 const csvUploadButton = this.shadowRoot.getElementById('csvUploadButton'); + const csvInjectButton = this.shadowRoot.getElementById('csvInjectButton'); const fileInput = this.shadowRoot.getElementById('csvFileInput'); if (csvUploadButton && fileInput) { @@ -1782,6 +1787,177 @@ class DataMonitor extends HTMLElement { if (fileInput) { fileInput.addEventListener('change', this.handleCsvFileSelect); } + + if (csvInjectButton) { + csvInjectButton.addEventListener('click', async () => { + // 检查监控状态 + if (this.monitorStatus !== 1) { + return; + } + // 如果按钮文本是"CSV停止注入",则停止注入 + if (csvInjectButton.textContent.trim() === 'CSV停止注入') { + try { + const response = await fetch('/api/data-monitor/stop-csv-inject', { + method: 'POST' + }); + + const result = await response.json(); + if (!result.success) { + throw new Error(result.message || '停止CSV注入失败'); + } + + // 恢复按钮状态 + csvUploadButton.disabled = false; + csvInjectButton.textContent = 'CSV数据注入'; + csvInjectButton.style.display = 'flex'; + + // 恢复表格中除绘图按钮外的所有按钮的状态 + const actionButtons = this.shadowRoot.querySelectorAll('.action-button:not(.plot)'); + actionButtons.forEach(button => { + button.disabled = false; + }); + + // 恢复文件名显示 + const csvFileName = this.shadowRoot.getElementById('csvFileName'); + if (csvFileName) { + csvFileName.textContent = this.csvState.fileName; + } + + // 更新CSV状态 + this.csvState.isInjecting = false; + } catch (error) { + console.error('停止CSV注入失败:', error); + alert(`停止CSV注入失败: ${error.message}`); + } + return; + } + + // 弹出确认对话框 + if (!confirm('此操作将停止所有持续注入,是否继续?')) { + return; + } + + try { + // 获取文件名元素 + const csvFileName = this.shadowRoot.getElementById('csvFileName'); + if (!csvFileName) { + throw new Error('找不到文件名元素'); + } + + // 停止所有持续注入 + const continuousInjectButtons = this.shadowRoot.querySelectorAll('.action-button.inject-continuous.active'); + for (const button of continuousInjectButtons) { + const interfaceName = button.getAttribute('data-interface'); + const modelStructName = button.getAttribute('data-struct'); + + if (interfaceName && modelStructName) { + const response = await fetch('/api/data-monitor/stop-continuous', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + structName: modelStructName, + interfaceName: interfaceName + }) + }); + + const result = await response.json(); + if (!result.success) { + throw new Error(result.message || '停止持续注入失败'); + } + + // 更新按钮状态 + button.textContent = '连续注入'; + button.classList.remove('active'); + const tr = button.closest('tr'); + const input = tr.querySelector('.inject-input'); + const injectButton = tr.querySelector('.action-button.inject-once'); + const frequencyInput = tr.querySelector('.frequency-input'); + const deleteButton = tr.querySelector('.action-button.delete'); + input.disabled = false; + injectButton.disabled = false; + frequencyInput.disabled = false; + deleteButton.disabled = false; + + // 更新表格数据状态 + const row = this.tableData.find(r => + r.InterfaceName === interfaceName && + r.ModelStructName === modelStructName + ); + if (row) { + row.isInjecting = false; + } + } + } + + // 调用CSV注入接口 + const response = await fetch('/api/data-monitor/inject-csv', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + structName: JSON.stringify(this.csvState.structNames), + csvFilePath: this.csvState.filePath + }) + }); + + const result = await response.json(); + if (!result.success) { + throw new Error(result.message || '启动CSV注入失败'); + } + + // 禁用上传按钮 + csvUploadButton.disabled = true; + + // 更新注入按钮文本 + csvInjectButton.textContent = 'CSV停止注入'; + + // 禁用表格中的除绘图按钮外的所有操作按钮 + const actionButtons = this.shadowRoot.querySelectorAll('.action-button:not(.plot)'); + actionButtons.forEach(button => { + button.disabled = true; + }); + + // 更新文件名显示 + if (csvFileName) { + csvFileName.textContent = this.csvState.fileName + ' 正在注入中...'; + } + + // 更新CSV状态 + this.csvState.isInjecting = true; + } catch (error) { + console.error('CSV注入失败:', error); + alert(`CSV注入失败: ${error.message}`); + } + }); + } + + // 恢复CSV状态 + if (this.csvState.fileName) { + const csvFileName = this.shadowRoot.getElementById('csvFileName'); + if (csvFileName) { + csvFileName.textContent = this.csvState.fileName; + if (this.csvState.isInjecting) { + csvFileName.textContent += ' 正在注入中...'; + } + } + + const csvInjectButton = this.shadowRoot.getElementById('csvInjectButton'); + if (csvInjectButton) { + csvInjectButton.style.display = 'flex'; + if (this.csvState.isInjecting) { + csvInjectButton.textContent = 'CSV停止注入'; + csvUploadButton.disabled = true; + // 禁用表格中除绘图外的所有按钮 + const actionButtons = this.shadowRoot.querySelectorAll('.action-button:not(.plot)'); + actionButtons.forEach(button => { + button.disabled = true; + }); + } + } + } } /** @@ -1861,10 +2037,7 @@ class DataMonitor extends HTMLElement { ); // 如果当前正在监控中,并且这是该结构体的最后一个接口,停止对该结构体的监控 - const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); - const isMonitoring = statusIndicator && statusIndicator.classList.contains('active'); - - if (isMonitoring && sameStructInterfaces.length === 0) { + if (this.monitorStatus === 1 && sameStructInterfaces.length === 0) { await fetch('/api/data-monitor/stop', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -1922,8 +2095,6 @@ class DataMonitor extends HTMLElement { throw new Error(result.message || '文件上传失败'); } - console.log('CSV文件上传成功:', result.file.name); - // 验证CSV文件头部 const validateResponse = await fetch(`/api/filesystem/validate-csv-headers?filename=${encodeURIComponent(result.file.name)}`); if (!validateResponse.ok) { @@ -1938,6 +2109,7 @@ class DataMonitor extends HTMLElement { // 检查每个头部是否在接口表中 const missingInterfaces = []; const invalidInterfaces = []; + const structNames = []; // 用于按顺序收集结构体名称 // 检查第一个接口(时间)是否在接口表中 const firstHeader = validateResult.headers[0]; @@ -1951,11 +2123,14 @@ class DataMonitor extends HTMLElement { // 检查其他接口是否在接口表中 for (let i = 1; i < validateResult.headers.length; i++) { const header = validateResult.headers[i]; - const exists = this.interfaces.some(interfaceItem => + const interfaceInfo = this.interfaces.find(interfaceItem => interfaceItem.InterfaceName === header ); - if (!exists) { + if (!interfaceInfo) { missingInterfaces.push(header); + } else { + // 按顺序收集结构体名称 + structNames.push(interfaceInfo.ModelStructName); } } @@ -1981,14 +2156,17 @@ class DataMonitor extends HTMLElement { console.error('删除验证失败的文件时出错:', deleteError); } throw new Error(errorMessages.join('\n\n')); - } - - console.log('CSV文件头部验证通过'); + } // 更新文件名显示 const csvFileName = this.shadowRoot.getElementById('csvFileName'); if (csvFileName) { csvFileName.textContent = result.file.name; + + // 更新CSV状态 + this.csvState.fileName = result.file.name; + this.csvState.structNames = structNames; + this.csvState.filePath = result.file.path; } // 显示数据注入按钮 diff --git a/XNSimHtml/routes/DataMonitor.js b/XNSimHtml/routes/DataMonitor.js index 6621d0e..59552db 100644 --- a/XNSimHtml/routes/DataMonitor.js +++ b/XNSimHtml/routes/DataMonitor.js @@ -336,7 +336,7 @@ router.post('/inject-csv', async (req, res) => { if (!structName || !csvFilePath) { return res.status(400).json({ success: false, - message: '结构体名称、CSV文件路径和注入次数不能为空' + message: '结构体名称、CSV文件路径不能为空' }); }