/** * @class DataMonitor * @extends HTMLElement * @description 数据监控组件的基础类 */ class DataMonitor extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.interfaces = []; // 存储接口数据 this.filteredInterfaces = []; // 存储过滤后的接口数据 this.searchText = ''; // 搜索文本 this.searchTimeout = null; // 用于防抖的定时器 this.cursorPosition = 0; // 存储光标位置 this.tableData = []; // 表格数据,存储已添加的接口 this.monitorId = `data_monitor_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // 添加监控器ID this.dataUpdateTimer = null; // 数据更新定时器 this.isActive = false; // 组件是否激活 this.chartWindows = new Map(); // 存储打开的图表窗口 // 绑定方法 this.handleSearch = this.handleSearch.bind(this); this.handleTreeItemDblClick = this.handleTreeItemDblClick.bind(this); this.handlePlot = this.handlePlot.bind(this); this.handleDelete = this.handleDelete.bind(this); this.handleCsvInject = this.handleCsvInject.bind(this); this.handleModalClose = this.handleModalClose.bind(this); this.handleModalCancel = this.handleModalCancel.bind(this); this.handleModalConfirm = this.handleModalConfirm.bind(this); this.handleFileInput = this.handleFileInput.bind(this); this.validateCsvFile = this.validateCsvFile.bind(this); // 确保 FloatingChartWindow 组件已注册 if (!customElements.get('floating-chart-window')) { const script = document.createElement('script'); script.src = './components/FloatingChartWindow.js'; document.head.appendChild(script); } } connectedCallback() { this.isActive = true; // 设置初始状态为激活 this.loadInterfaces(); // 等待组件完全加载后初始化 setTimeout(() => { this.initializeComponent(); // 初始化完成后再启动定时器,给服务器一些准备时间 setTimeout(() => { this.startDataUpdateTimer(); }, 1000); // 延迟1秒启动定时器 }, 100); } // 重新激活组件 reactivate() { if (this.isActive) return; // 如果已经激活,直接返回 this.isActive = true; this.initializeComponent(); // 重新启动定时器 this.startDataUpdateTimer(); } async initializeComponent() { try { // 更新所有行的监控状态 this.tableData.forEach(row => { row.isMonitoring = true; }); // 初始状态设置为未监控 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 = '未监控'; } } 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'); } if (statusText) { statusText.textContent = '监控错误'; } } } /** * @description 从localStorage获取当前选择的配置 * @returns {Object} 包含plane、configurationId和domainId的对象 */ getCurrentSelection() { const selection = localStorage.getItem('xnsim-selection'); if (!selection) { return { plane: '', configurationId: '', domainId: '' }; } try { const parsedSelection = JSON.parse(selection); return { plane: parsedSelection.plane || '', configurationId: parsedSelection.configurationId || '', domainId: parsedSelection.domainId || '' }; } catch (error) { return { plane: '', configurationId: '', domainId: '' }; } } /** * @description 加载接口数据 */ async loadInterfaces() { try { const { configurationId } = this.getCurrentSelection(); if (!configurationId) { console.warn('未找到配置ID'); return; } const response = await fetch(`/api/interface/list?systemName=XNSim&confID=${configurationId}`); const data = await response.json(); this.interfaces = data; this.filteredInterfaces = this.filterInterfaces(this.searchText); this.render(); } catch (error) { console.error('加载接口数据失败:', error); } } /** * @description 根据搜索文本过滤接口 * @param {string} searchText - 搜索文本 * @returns {Array} 过滤后的接口数据 */ filterInterfaces(searchText) { if (!searchText) return this.interfaces; return this.interfaces.filter(item => item.InterfaceName.toLowerCase().includes(searchText.toLowerCase()) || item.ModelStructName.toLowerCase().includes(searchText.toLowerCase()) ); } /** * @description 处理搜索输入 * @param {Event} event - 输入事件 */ handleSearch(event) { this.searchText = event.target.value; this.cursorPosition = event.target.selectionStart; // 清除之前的定时器 if (this.searchTimeout) { clearTimeout(this.searchTimeout); } // 设置新的定时器,300ms后执行搜索 this.searchTimeout = setTimeout(() => { this.filteredInterfaces = this.filterInterfaces(this.searchText); // 只更新树型控件部分 const treeView = this.shadowRoot.querySelector('.tree-view'); if (treeView) { // 按ModelStructName分组 const groupedInterfaces = this.filteredInterfaces.reduce((groups, item) => { const group = groups[item.ModelStructName] || []; group.push(item); groups[item.ModelStructName] = group; return groups; }, {}); // 更新树型控件内容 treeView.innerHTML = Object.entries(groupedInterfaces).map(([groupName, items]) => `
${groupName}
${items.map(item => `
${item.InterfaceName}
`).join('')}
`).join(''); // 重新绑定树节点双击事件 this.shadowRoot.querySelectorAll('.interface-item').forEach(itemEl => { itemEl.ondblclick = (e) => { const name = itemEl.getAttribute('data-interfacename'); const struct = itemEl.getAttribute('data-modelstructname'); this.handleTreeItemDblClick({ InterfaceName: name, ModelStructName: struct }); }; }); } // 在下一个事件循环中恢复焦点和光标位置 requestAnimationFrame(() => { const searchInput = this.shadowRoot.querySelector('.search-input'); if (searchInput) { searchInput.focus(); searchInput.setSelectionRange(this.cursorPosition, this.cursorPosition); } }); }, 300); } /** * @description 处理树节点双击事件,将接口添加到表格 * @param {Object} item - 接口对象 */ handleTreeItemDblClick(item) { // 防止重复添加 if (!this.tableData.some(row => row.InterfaceName === item.InterfaceName && row.ModelStructName === item.ModelStructName)) { // 添加新数据 this.tableData.push({ InterfaceName: item.InterfaceName, ModelStructName: item.ModelStructName, InjectValue: '', InjectFrequency: 100, monitorData: '', isMonitoring: false, isInjecting: false }); // 创建新行 const tbody = this.shadowRoot.querySelector('.data-table tbody'); const tr = document.createElement('tr'); tr.setAttribute('data-interface', item.InterfaceName); tr.setAttribute('data-struct', item.ModelStructName); tr.innerHTML = ` ${item.InterfaceName} ${item.ModelStructName} -
`; tbody.appendChild(tr); } } /** * @description 按结构体名称分组获取接口数据 * @returns {Object} 按结构体名称分组的接口数据 */ getGroupedInterfaces() { return this.tableData.reduce((groups, row) => { if (!groups[row.ModelStructName]) { groups[row.ModelStructName] = []; } groups[row.ModelStructName].push(row.InterfaceName); return groups; }, {}); } /** * @description 估算数据缓冲区大小 * @param {Array} interfaceNames - 接口名称数组 * @returns {number} 估算的缓冲区大小(字节) */ estimateBufferSize(interfaceNames) { // 基础开销:JSON格式的开销(括号、引号、逗号等) const baseOverhead = 100; // 每个接口的估算大小 const interfaceSize = interfaceNames.reduce((total, name) => { // 接口名称长度 + 引号 + 冒号 const nameOverhead = name.length + 4; // 假设每个数据值平均长度为50字节(包括数字、字符串等) const estimatedValueSize = 50; // 逗号分隔符 const separatorSize = 1; return total + nameOverhead + estimatedValueSize + separatorSize; }, 0); // 添加一些额外的缓冲空间(20%) const safetyMargin = Math.ceil((baseOverhead + interfaceSize) * 0.2); // 确保返回的大小是4KB的倍数,并设置最小值为8KB const minSize = 8192; const size = Math.max(minSize, Math.ceil((baseOverhead + interfaceSize + safetyMargin) / 4096) * 4096); return size; } /** * @description 格式化监控数据 * @param {string} data - 原始数据 * @returns {string} 格式化后的数据 */ formatMonitorData(data) { if (!data) return '-'; try { // 尝试解析JSON数据 const parsedData = JSON.parse(data); // 如果是对象,转换为格式化的字符串 if (typeof parsedData === 'object') { return JSON.stringify(parsedData, null, 2); } return data; } catch (e) { // 如果不是JSON,直接返回原始数据 return data; } } /** * @description 更新表格数据 * @param {Object} newData - 新的监控数据 */ updateTableData(newData) { let hasChanges = false; // 更新内存中的数据 this.tableData.forEach(row => { const newValue = newData[row.InterfaceName]; if (newValue !== undefined && newValue !== row.monitorData) { row.monitorData = newValue; hasChanges = true; } }); if (hasChanges) { // 只更新数据列的内容 const rows = this.shadowRoot.querySelectorAll('.data-table tbody tr'); rows.forEach((row, rowIndex) => { const dataCell = row.querySelector('td:nth-child(3)'); if (dataCell) { const formattedData = this.formatMonitorData(this.tableData[rowIndex].monitorData); dataCell.textContent = formattedData; dataCell.title = formattedData; } }); } } /** * @description 启动数据更新定时器 */ startDataUpdateTimer() { if (this.dataUpdateTimer) { clearInterval(this.dataUpdateTimer); } this.dataUpdateTimer = setInterval(async () => { try { // 检查DDS监控状态 const statusResponse = await fetch('/api/dds-monitor/status'); if (!statusResponse.ok) { throw new Error(`获取DDS监控状态失败: ${statusResponse.status} ${statusResponse.statusText}`); } 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.tableData.forEach(row => { row.isMonitoring = false; row.monitorData = ''; }); // 只更新数据单元格 const dataCells = this.shadowRoot.querySelectorAll('.data-cell'); dataCells.forEach(cell => { cell.textContent = '-'; cell.title = '-'; }); return; // 未初始化时等待下一次循环 } // DDS已初始化,更新状态显示 if (statusIndicator) { statusIndicator.classList.remove('inactive', 'error'); statusIndicator.classList.add('active'); } if (statusText) { statusText.textContent = '监控中'; } const groupedInterfaces = this.getGroupedInterfaces(); const allData = {}; // 存储所有结构体的数据 // 对每个结构体启动监控并获取数据 for (const [structName, interfaceNames] of Object.entries(groupedInterfaces)) { try { // 启动数据监控 const startResponse = await fetch('/api/data-monitor/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ structName }) }); if (!startResponse.ok) { throw new Error(`启动数据监控失败: ${structName}`); } // 获取监控数据 const interfaceNamesStr = JSON.stringify(interfaceNames); const bufferSize = this.estimateBufferSize(interfaceNames); const infoResponse = await fetch(`/api/data-monitor/info?structName=${encodeURIComponent(structName)}&interfaceName=${encodeURIComponent(interfaceNamesStr)}&bufferSize=${bufferSize}`); if (!infoResponse.ok) { throw new Error(`获取监控数据失败: ${structName}`); } const responseData = await infoResponse.json(); if (!responseData.success) { throw new Error(`获取监控数据失败: ${responseData.message || '未知错误'}`); } if (!responseData.data) { throw new Error(`获取监控数据失败: 返回数据为空`); } // 合并数据 Object.assign(allData, responseData.data); } catch (structError) { console.error(`处理结构体 ${structName} 时出错:`, structError); continue; } } // 一次性更新所有数据 if (Object.keys(allData).length > 0) { this.updateTableData(allData); } } 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.tableData.forEach(row => { row.isMonitoring = false; row.monitorData = ''; }); // 只更新数据单元格 const dataCells = this.shadowRoot.querySelectorAll('.data-cell'); dataCells.forEach(cell => { cell.textContent = '-'; cell.title = '-'; }); } }, 1000); // 每秒更新一次 } /** * @description 停止数据更新定时器,并通知后端停止监控所有相关结构体 */ async stopDataUpdateTimer() { // 先清理前端定时器 if (this.dataUpdateTimer) { clearInterval(this.dataUpdateTimer); this.dataUpdateTimer = null; } // 收集所有正在监控的结构体名(去重) const monitoringStructs = Array.from(new Set( this.tableData .filter(row => row.isMonitoring) .map(row => row.ModelStructName) )); // 并发调用后端接口,通知停止监控 try { await Promise.all( monitoringStructs.map(structName => fetch('/api/data-monitor/stop', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ structName }) }) ) ); } catch (error) { console.error('停止后端监控时发生错误:', error); } } /** * @description 获取随机颜色 * @returns {string} 颜色字符串 */ getRandomColor() { const letters = '0123456789ABCDEF'; let color = '#'; for (let i = 0; i < 6; i++) { color += letters[Math.floor(Math.random() * 16)]; } return color; } /** * @description 处理绘图按钮点击事件 * @param {string} interfaceName - 接口名称 * @param {string} modelStructName - 结构体名称 */ async handlePlot(interfaceName, modelStructName) { // 检查监控状态 const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); if (!statusIndicator || !statusIndicator.classList.contains('active')) { return; // 如果不在监控状态,直接返回 } // 检查是否已经存在该接口的图表窗口 const windowId = `${interfaceName}_${modelStructName}`; if (this.chartWindows.has(windowId)) { // 如果窗口已存在,将其置顶 const window = this.chartWindows.get(windowId); window.setAttribute('z-index', Date.now()); return; } if (typeof Chart === 'undefined') { console.log('Chart.js 未加载...'); } try { // 创建图表数据 const chartData = { labels: [], datasets: [] }; // 创建图表配置 const chartOptions = { responsive: true, maintainAspectRatio: false, animation: false, elements: { point: { radius: 0 }, line: { tension: 0 } }, scales: { y: { beginAtZero: false, display: true, ticks: { callback: function(value) { // 如果数值的绝对值大于1000或小于0.1,使用科学计数法 if (Math.abs(value) > 1000 || (Math.abs(value) < 0.1 && value !== 0)) { return value.toExponential(1); } // 否则使用普通数字,保留一位小数 return value.toFixed(1); }, maxTicksLimit: 8 // 限制刻度数量 } }, x: { display: true, ticks: { callback: function(value) { // 如果数值大于1000,使用科学计数法 if (value > 1000) { return value.toExponential(1); } return value; } } } }, plugins: { legend: { display: true, position: 'top' }, tooltip: { enabled: true, mode: 'index', intersect: false, callbacks: { label: function(context) { const value = context.raw; // 始终使用普通数字格式,保留一位小数 return `${context.dataset.label}: ${value.toFixed(1)}`; } } } } }; // 创建浮动窗口组件 const floatingWindow = document.createElement('floating-chart-window'); if (!floatingWindow) { throw new Error('创建浮动窗口组件失败'); } // 添加数据点计数器 floatingWindow.dataPointIndex = 0; // 先添加到页面 document.body.appendChild(floatingWindow); this.chartWindows.set(windowId, floatingWindow); // 再设置属性 floatingWindow.setAttribute('title', interfaceName); floatingWindow.setAttribute('initial-position', JSON.stringify({ x: 400, y: 200 })); floatingWindow.setAttribute('initial-size', JSON.stringify({ width: 400, height: 300 })); floatingWindow.setAttribute('chart-data', JSON.stringify(chartData)); floatingWindow.setAttribute('chart-options', JSON.stringify(chartOptions)); // 更新图表数据 const updateChartData = () => { const row = this.tableData.find(r => r.InterfaceName === interfaceName && r.ModelStructName === modelStructName ); if (row && row.monitorData) { try { const data = row.monitorData; let values = []; // 尝试解析数据 if (typeof data === 'string' && data.includes(',')) { // 如果是逗号分隔的字符串,分割并转换为数字 values = data.split(',').map(v => parseFloat(v.trim())); } else if (typeof data === 'number') { // 如果是单个数字 values = [data]; } else { // 尝试将字符串转换为数字 const numValue = parseFloat(data); values = isNaN(numValue) ? [] : [numValue]; } // 如果数据有效,更新图表 if (values.length > 0) { floatingWindow.handleDataUpdate(values, interfaceName); } } catch (e) { console.error('解析数据失败:', e); } } }; // 将更新函数添加到数据更新定时器中 const originalUpdateTableData = this.updateTableData; this.updateTableData = (newData) => { originalUpdateTableData.call(this, newData); updateChartData(); }; // 添加关闭事件处理 floatingWindow.addEventListener('close', () => { // 恢复原始的 updateTableData 方法 this.updateTableData = originalUpdateTableData; // 从 Map 中移除窗口引用 this.chartWindows.delete(windowId); }); } catch (error) { console.error('创建图表窗口失败:', error); alert('创建图表窗口失败,请查看控制台了解详情'); } } 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] || []; group.push(item); groups[item.ModelStructName] = group; return groups; }, {}); this.shadowRoot.innerHTML = `
未监控
${Object.entries(groupedInterfaces).map(([groupName, items]) => `
${groupName}
${items.map(item => `
${item.InterfaceName}
`).join('')}
`).join('')}
${this.tableData.map(row => { return ` `; }).join('')}
接口名
结构体名
数据
注入值
注入频率(Hz)
操作
${row.InterfaceName} ${row.ModelStructName} ${this.formatMonitorData(row.monitorData)}
`; const injectInputs = this.shadowRoot.querySelectorAll('.inject-input'); // 搜索框事件 const searchInput = this.shadowRoot.querySelector('.search-input'); if (searchInput) { searchInput.addEventListener('input', (e) => this.handleSearch(e)); } // 树节点双击事件 this.shadowRoot.querySelectorAll('.interface-item').forEach(itemEl => { itemEl.ondblclick = (e) => { const name = itemEl.getAttribute('data-interfacename'); const struct = itemEl.getAttribute('data-modelstructname'); this.handleTreeItemDblClick({ InterfaceName: name, ModelStructName: struct }); }; }); // 添加列宽调整功能 const table = this.shadowRoot.querySelector('.data-table'); const resizers = table.querySelectorAll('.resizer'); resizers.forEach(resizer => { let startX, startWidth; resizer.addEventListener('mousedown', (e) => { e.preventDefault(); const th = resizer.parentElement; startX = e.pageX; startWidth = th.offsetWidth; const mouseMoveHandler = (e) => { const width = startWidth + (e.pageX - startX); if (width > 50) { th.style.width = width + 'px'; } }; const mouseUpHandler = () => { document.removeEventListener('mousemove', mouseMoveHandler); document.removeEventListener('mouseup', mouseUpHandler); document.body.style.cursor = 'default'; resizer.classList.remove('active'); }; document.addEventListener('mousemove', mouseMoveHandler); document.addEventListener('mouseup', mouseUpHandler); document.body.style.cursor = 'col-resize'; resizer.classList.add('active'); }); }); // 添加注入值输入框的事件处理 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; } }); }); // 添加频率输入框的事件处理 const frequencyInputs = this.shadowRoot.querySelectorAll('.frequency-input'); frequencyInputs.forEach(input => { input.addEventListener('change', (e) => { const interfaceName = e.target.dataset.interface; const modelStructName = e.target.dataset.struct; const value = parseInt(e.target.value); // 验证频率范围 if (value < 0 || value > 1000) { e.target.value = 100; // 默认值 return; } // 更新表格数据 const row = this.tableData.find(r => r.InterfaceName === interfaceName && r.ModelStructName === modelStructName ); if (row) { row.InjectFrequency = 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'); if (deleteButton) { const interfaceName = deleteButton.dataset.interface; const modelStructName = deleteButton.dataset.struct; this.handleDelete(interfaceName, modelStructName); } }); // 添加绘图按钮的事件委托 table.addEventListener('click', (e) => { const plotButton = e.target.closest('.action-button.plot'); if (plotButton) { const interfaceName = plotButton.dataset.interface; const modelStructName = plotButton.dataset.struct; this.handlePlot(interfaceName, modelStructName); } }); // 添加连续注入按钮的事件委托 table.addEventListener('click', async (e) => { const continuousInjectButton = e.target.closest('.action-button.inject-continuous'); if (continuousInjectButton) { // 检查监控状态 const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); if (!statusIndicator || !statusIndicator.classList.contains('active')) { return; } const interfaceName = continuousInjectButton.getAttribute('data-interface'); const modelStructName = continuousInjectButton.getAttribute('data-struct'); if (!interfaceName || !modelStructName) { console.error('按钮缺少必要的数据属性'); return; } const row = this.tableData.find(r => r.InterfaceName === interfaceName && r.ModelStructName === modelStructName ); if (!row) { console.error('未找到对应的行数据:', { interfaceName, modelStructName }); return; } // 如果正在连续注入,则停止注入 if (continuousInjectButton.classList.contains('active')) { try { // 调用停止注入接口 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); } // 更新按钮和输入框状态 continuousInjectButton.textContent = '连续注入'; continuousInjectButton.classList.remove('active'); const tr = continuousInjectButton.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; // 更新表格数据状态 row.isInjecting = false; } catch (error) { console.error('停止注入失败:', error); alert(`停止注入失败: ${error.message}`); } 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/start-continuous', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ structName: modelStructName, interfaceNameAndData: JSON.stringify(interfaceData), frequency: row.InjectFrequency || 100 }) }); const result = await response.json(); if (!result.success) { throw new Error(result.message); } // 更新按钮和输入框状态 continuousInjectButton.textContent = '停止注入'; continuousInjectButton.classList.add('active'); const tr = continuousInjectButton.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 = true; injectButton.disabled = true; frequencyInput.disabled = true; deleteButton.disabled = true; // 更新表格数据状态 row.isInjecting = true; } catch (error) { console.error('连续注入失败:', error); alert(`连续注入失败: ${error.message}`); } } }); // 添加CSV文件注入相关的事件监听 const csvInjectButton = this.shadowRoot.getElementById('csvInjectButton'); const modal = this.shadowRoot.getElementById('csvModal'); const modalClose = this.shadowRoot.getElementById('modalClose'); const modalCancel = this.shadowRoot.getElementById('modalCancel'); const modalConfirm = this.shadowRoot.getElementById('modalConfirm'); const fileInput = this.shadowRoot.getElementById('csvFileInput'); const fileInputTrigger = this.shadowRoot.getElementById('fileInputTrigger'); if (csvInjectButton) { csvInjectButton.addEventListener('click', this.handleCsvInject); } if (modalClose) { modalClose.addEventListener('click', this.handleModalClose); } if (modalCancel) { modalCancel.addEventListener('click', this.handleModalCancel); } if (modalConfirm) { modalConfirm.addEventListener('click', this.handleModalConfirm); } if (fileInput) { fileInput.addEventListener('change', this.handleFileInput); } if (fileInputTrigger) { fileInputTrigger.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); fileInputTrigger.style.borderColor = '#1890ff'; }); fileInputTrigger.addEventListener('dragleave', (e) => { e.preventDefault(); e.stopPropagation(); fileInputTrigger.style.borderColor = '#d9d9d9'; }); fileInputTrigger.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); fileInputTrigger.style.borderColor = '#d9d9d9'; const files = e.dataTransfer.files; if (files.length > 0) { fileInput.files = files; this.handleFileInput({ target: fileInput }); } }); } } /** * @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 - 接口名称 * @param {string} modelStructName - 结构体名称 */ async handleDelete(interfaceName, modelStructName) { try { // 检查是否还有其他相同结构体的接口 const sameStructInterfaces = this.tableData.filter(row => row.ModelStructName === modelStructName && !(row.InterfaceName === interfaceName && row.ModelStructName === modelStructName) ); // 如果当前正在监控中,并且这是该结构体的最后一个接口,停止对该结构体的监控 const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); const isMonitoring = statusIndicator && statusIndicator.classList.contains('active'); if (isMonitoring && sameStructInterfaces.length === 0) { await fetch('/api/data-monitor/stop', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ structName: modelStructName }) }); } // 从表格数据中移除 this.tableData = this.tableData.filter(row => !(row.InterfaceName === interfaceName && row.ModelStructName === modelStructName) ); // 找到并移除对应的表格行 const rows = this.shadowRoot.querySelectorAll('.data-table tbody tr'); rows.forEach(row => { if (row.cells[0].textContent === interfaceName && row.cells[1].textContent === modelStructName) { row.remove(); } }); } catch (error) { console.error('删除接口时发生错误:', error); } } /** * @description 处理CSV文件注入按钮点击事件 */ handleCsvInject() { const modal = this.shadowRoot.getElementById('csvModal'); if (modal) { modal.classList.add('active'); } } /** * @description 处理模态框关闭事件 */ handleModalClose() { const modal = this.shadowRoot.getElementById('csvModal'); if (modal) { modal.classList.remove('active'); } } /** * @description 处理模态框取消按钮点击事件 */ handleModalCancel() { this.handleModalClose(); } /** * @description 处理模态框确认按钮点击事件 */ async handleModalConfirm() { const fileInput = this.shadowRoot.getElementById('csvFileInput'); const frequencyInput = this.shadowRoot.getElementById('injectFrequency'); const durationInput = this.shadowRoot.getElementById('injectDuration'); if (!fileInput.files.length) { alert('请选择CSV文件'); return; } const file = fileInput.files[0]; const frequency = parseInt(frequencyInput.value); const duration = parseInt(durationInput.value); if (!this.validateCsvFile(file)) { return; } try { // TODO: 实现CSV文件注入逻辑 console.log('CSV文件注入:', { file: file.name, frequency, duration }); this.handleModalClose(); } catch (error) { console.error('CSV文件注入失败:', error); alert(`CSV文件注入失败: ${error.message}`); } } /** * @description 处理文件输入事件 * @param {Event} event - 文件输入事件 */ handleFileInput(event) { const file = event.target.files[0]; const fileName = this.shadowRoot.getElementById('fileName'); if (file) { fileName.textContent = file.name; } else { fileName.textContent = ''; } } /** * @description 验证CSV文件 * @param {File} file - CSV文件对象 * @returns {boolean} 是否验证通过 */ validateCsvFile(file) { // TODO: 实现CSV文件验证逻辑 return true; } disconnectedCallback() { this.isActive = false; // 清理所有图表窗口 this.chartWindows.forEach((window, windowId) => { // 触发关闭事件,确保所有清理工作都完成 window.dispatchEvent(new CustomEvent('close')); window.remove(); }); this.chartWindows.clear(); // 组件销毁时清理定时器 this.stopDataUpdateTimer(); } } customElements.define('data-monitor', DataMonitor);