/** * @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; } 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的对象 */ getCurrentSelection() { const selection = localStorage.getItem('xnsim-selection'); return selection ? JSON.parse(selection) : { plane: '', configurationId: '' }; } /** * @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, color: this.getRandomColor() }); this.renderTable(); } } /** * @description 渲染表格内容 */ renderTable() { const tableBody = this.shadowRoot.querySelector('#monitor-table-body'); if (tableBody) { tableBody.innerHTML = this.tableData.map((row, idx) => ` ${row.InterfaceName} ${row.ModelStructName} `).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 = ''; } }; } } customElements.define('data-monitor', DataMonitor);