/** * @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.chart = null; // Chart.js 实例 this.tableHeight = 220; // 表格初始高度(px) this.isResizing = false; // 是否正在拖动分隔线 this.startY = 0; // 拖动起始Y this.startTableHeight = 0; // 拖动起始表格高度 this.activeChartIndexes = []; // 当前绘图的行索引 // 列宽数组,初始为像素 this.colWidths = [160, 160, 120, 180, 320]; // px this.dragColIndex = null; this.dragStartX = 0; this.dragStartWidth = 0; this._colWidthInited = false; this._resizeEventBinded = false; this.monitorId = `data_monitor_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // 添加监控器ID this.dataUpdateTimer = null; // 数据更新定时器 } connectedCallback() { this.loadInterfaces(); // 延迟多次尝试,直到拿到有效宽度 const tryInitColWidth = (tryCount = 0) => { const section = this.shadowRoot && this.shadowRoot.querySelector('.table-section'); if (section && section.clientWidth > 0) { const totalWidth = section.clientWidth; const ratios = [0.17, 0.17, 0.13, 0.19, 0.34]; this.colWidths = ratios.map(r => Math.floor(totalWidth * r)); this._colWidthInited = true; this.render(); } else if (tryCount < 10) { setTimeout(() => tryInitColWidth(tryCount + 1), 50); } }; tryInitColWidth(); // 只绑定一次全局拖动事件 if (!this._resizeEventBinded) { window.addEventListener('mousemove', this._onWindowMouseMove = (e) => { if (this.isResizing) { const delta = e.clientY - this.startY; let newHeight = this.startTableHeight + delta; const minHeight = 60; const maxHeight = Math.max(120, this.offsetHeight - 180); if (newHeight < minHeight) newHeight = minHeight; if (newHeight > maxHeight) newHeight = maxHeight; this.tableHeight = newHeight; this.render(); } }); window.addEventListener('mouseup', this._onWindowMouseUp = () => { if (this.isResizing) { this.isResizing = false; const divider = this.shadowRoot.querySelector('.divider'); if (divider) divider.classList.remove('active'); document.body.style.cursor = ''; document.body.style.userSelect = ''; } }); this._resizeEventBinded = true; } } /** * @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); this.render(); // 在下一个事件循环中恢复焦点和光标位置 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: '', Drawing: false, isMonitoring: false, color: this.getRandomColor() }); this.renderTable(); } } /** * @description 初始化DDS监控服务 * @returns {Promise} 初始化是否成功 */ async initializeDDSMonitor() { try { // 获取当前选择的配置 const selection = this.getCurrentSelection(); const { domainId } = selection; if (!domainId) { throw new Error('未找到有效的域ID,请确保已选择构型并等待构型加载完成'); } // 检查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(); // 如果未初始化,则初始化 if (!statusData.isInitialized) { const initResponse = await fetch('/api/dds-monitor/initialize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ domainId, monitorId: this.monitorId }) }); if (!initResponse.ok) { const errorData = await initResponse.json(); throw new Error(`初始化DDS监控失败: ${errorData.error || initResponse.statusText}`); } } return true; } catch (error) { throw error; } } /** * @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 启动数据更新定时器 */ startDataUpdateTimer() { if (this.dataUpdateTimer) { clearInterval(this.dataUpdateTimer); } this.dataUpdateTimer = setInterval(async () => { try { const groupedInterfaces = this.getGroupedInterfaces(); // 对每个结构体启动监控并获取数据 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(`获取监控数据失败: 返回数据为空`); } // 更新表格数据 this.tableData.forEach(row => { if (row.ModelStructName === structName && responseData.data[row.InterfaceName]) { row.monitorData = responseData.data[row.InterfaceName]; } }); } catch (structError) { console.error(`处理结构体 ${structName} 时出错:`, structError); // 继续处理其他结构体 continue; } } // 更新表格显示 this.renderTable(); } catch (error) { console.error('数据更新失败:', error); // 如果发生错误,停止定时器 this.stopDataUpdateTimer(); // 更新UI状态 const globalMonitorBtn = this.shadowRoot.getElementById('globalMonitorBtn'); const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); const statusText = this.shadowRoot.getElementById('statusText'); if (globalMonitorBtn) { globalMonitorBtn.textContent = '开始监控'; globalMonitorBtn.classList.remove('monitoring'); } if (statusIndicator) { statusIndicator.classList.remove('active'); statusIndicator.classList.add('error'); } if (statusText) { statusText.textContent = '监控错误'; } // 清空所有监控数据 this.tableData.forEach(row => { row.isMonitoring = false; row.monitorData = ''; }); this.renderTable(); alert(`数据监控发生错误: ${error.message}`); } }, 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 渲染表格内容 */ renderTable() { const tableBody = this.shadowRoot.querySelector('#monitor-table-body'); if (tableBody) { tableBody.innerHTML = this.tableData.map((row, idx) => ` ${row.InterfaceName} ${row.ModelStructName} ${row.monitorData || ''} `).join(''); } // 注入值输入限制 this.shadowRoot.querySelectorAll('.inject-input').forEach(input => { input.oninput = (e) => { // 只允许数字、负号、小数点、逗号 let v = e.target.value.replace(/[^0-9\-.,]/g, ''); e.target.value = v; const idx = parseInt(e.target.getAttribute('data-index')); this.tableData[idx].InjectValue = v; }; }); // 按钮事件 this.shadowRoot.querySelectorAll('.chart-btn').forEach(btn => { btn.onclick = (e) => { const idx = parseInt(btn.getAttribute('data-index')); // 多选支持 if (this.tableData[idx].Drawing) { this.tableData[idx].Drawing = false; } else { this.tableData[idx].Drawing = true; } // 更新activeChartIndexes this.activeChartIndexes = this.tableData.map((row, i) => row.Drawing ? i : null).filter(i => i !== null); this.renderTable(); this.updateChart(); }; }); this.shadowRoot.querySelectorAll('.delete-btn').forEach(btn => { btn.onclick = (e) => { const idx = parseInt(btn.getAttribute('data-index')); this.tableData.splice(idx, 1); // 删除后同步更新activeChartIndexes this.activeChartIndexes = this.tableData.map((row, i) => row.Drawing ? i : null).filter(i => i !== null); this.renderTable(); this.updateChart(); }; }); } /** * @description 初始化图表 */ initChart() { const chartElement = this.shadowRoot.querySelector('#monitor-chart'); if (!chartElement) return; if (typeof Chart === 'undefined') return; if (this.chart) { this.chart.destroy(); this.chart = null; } const ctx = chartElement.getContext('2d'); this.chart = new Chart(ctx, { type: 'line', data: { labels: [], datasets: [] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true } }, animation: false, plugins: { legend: { display: true }, tooltip: { enabled: false } } } }); } /** * @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 更新图表,支持多条曲线 */ updateChart() { if (!this.chart) return; if (!this.activeChartIndexes || this.activeChartIndexes.length === 0) { this.chart.data.labels = []; this.chart.data.datasets = []; this.chart.update('none'); return; } // 多条曲线 const labels = Array.from({length: 20}, (_, i) => i+1); this.chart.data.labels = labels; this.chart.data.datasets = this.activeChartIndexes.map(idx => { const row = this.tableData[idx]; return { label: row.InterfaceName, data: Array.from({length: 20}, () => (Math.random()*100).toFixed(2)), borderColor: row.color, fill: false }; }); this.chart.update('none'); } render() { // 计算所有列宽之和 const totalColWidth = this.colWidths.reduce((a, b) => a + b, 0); let tableWidthStyle = ''; const section = this.shadowRoot && this.shadowRoot.querySelector('.table-section'); const containerWidth = section ? section.clientWidth : 940; if (totalColWidth < containerWidth) { tableWidthStyle = 'width:100%;'; } else { tableWidthStyle = 'width:max-content;'; } // 首次渲染时动态分配列宽(已移至connectedCallback,防止死循环) // 按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('')}
接口名称
结构体名
数据
注入值
操作
数据监控图表
`; // 搜索框事件 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 }); }; }); // 渲染表格内容 this.renderTable(); // 初始化图表 this.initChart(); // 初始绘图 this.updateChart(); // 分隔线拖动事件 const divider = this.shadowRoot.querySelector('.divider'); if (divider) { divider.onmousedown = (e) => { this.isResizing = true; this.startY = e.clientY; this.startTableHeight = this.tableHeight; divider.classList.add('active'); document.body.style.cursor = 'row-resize'; document.body.style.userSelect = 'none'; }; } // 列宽拖动事件 this.shadowRoot.querySelectorAll('.th-resize').forEach(handle => { handle.onmousedown = (e) => { e.preventDefault(); this.dragColIndex = parseInt(handle.getAttribute('data-col')); this.dragStartX = e.clientX; this.dragStartWidth = this.colWidths[this.dragColIndex]; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; handle.classList.add('active'); // 高亮竖线 const th = handle.parentElement; if (th) th.classList.add('th-resize-active'); }; handle.onmouseup = () => { handle.classList.remove('active'); const th = handle.parentElement; if (th) th.classList.remove('th-resize-active'); }; }); window.onmousemove = (e) => { if (this.dragColIndex !== null) { const delta = e.clientX - this.dragStartX; let newWidth = this.dragStartWidth + delta; if (newWidth < 60) newWidth = 60; this.colWidths[this.dragColIndex] = newWidth; this.render(); } }; window.onmouseup = () => { if (this.dragColIndex !== null) { // 移除所有th-resize的active this.shadowRoot.querySelectorAll('.th-resize').forEach(h => h.classList.remove('active')); // 移除所有th的高亮 this.shadowRoot.querySelectorAll('.monitor-table th').forEach(th => th.classList.remove('th-resize-active')); this.dragColIndex = null; document.body.style.cursor = ''; document.body.style.userSelect = ''; } }; // 绑定全局监控按钮事件 const globalMonitorBtn = this.shadowRoot.getElementById('globalMonitorBtn'); const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); const statusText = this.shadowRoot.getElementById('statusText'); globalMonitorBtn.addEventListener('click', async () => { try { if (!globalMonitorBtn.classList.contains('monitoring')) { // 开始监控 const ddsInitialized = await this.initializeDDSMonitor(); if (!ddsInitialized) { throw new Error('DDS监控初始化失败'); } // 更新所有行的监控状态 this.tableData.forEach(row => { row.isMonitoring = true; }); // 启动数据更新定时器 this.startDataUpdateTimer(); // 更新状态 globalMonitorBtn.textContent = '停止监控'; globalMonitorBtn.classList.add('monitoring'); statusIndicator.classList.add('active'); statusText.textContent = '监控中'; this.renderTable(); } else { // 停止监控 // 停止数据更新定时器 await this.stopDataUpdateTimer(); // 更新所有行的监控状态 this.tableData.forEach(row => { row.isMonitoring = false; row.monitorData = ''; // 清空监控数据 }); // 更新状态 globalMonitorBtn.textContent = '开始监控'; globalMonitorBtn.classList.remove('monitoring'); statusIndicator.classList.remove('active'); statusText.textContent = '未监控'; this.renderTable(); } } catch (error) { statusIndicator.classList.add('error'); statusText.textContent = '监控错误'; alert(error.message); } }); } disconnectedCallback() { // 组件销毁时清理定时器 this.stopDataUpdateTimer(); } } customElements.define('data-monitor', DataMonitor);