diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index 912e97b..ae47f18 100644 Binary files a/Release/database/XNSim.db and b/Release/database/XNSim.db differ diff --git a/Release/include/XNCore/XNDDSInterface.h b/Release/include/XNCore/XNDDSInterface.h index b219495..6206ebc 100644 --- a/Release/include/XNCore/XNDDSInterface.h +++ b/Release/include/XNCore/XNDDSInterface.h @@ -213,6 +213,9 @@ protected: while (std::getline(ss, item, ',')) { items.push_back(item); } + if (items.size() != getArrayWholeSize()) { + return; + } T temp; setStdArrayFromString(temp, items, 0); data = temp; diff --git a/Release/include/XNCore/XNLogger.h b/Release/include/XNCore/XNLogger.h index 16d8c3e..c9c2bfb 100644 --- a/Release/include/XNCore/XNLogger.h +++ b/Release/include/XNCore/XNLogger.h @@ -199,7 +199,7 @@ private: static_assert(std::is_arithmetic_v || std::is_same_v || std::is_convertible_v || std::is_same_v || std::is_same_v, - "错误码:010211001,不支持的类型转换,详见XNLogger.cpp:line 199"); + "A01021001: 不支持的类型转换,详见XNLogger.cpp:line 199"); } } @@ -214,7 +214,7 @@ private: static std::string formatMessage(const std::string &message, Args &&...args) { static_assert(sizeof...(Args) <= 9, - "错误码:010211002,单条日志参数数量超过限制,详见XNLogger.cpp:line 216"); + "A01021002: 单条日志参数数量超过限制,详见XNLogger.cpp:line 216"); std::string result = message; // 使用初始化列表展开参数包 diff --git a/Release/include/XNCore/XNTypeTraits.h b/Release/include/XNCore/XNTypeTraits.h index ebd2671..e754392 100644 --- a/Release/include/XNCore/XNTypeTraits.h +++ b/Release/include/XNCore/XNTypeTraits.h @@ -43,3 +43,14 @@ constexpr size_t getTypeSize() return sizeof(T); } } + +template +constexpr size_t getArrayWholeSize() +{ + if constexpr (is_std_array_v) { + // 对于std::array,计算所有元素的总大小 + return getArrayWholeSize() * array_size_v; + } else { + return 1; + } +} diff --git a/XNCore/XNDDSInterface.h b/XNCore/XNDDSInterface.h index b219495..6206ebc 100644 --- a/XNCore/XNDDSInterface.h +++ b/XNCore/XNDDSInterface.h @@ -213,6 +213,9 @@ protected: while (std::getline(ss, item, ',')) { items.push_back(item); } + if (items.size() != getArrayWholeSize()) { + return; + } T temp; setStdArrayFromString(temp, items, 0); data = temp; diff --git a/XNCore/XNTypeTraits.h b/XNCore/XNTypeTraits.h index ebd2671..e754392 100644 --- a/XNCore/XNTypeTraits.h +++ b/XNCore/XNTypeTraits.h @@ -43,3 +43,14 @@ constexpr size_t getTypeSize() return sizeof(T); } } + +template +constexpr size_t getArrayWholeSize() +{ + if constexpr (is_std_array_v) { + // 对于std::array,计算所有元素的总大小 + return getArrayWholeSize() * array_size_v; + } else { + return 1; + } +} diff --git a/XNSimHtml/components/data-monitor.js b/XNSimHtml/components/data-monitor.js index 5a955c7..cdb4abd 100644 --- a/XNSimHtml/components/data-monitor.js +++ b/XNSimHtml/components/data-monitor.js @@ -196,23 +196,154 @@ class DataMonitor extends HTMLElement { ${item.InterfaceName} ${item.ModelStructName} - - - + + +
- - + +
`; - // 添加删除按钮事件 + // 为输入框绑定事件 + const input = tr.querySelector('.inject-input'); + 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 row = this.tableData.find(r => + r.InterfaceName === interfaceName && + r.ModelStructName === modelStructName + ); + if (row) { + row.InjectValue = validValue; + } + }); + + 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; + } + }); + + // 为按钮绑定事件 const deleteButton = tr.querySelector('.action-button.delete'); deleteButton.addEventListener('click', () => { this.handleDelete(item.InterfaceName, item.ModelStructName); }); + const plotButton = tr.querySelector('.action-button.plot'); + plotButton.addEventListener('click', () => { + this.handlePlot(item.InterfaceName, item.ModelStructName); + }); + + const injectButton = tr.querySelector('.action-button.inject-once'); + injectButton.addEventListener('click', async () => { + // 检查监控状态 + const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); + if (!statusIndicator || !statusIndicator.classList.contains('active')) { + return; + } + + const row = this.tableData.find(r => + r.InterfaceName === item.InterfaceName && + r.ModelStructName === item.ModelStructName + ); + + if (!row) { + return; + } + + if (!row.InjectValue || row.InjectValue.trim() === '') { + alert('请先输入注入值'); + return; + } + + // 检查接口类型和注入值格式 + const isArray = this.isInterfaceArray(item.InterfaceName, item.ModelStructName); + if (!this.validateInjectValue(row.InjectValue, isArray, item.InterfaceName, item.ModelStructName)) { + alert(isArray ? '请输入正确格式的数组数据(用逗号分隔的数字)' : '请输入单个数字'); + return; + } + + try { + // 构造接口数据 + const interfaceData = { + [item.InterfaceName]: row.InjectValue + }; + + // 调用注入接口 + const response = await fetch('/api/data-monitor/inject', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + structName: item.ModelStructName, + interfaceNameAndData: JSON.stringify(interfaceData) + }) + }); + + const result = await response.json(); + if (!result.success) { + throw new Error(result.message); + } + } catch (error) { + console.error('注入失败:', error); + alert(`注入失败: ${error.message}`); + } + }); + tbody.appendChild(tr); } } @@ -915,6 +1046,29 @@ class DataMonitor extends HTMLElement { overflow-y: auto; } + .inject-input { + width: 100%; + padding: 4px 8px; + border: 1px solid #d9d9d9; + border-radius: 4px; + font-size: 14px; + box-sizing: border-box; + } + + .inject-input:focus { + border-color: #1890ff; + outline: none; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); + } + + .inject-input.invalid { + border-color: #ff4d4f; + } + + .inject-input.invalid:focus { + box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2); + } + .floating-chart-window { position: fixed; width: 400px; @@ -1034,22 +1188,31 @@ class DataMonitor extends HTMLElement { - ${this.tableData.map(row => ` - - ${row.InterfaceName} - ${row.ModelStructName} - ${this.formatMonitorData(row.monitorData)} - ${row.InjectValue || '-'} - -
- - - - -
- - - `).join('')} + ${this.tableData.map(row => { + return ` + + ${row.InterfaceName} + ${row.ModelStructName} + ${this.formatMonitorData(row.monitorData)} + + + + +
+ + + + +
+ + + `; + }).join('')} @@ -1057,6 +1220,8 @@ class DataMonitor extends HTMLElement { `; + const injectInputs = this.shadowRoot.querySelectorAll('.inject-input'); + // 搜索框事件 const searchInput = this.shadowRoot.querySelector('.search-input'); if (searchInput) { @@ -1106,6 +1271,136 @@ class DataMonitor extends HTMLElement { }); }); + // 添加注入值输入框的事件处理 + injectInputs.forEach(input => { + + // 添加输入验证 + 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 row = this.tableData.find(r => + r.InterfaceName === interfaceName && + r.ModelStructName === modelStructName + ); + if (row) { + row.InjectValue = validValue; + } + }); + + 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; + } + }); + }); + + // 添加注入一次按钮的事件委托 + table.addEventListener('click', async (e) => { + const injectButton = e.target.closest('.action-button.inject-once'); + if (injectButton) { + // 检查监控状态 + const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); + if (!statusIndicator || !statusIndicator.classList.contains('active')) { + return; + } + + const interfaceName = injectButton.dataset.interface; + const modelStructName = injectButton.dataset.struct; + + // 获取对应的注入值 + const row = this.tableData.find(r => + r.InterfaceName === interfaceName && + r.ModelStructName === modelStructName + ); + + if (!row) { + console.error('未找到对应的行数据'); + return; + } + + if (!row.InjectValue || row.InjectValue.trim() === '') { + alert('请先输入注入值'); + return; + } + + // 检查接口类型和注入值格式 + const isArray = this.isInterfaceArray(interfaceName, modelStructName); + if (!this.validateInjectValue(row.InjectValue, isArray, interfaceName, modelStructName)) { + alert(isArray ? '请输入正确格式的数组数据(用逗号分隔的数字)' : '请输入单个数字'); + return; + } + + try { + // 构造接口数据 + const interfaceData = { + [interfaceName]: row.InjectValue + }; + + // 调用注入接口 + const response = await fetch('/api/data-monitor/inject', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + structName: modelStructName, + interfaceNameAndData: JSON.stringify(interfaceData) + }) + }); + + const result = await response.json(); + if (!result.success) { + throw new Error(result.message); + } + } catch (error) { + console.error('注入失败:', error); + alert(`注入失败: ${error.message}`); + } + } + }); + // 添加删除按钮的事件委托 table.addEventListener('click', (e) => { const deleteButton = e.target.closest('.action-button.delete'); @@ -1127,6 +1422,69 @@ class DataMonitor extends HTMLElement { }); } + /** + * @description 验证注入值的格式 + * @param {string} value - 要验证的值 + * @param {boolean} isArray - 是否为数组类型 + * @param {string} interfaceName - 接口名称 + * @param {string} modelStructName - 结构体名称 + * @returns {boolean} 是否有效 + */ + validateInjectValue(value, isArray = false, interfaceName = '', modelStructName = '') { + if (!value) return true; // 空值视为有效 + + // 检查是否包含非法字符 + if (/[^0-9.,-]/.test(value)) return false; + + // 检查逗号分隔的数字 + const numbers = value.split(','); + + // 如果不是数组类型,不应该有逗号 + if (!isArray && numbers.length > 1) return false; + + // 验证每个数字的格式 + for (const num of numbers) { + if (num && !/^-?\d*\.?\d*$/.test(num)) return false; + } + + // 如果是数组类型,验证数组大小 + if (isArray && interfaceName && modelStructName) { + const interfaceInfo = this.interfaces.find(item => + item.InterfaceName === interfaceName && + item.ModelStructName === modelStructName + ); + + if (interfaceInfo) { + const size1 = interfaceInfo.InterfaceArraySize_1 || 0; + const size2 = interfaceInfo.InterfaceArraySize_2 || 0; + const expectedSize = size2 <= 1 ? size1 : size1 * size2; + + if (numbers.length !== expectedSize) { + return false; + } + } + } + + return true; + } + + /** + * @description 检查接口是否为数组类型 + * @param {string} interfaceName - 接口名称 + * @param {string} modelStructName - 结构体名称 + * @returns {boolean} 是否为数组类型 + */ + isInterfaceArray(interfaceName, modelStructName) { + // 从接口数据中查找对应的接口信息 + const interfaceInfo = this.interfaces.find(item => + item.InterfaceName === interfaceName && + item.ModelStructName === modelStructName + ); + + // 如果找到接口信息,检查是否为数组类型 + return interfaceInfo ? interfaceInfo.InterfaceIsArray : false; + } + /** * @description 处理删除按钮点击事件 * @param {string} interfaceName - 接口名称