diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db
index d6b2cbd..6ba44ec 100644
Binary files a/Release/database/XNSim.db and b/Release/database/XNSim.db differ
diff --git a/XNSimHtml/components/FloatingChartWindow.css b/XNSimHtml/components/FloatingChartWindow.css
new file mode 100644
index 0000000..370d5af
--- /dev/null
+++ b/XNSimHtml/components/FloatingChartWindow.css
@@ -0,0 +1,106 @@
+.floating-window {
+ position: fixed;
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ z-index: 1000;
+ transition: all 0.3s ease;
+}
+
+.floating-window.minimized {
+ transform: translateY(calc(100% - 40px));
+ border-radius: 8px 8px 0 0;
+}
+
+.window-header {
+ background: #f5f5f5;
+ padding: 8px 12px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: move;
+ user-select: none;
+ border-bottom: 1px solid #e0e0e0;
+ height: 40px;
+}
+
+.window-title {
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+}
+
+.window-controls {
+ display: flex;
+ gap: 8px;
+}
+
+.window-control-button {
+ background: none;
+ border: none;
+ font-size: 16px;
+ color: #666;
+ cursor: pointer;
+ padding: 0 4px;
+ line-height: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ border-radius: 4px;
+}
+
+.window-control-button:hover {
+ background: rgba(0, 0, 0, 0.05);
+}
+
+.minimize-button:hover {
+ color: #1890ff;
+}
+
+.close-button:hover {
+ color: #ff4d4f;
+}
+
+.window-content {
+ flex: 1;
+ padding: 12px;
+ position: relative;
+ min-height: 200px;
+ transition: height 0.3s ease;
+}
+
+.window-content.minimized {
+ height: 0;
+ padding: 0;
+ overflow: hidden;
+}
+
+.window-content canvas {
+ width: 100% !important;
+ height: 100% !important;
+}
+
+.resize-handle {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ width: 20px;
+ height: 20px;
+ cursor: se-resize;
+}
+
+.resize-handle::after {
+ content: '';
+ position: absolute;
+ right: 4px;
+ bottom: 4px;
+ width: 8px;
+ height: 8px;
+ border-right: 2px solid #ccc;
+ border-bottom: 2px solid #ccc;
+}
\ No newline at end of file
diff --git a/XNSimHtml/components/FloatingChartWindow.js b/XNSimHtml/components/FloatingChartWindow.js
new file mode 100644
index 0000000..554e2da
--- /dev/null
+++ b/XNSimHtml/components/FloatingChartWindow.js
@@ -0,0 +1,570 @@
+// 确保 Chart.js 已加载
+if (!window.Chart) {
+ const script = document.createElement('script');
+ script.src = '../chart.min.js';
+ document.head.appendChild(script);
+}
+
+class FloatingChartWindow extends HTMLElement {
+ // 添加静态计数器
+ static zIndexCounter = 1;
+
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' });
+
+ // 初始化状态
+ this.position = { x: 100, y: 100 };
+ this.size = { width: 400, height: 300 };
+ this.isDragging = false;
+ this.dragOffset = { x: 0, y: 0 };
+ this.isResizing = false;
+ this.resizeStart = { x: 0, y: 0 };
+ this.isMinimized = false;
+ this.chartInstance = null;
+ this.zIndex = FloatingChartWindow.zIndexCounter++;
+
+ // 存储初始属性值
+ this._title = this.getAttribute('title') || '图表窗口';
+ this._initialPosition = this.getAttribute('initial-position') ?
+ JSON.parse(this.getAttribute('initial-position')) : { x: 100, y: 100 };
+ this._initialSize = this.getAttribute('initial-size') ?
+ JSON.parse(this.getAttribute('initial-size')) : { width: 400, height: 300 };
+ this._chartData = this.getAttribute('chart-data') ?
+ JSON.parse(this.getAttribute('chart-data')) : { labels: [], datasets: [] };
+ this._chartOptions = this.getAttribute('chart-options') ?
+ JSON.parse(this.getAttribute('chart-options')) : {};
+
+ // 绑定方法
+ this.handleMouseDown = this.handleMouseDown.bind(this);
+ this.handleMouseMove = this.handleMouseMove.bind(this);
+ this.handleMouseUp = this.handleMouseUp.bind(this);
+ this.handleResizeStart = this.handleResizeStart.bind(this);
+ this.handleResize = this.handleResize.bind(this);
+ this.handleResizeEnd = this.handleResizeEnd.bind(this);
+ this.handleMinimize = this.handleMinimize.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+ }
+
+ static get observedAttributes() {
+ return ['title', 'initial-position', 'initial-size', 'chart-data', 'chart-options', 'z-index'];
+ }
+
+ connectedCallback() {
+ this.render();
+ this.initChart();
+ this.addEventListeners();
+
+ // 应用初始属性值
+ this.updateTitle(this._title);
+ this.position = this._initialPosition;
+ this.size = this._initialSize;
+ this.updatePosition();
+ this.updateSize();
+ this.updateChartData(this._chartData);
+ this.updateChartOptions(this._chartOptions);
+ this.updateZIndex(this.zIndex);
+
+ // 触发 connected 事件
+ this.dispatchEvent(new CustomEvent('connected'));
+ }
+
+ disconnectedCallback() {
+ this.removeEventListeners();
+ if (this.chartInstance) {
+ this.chartInstance.destroy();
+ }
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ if (oldValue === newValue) return;
+
+ // 存储新的属性值
+ switch (name) {
+ case 'title':
+ this._title = newValue;
+ break;
+ case 'initial-position':
+ this._initialPosition = JSON.parse(newValue);
+ break;
+ case 'initial-size':
+ this._initialSize = JSON.parse(newValue);
+ break;
+ case 'chart-data':
+ this._chartData = JSON.parse(newValue);
+ break;
+ case 'chart-options':
+ this._chartOptions = JSON.parse(newValue);
+ break;
+ case 'z-index':
+ this.zIndex = parseInt(newValue);
+ break;
+ }
+
+ // 如果组件已经渲染,则更新相应的值
+ if (this.shadowRoot) {
+ switch (name) {
+ case 'title':
+ this.updateTitle(this._title);
+ break;
+ case 'initial-position':
+ this.position = this._initialPosition;
+ this.updatePosition();
+ break;
+ case 'initial-size':
+ this.size = this._initialSize;
+ this.updateSize();
+ break;
+ case 'chart-data':
+ this.updateChartData(this._chartData);
+ break;
+ case 'chart-options':
+ this.updateChartOptions(this._chartOptions);
+ break;
+ case 'z-index':
+ this.updateZIndex(this.zIndex);
+ break;
+ }
+ }
+ }
+
+ render() {
+ const style = document.createElement('style');
+ style.textContent = `
+ .floating-window {
+ position: fixed;
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ }
+
+ .window-header {
+ background: #f5f5f5;
+ padding: 8px 12px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: move;
+ user-select: none;
+ border-bottom: 1px solid #e8e8e8;
+ flex-shrink: 0;
+ }
+
+ .window-title {
+ font-size: 14px;
+ font-weight: 500;
+ color: #262626;
+ }
+
+ .window-controls {
+ display: flex;
+ gap: 8px;
+ }
+
+ .window-control-button {
+ width: 24px;
+ height: 24px;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ color: #666;
+ border-radius: 4px;
+ transition: all 0.3s;
+ }
+
+ .window-control-button:hover {
+ background: rgba(0, 0, 0, 0.05);
+ color: #262626;
+ }
+
+ .window-control-button.close-button:hover {
+ background: #ff4d4f;
+ color: white;
+ }
+
+ .window-content {
+ flex: 1;
+ padding: 12px;
+ position: relative;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .window-content.minimized {
+ display: none;
+ }
+
+ .chart-container {
+ flex: 1;
+ position: relative;
+ min-height: 0;
+ }
+
+ canvas {
+ width: 100% !important;
+ height: 100% !important;
+ }
+
+ .resize-handle {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ width: 10px;
+ height: 10px;
+ cursor: se-resize;
+ }
+
+ .resize-handle::after {
+ content: '';
+ position: absolute;
+ right: 2px;
+ bottom: 2px;
+ width: 6px;
+ height: 6px;
+ border-right: 2px solid #ccc;
+ border-bottom: 2px solid #ccc;
+ }
+ `;
+
+ const template = document.createElement('template');
+ template.innerHTML = `
+
+ `;
+
+ this.shadowRoot.appendChild(style);
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
+
+ // 更新位置和大小
+ this.updatePosition();
+ this.updateSize();
+ }
+
+ initChart() {
+ const canvas = this.shadowRoot.querySelector('canvas');
+ const ctx = canvas.getContext('2d');
+
+ const chartData = JSON.parse(this.getAttribute('chart-data') || '{"labels":[],"datasets":[]}');
+ const chartOptions = JSON.parse(this.getAttribute('chart-options') || '{}');
+
+ // 基础配置
+ const baseOptions = {
+ responsive: true,
+ maintainAspectRatio: false,
+ animation: false,
+ elements: {
+ point: {
+ radius: 0
+ },
+ line: {
+ tension: 0
+ }
+ },
+ scales: {
+ y: {
+ beginAtZero: false,
+ 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 finalOptions = {
+ ...baseOptions,
+ scales: {
+ y: {
+ ...baseOptions.scales.y,
+ ...(chartOptions.scales?.y || {}),
+ ticks: {
+ ...baseOptions.scales.y.ticks,
+ ...(chartOptions.scales?.y?.ticks || {})
+ }
+ },
+ x: {
+ ...baseOptions.scales.x,
+ ...(chartOptions.scales?.x || {}),
+ ticks: {
+ ...baseOptions.scales.x.ticks,
+ ...(chartOptions.scales?.x?.ticks || {})
+ }
+ }
+ },
+ plugins: {
+ ...baseOptions.plugins,
+ ...(chartOptions.plugins || {}),
+ tooltip: {
+ ...baseOptions.plugins.tooltip,
+ ...(chartOptions.plugins?.tooltip || {}),
+ callbacks: {
+ ...baseOptions.plugins.tooltip.callbacks,
+ ...(chartOptions.plugins?.tooltip?.callbacks || {})
+ }
+ }
+ }
+ };
+
+ this.chartInstance = new Chart(ctx, {
+ type: 'line',
+ data: chartData,
+ options: finalOptions
+ });
+ }
+
+ addEventListeners() {
+ const header = this.shadowRoot.querySelector('.window-header');
+ const minimizeButton = this.shadowRoot.querySelector('.minimize-button');
+ const closeButton = this.shadowRoot.querySelector('.close-button');
+ const resizeHandle = this.shadowRoot.querySelector('.resize-handle');
+
+ header.addEventListener('mousedown', this.handleMouseDown);
+ minimizeButton.addEventListener('click', this.handleMinimize);
+ closeButton.addEventListener('click', this.handleClose);
+ resizeHandle.addEventListener('mousedown', this.handleResizeStart);
+
+ document.addEventListener('mousemove', this.handleMouseMove);
+ document.addEventListener('mouseup', this.handleMouseUp);
+ }
+
+ removeEventListeners() {
+ document.removeEventListener('mousemove', this.handleMouseMove);
+ document.removeEventListener('mouseup', this.handleMouseUp);
+ }
+
+ handleMouseDown(e) {
+ // 检查点击是否在标题栏或其子元素上
+ const header = this.shadowRoot.querySelector('.window-header');
+ if (header && (e.target === header || header.contains(e.target)) && !this.isMinimized) {
+ this.isDragging = true;
+ this.dragOffset = {
+ x: e.clientX - this.position.x,
+ y: e.clientY - this.position.y
+ };
+ // 点击标题栏时置顶
+ const window = this.shadowRoot.querySelector('.floating-window');
+ if (window) {
+ this.zIndex = FloatingChartWindow.zIndexCounter++;
+ window.style.zIndex = this.zIndex;
+ }
+ }
+ }
+
+ handleMouseMove(e) {
+ if (this.isDragging) {
+ // 限制拖动范围在当前页面区域
+ const minX = 300; // 左侧工具栏宽度
+ const minY = 100; // 顶部标签页栏高度
+
+ this.position = {
+ x: Math.max(minX, e.clientX - this.dragOffset.x),
+ y: Math.max(minY, e.clientY - this.dragOffset.y)
+ };
+ this.updatePosition();
+ } else if (this.isResizing && !this.isMinimized) {
+ const deltaX = e.clientX - this.resizeStart.x;
+ const deltaY = e.clientY - this.resizeStart.y;
+
+ this.size = {
+ width: Math.max(300, this.size.width + deltaX),
+ height: Math.max(200, this.size.height + deltaY)
+ };
+
+ this.resizeStart = {
+ x: e.clientX,
+ y: e.clientY
+ };
+
+ this.updateSize();
+ }
+ }
+
+ handleMouseUp() {
+ this.isDragging = false;
+ this.isResizing = false;
+ }
+
+ handleResizeStart(e) {
+ this.isResizing = true;
+ this.resizeStart = {
+ x: e.clientX,
+ y: e.clientY
+ };
+ }
+
+ handleResize(e) {
+ if (this.isResizing && !this.isMinimized) {
+ const deltaX = e.clientX - this.resizeStart.x;
+ const deltaY = e.clientY - this.resizeStart.y;
+
+ this.size = {
+ width: Math.max(300, this.size.width + deltaX),
+ height: Math.max(200, this.size.height + deltaY)
+ };
+
+ this.resizeStart = {
+ x: e.clientX,
+ y: e.clientY
+ };
+
+ this.updateSize();
+
+ // 更新图表大小
+ if (this.chartInstance) {
+ this.chartInstance.resize();
+ }
+ }
+ }
+
+ handleResizeEnd() {
+ this.isResizing = false;
+ }
+
+ handleMinimize() {
+ this.isMinimized = !this.isMinimized;
+ const content = this.shadowRoot.querySelector('.window-content');
+ const minimizeButton = this.shadowRoot.querySelector('.minimize-button');
+
+ if (this.isMinimized) {
+ content.classList.add('minimized');
+ minimizeButton.textContent = '□';
+ } else {
+ content.classList.remove('minimized');
+ minimizeButton.textContent = '−';
+ }
+ }
+
+ handleClose() {
+ this.dispatchEvent(new CustomEvent('close'));
+ this.remove();
+ }
+
+ updatePosition() {
+ const window = this.shadowRoot.querySelector('.floating-window');
+ window.style.left = `${this.position.x}px`;
+ window.style.top = `${this.position.y}px`;
+ }
+
+ updateSize() {
+ const window = this.shadowRoot.querySelector('.floating-window');
+ window.style.width = `${this.size.width}px`;
+ window.style.height = `${this.size.height}px`;
+
+ // 强制图表重新渲染
+ if (this.chartInstance) {
+ this.chartInstance.resize();
+ }
+ }
+
+ updateTitle(title) {
+ const titleElement = this.shadowRoot.querySelector('.window-title');
+ titleElement.textContent = title;
+ }
+
+ updateChartData(data) {
+ if (this.chartInstance) {
+ // 更新数据
+ this.chartInstance.data = data;
+
+ // 强制更新配置
+ const options = this.chartInstance.options;
+ options.scales.y.ticks.callback = function(value) {
+ if (Math.abs(value) > 1000 || (Math.abs(value) < 0.1 && value !== 0)) {
+ return value.toExponential(1);
+ }
+ return value.toFixed(1);
+ };
+
+ options.scales.x.ticks.callback = function(value) {
+ if (value > 1000) {
+ return value.toExponential(1);
+ }
+ return value;
+ };
+
+ options.plugins.tooltip.callbacks.label = function(context) {
+ const value = context.raw;
+ // 始终使用普通数字格式,保留一位小数
+ return `${context.dataset.label}: ${value.toFixed(1)}`;
+ };
+
+ // 更新图表
+ this.chartInstance.update('none');
+ }
+ }
+
+ updateChartOptions(options) {
+ if (this.chartInstance) {
+ Object.assign(this.chartInstance.options, options);
+ this.chartInstance.update('none');
+ }
+ }
+
+ updateZIndex(zIndex) {
+ const window = this.shadowRoot.querySelector('.floating-window');
+ if (window) {
+ window.style.zIndex = zIndex;
+ }
+ }
+}
+
+customElements.define('floating-chart-window', FloatingChartWindow);
\ No newline at end of file
diff --git a/XNSimHtml/components/data-monitor.js b/XNSimHtml/components/data-monitor.js
index b62e000..f41e406 100644
--- a/XNSimHtml/components/data-monitor.js
+++ b/XNSimHtml/components/data-monitor.js
@@ -13,67 +13,29 @@ class DataMonitor extends HTMLElement {
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; // 数据更新定时器
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);
+
+ // 确保 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();
- // 延迟多次尝试,直到拿到有效宽度
- 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;
- }
-
+
// 等待组件完全加载后初始化
setTimeout(() => {
this.initializeComponent();
@@ -110,8 +72,6 @@ class DataMonitor extends HTMLElement {
if (statusText) {
statusText.textContent = '未监控';
}
-
- this.renderTable();
} catch (error) {
console.error('初始化组件失败:', error);
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
@@ -220,15 +180,40 @@ class DataMonitor extends HTMLElement {
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()
+ monitorData: '',
+ isMonitoring: false
});
- this.renderTable();
+
+ // 创建新行
+ const tbody = this.shadowRoot.querySelector('.data-table tbody');
+ const tr = document.createElement('tr');
+ tr.innerHTML = `
+ ${item.InterfaceName} |
+ ${item.ModelStructName} |
+ - |
+ - |
+
+
+
+
+
+
+
+ |
+ `;
+
+ // 添加删除按钮事件
+ const deleteButton = tr.querySelector('.action-button.delete');
+ deleteButton.addEventListener('click', () => {
+ this.handleDelete(item.InterfaceName, item.ModelStructName);
+ });
+
+ tbody.appendChild(tr);
}
}
@@ -277,6 +262,57 @@ class DataMonitor extends HTMLElement {
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 启动数据更新定时器
*/
@@ -311,7 +347,12 @@ class DataMonitor extends HTMLElement {
row.isMonitoring = false;
row.monitorData = '';
});
- this.renderTable();
+ // 只更新数据单元格
+ const dataCells = this.shadowRoot.querySelectorAll('.data-cell');
+ dataCells.forEach(cell => {
+ cell.textContent = '-';
+ cell.title = '-';
+ });
return; // 未初始化时等待下一次循环
}
@@ -344,7 +385,6 @@ class DataMonitor extends HTMLElement {
// 获取监控数据
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}`);
@@ -364,33 +404,18 @@ class DataMonitor extends HTMLElement {
}
// 更新表格数据
- this.tableData.forEach(row => {
- if (row.ModelStructName === structName && responseData.data[row.InterfaceName]) {
- row.monitorData = responseData.data[row.InterfaceName];
- }
- });
+ this.updateTableData(responseData.data);
} 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');
@@ -399,14 +424,16 @@ class DataMonitor extends HTMLElement {
statusText.textContent = '监控错误';
}
- // 清空所有监控数据
this.tableData.forEach(row => {
row.isMonitoring = false;
row.monitorData = '';
});
-
- this.renderTable();
- alert(`数据监控发生错误: ${error.message}`);
+ // 只更新数据单元格
+ const dataCells = this.shadowRoot.querySelectorAll('.data-cell');
+ dataCells.forEach(cell => {
+ cell.textContent = '-';
+ cell.title = '-';
+ });
}
}, 1000); // 每秒更新一次
}
@@ -442,103 +469,6 @@ class DataMonitor extends HTMLElement {
}
}
- /**
- * @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} 颜色字符串
@@ -553,44 +483,194 @@ class DataMonitor extends HTMLElement {
}
/**
- * @description 更新图表,支持多条曲线
+ * @description 处理绘图按钮点击事件
+ * @param {string} interfaceName - 接口名称
+ * @param {string} modelStructName - 结构体名称
*/
- updateChart() {
- if (!this.chart) return;
- if (!this.activeChartIndexes || this.activeChartIndexes.length === 0) {
- this.chart.data.labels = [];
- this.chart.data.datasets = [];
- this.chart.update('none');
+ async handlePlot(interfaceName, modelStructName) {
+
+ // 检查是否已经存在该接口的图表窗口
+ const windowId = `${interfaceName}_${modelStructName}`;
+ if (this.chartWindows.has(windowId)) {
+ // 如果窗口已存在,将其置顶
+ const window = this.chartWindows.get(windowId);
+ window.setAttribute('z-index', Date.now());
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
+
+ if (typeof Chart === 'undefined') {
+ console.log('Chart.js 未加载...');
+ }
+
+ try {
+ // 创建图表数据
+ const chartData = {
+ labels: [],
+ datasets: [{
+ label: interfaceName,
+ data: [],
+ borderColor: this.getRandomColor(),
+ fill: false
+ }]
};
- });
- this.chart.update('none');
+
+ // 创建图表配置
+ const chartOptions = {
+ responsive: true,
+ maintainAspectRatio: false,
+ animation: false,
+ elements: {
+ point: {
+ radius: 0
+ },
+ line: {
+ tension: 0
+ }
+ },
+ scales: {
+ y: {
+ beginAtZero: false,
+ 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;
+ // 如果数值的绝对值大于1000或小于0.1,使用科学计数法
+ if (Math.abs(value) > 1000 || (Math.abs(value) < 0.1 && value !== 0)) {
+ return `${context.dataset.label}: ${value.toExponential(1)}`;
+ }
+ // 否则使用普通数字,保留一位小数
+ 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 = JSON.parse(row.monitorData);
+ let value;
+ // 处理数值类型
+ if (typeof data === 'number') {
+ value = data;
+ } else if (typeof data === 'object') {
+ value = JSON.stringify(data);
+ } else {
+ // 尝试将字符串转换为数字
+ const numValue = parseFloat(data);
+ value = isNaN(numValue) ? data : numValue;
+ }
+
+ // 使用独立的数据点计数器
+ chartData.labels.push(floatingWindow.dataPointIndex.toString());
+ chartData.datasets[0].data.push(value);
+ floatingWindow.dataPointIndex++; // 增加计数器
+
+ // 保持最近100个数据点
+ if (chartData.labels.length > 100) {
+ chartData.labels.shift();
+ chartData.datasets[0].data.shift();
+ }
+
+ // 如果是第一个数据点,设置y轴范围
+ if (chartData.datasets[0].data.length === 1 && typeof value === 'number') {
+ // 计算合适的y轴范围
+ const range = Math.abs(value) * 0.2; // 使用20%的范围
+ chartOptions.scales.y.min = value - range;
+ chartOptions.scales.y.max = value + range;
+ // 更新图表配置
+ floatingWindow.setAttribute('chart-options', JSON.stringify(chartOptions));
+ }
+
+ // 更新图表数据
+ floatingWindow.setAttribute('chart-data', JSON.stringify(chartData));
+ } 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 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] || [];
@@ -659,29 +739,6 @@ class DataMonitor extends HTMLElement {
background: #d9d9d9;
}
- .global-monitor-btn {
- padding: 6px 16px;
- border: none;
- border-radius: 4px;
- background: #1890ff;
- color: white;
- cursor: pointer;
- font-size: 14px;
- transition: all 0.3s;
- }
-
- .global-monitor-btn:hover {
- background: #40a9ff;
- }
-
- .global-monitor-btn.monitoring {
- background: #ff4d4f;
- }
-
- .global-monitor-btn.monitoring:hover {
- background: #ff7875;
- }
-
.monitor-container {
background-color: white;
border-radius: 8px;
@@ -764,213 +821,179 @@ class DataMonitor extends HTMLElement {
overflow: hidden;
}
- .table-section {
- background: #fff;
- border-radius: 8px 8px 0 0;
- box-shadow: 0 2px 8px rgba(0,0,0,0.06);
- padding: 0;
- margin-bottom: 0;
- overflow: hidden;
- max-height: 70vh;
- min-height: 60px;
- height: ${this.tableHeight}px;
- transition: height 0.1s;
- display: flex;
- flex-direction: column;
- }
- .table-scroll-x {
- width: 100%;
- height: 100%;
- overflow-x: auto;
- overflow-y: hidden;
- display: flex;
- flex-direction: column;
- }
- .table-header-fixed {
- overflow: hidden;
- width: fit-content;
- }
- .table-header-fixed .monitor-table {
- width: max-content;
- min-width: 100%;
- }
- .table-body-scroll {
+ .table-container {
flex: 1;
- overflow-y: auto;
+ overflow: auto;
+ margin-top: 16px;
width: 100%;
- overflow-x: hidden;
- overflow-x: auto;
- max-height: calc(100% - 1px);
+ position: relative;
}
- .table-body-scroll .monitor-table {
- width: max-content;
- min-width: 100%;
- }
- .monitor-table {
- width: max-content;
- min-width: 100%;
- border-collapse: separate;
- border-spacing: 0;
+
+ .data-table {
+ width: 100%;
+ border-collapse: collapse;
+ background: white;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.06);
table-layout: fixed;
- min-width: unset;
}
- .monitor-table th, .monitor-table td {
- padding: 8px;
- border-bottom: 1px solid #e0e0e0;
+
+ .data-table th,
+ .data-table td {
+ padding: 12px 16px;
text-align: left;
- background: #fff;
+ border-bottom: 1px solid #f0f0f0;
+ border-right: 1px solid #f0f0f0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
position: relative;
- max-width: 0;
}
- .cell-text {
- display: inline-block;
- max-width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- vertical-align: middle;
+
+ .data-table th:last-child,
+ .data-table td:last-child {
+ border-right: none;
+ width: 300px !important; /* 固定操作列宽度 */
}
- .monitor-table input,
- .monitor-table button {
- min-width: 0;
- max-width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+
+ .data-table th:last-child .resizer {
+ display: none; /* 隐藏操作列的调整器 */
}
- .monitor-table th {
- background: #f5f5f5;
- position: sticky;
- top: 0;
- z-index: 2;
- position: relative;
+
+ .data-table th {
+ background: #fafafa;
+ font-weight: 500;
+ color: #262626;
+ user-select: none;
}
- /* 竖线:表头和内容区 */
- .monitor-table td:not(:last-child)::after {
- content: '';
+
+ .data-table tr:hover {
+ background: #fafafa;
+ }
+
+ .resizer {
position: absolute;
right: 0;
- top: 20%;
- width: 1px;
- height: 60%;
- background: #e0e0e0;
- z-index: 1;
+ top: 0;
+ bottom: 0;
+ width: 5px;
+ cursor: col-resize;
+ background: transparent;
}
- .monitor-table th.th-resize-active::after {
+
+ .resizer:hover,
+ .resizer.active {
background: #1890ff;
}
- .divider {
- height: 12px;
+ .action-buttons {
+ display: flex;
+ gap: 8px;
+ }
+
+ .action-button {
+ padding: 4px 8px;
+ border: 1px solid #d9d9d9;
+ border-radius: 4px;
+ background: white;
+ cursor: pointer;
+ font-size: 12px;
+ color: #262626;
+ transition: all 0.3s;
+ }
+
+ .action-button:hover {
+ color: #1890ff;
+ border-color: #1890ff;
+ }
+
+ .action-button.delete {
+ color: #ff4d4f;
+ }
+
+ .action-button.delete:hover {
+ border-color: #ff4d4f;
+ }
+
+ .data-cell {
+ font-family: monospace;
+ white-space: pre-wrap;
+ word-break: break-all;
+ max-height: 100px;
+ overflow-y: auto;
+ }
+
+ .floating-chart-window {
+ position: fixed;
+ width: 400px;
+ height: 300px;
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ left: 100px;
+ top: 100px;
+ }
+
+ .window-header {
+ background: #f5f5f5;
+ padding: 8px 12px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: move;
+ user-select: none;
+ border-bottom: 1px solid #e8e8e8;
+ }
+
+ .window-title {
+ font-size: 14px;
+ font-weight: 500;
+ color: #262626;
+ }
+
+ .window-controls {
+ display: flex;
+ gap: 8px;
+ }
+
+ .window-control-button {
+ width: 24px;
+ height: 24px;
+ border: none;
background: transparent;
- cursor: row-resize;
- width: 100%;
- position: relative;
- z-index: 2;
- transition: background 0.2s;
+ cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
- }
- .divider-bar {
- width: 60px;
- height: 5px;
- background: #c0c4cc;
- border-radius: 3px;
- box-shadow: 0 1px 4px rgba(0,0,0,0.08);
- transition: background 0.2s, box-shadow 0.2s;
- }
- .divider:hover .divider-bar,
- .divider.active .divider-bar {
- background: #1890ff;
- box-shadow: 0 2px 8px rgba(24,144,255,0.15);
+ font-size: 16px;
+ color: #666;
+ border-radius: 4px;
+ transition: all 0.3s;
}
- .chart-section {
- background: #fff;
- border-radius: 0 0 8px 8px;
- box-shadow: 0 2px 8px rgba(0,0,0,0.06);
+ .window-control-button:hover {
+ background: rgba(0, 0, 0, 0.05);
+ color: #262626;
+ }
+
+ .window-control-button.close-button:hover {
+ background: #ff4d4f;
+ color: white;
+ }
+
+ .window-content {
+ flex: 1;
padding: 12px;
- flex: 1;
- display: flex;
- flex-direction: column;
- min-height: 120px;
- min-width: 0;
- overflow: hidden;
- }
- .chart-title {
- font-size: 15px;
- font-weight: bold;
- margin-bottom: 8px;
- }
- .chart-container {
- flex: 1;
- min-height: 120px;
position: relative;
}
- #monitor-chart {
+
+ .window-content canvas {
width: 100% !important;
height: 100% !important;
- display: block;
- }
-
- .action-btn {
- margin: 0 2px;
- padding: 3px 8px;
- border: none;
- border-radius: 4px;
- background: #f0f0f0;
- color: #333;
- font-size: 13px;
- cursor: pointer;
- transition: background 0.2s;
- }
- .action-btn:hover {
- background: #e6f7ff;
- }
- .chart-btn.drawing {
- background: #1890ff;
- color: #fff;
- }
-
- .th-resize {
- position: absolute;
- right: 0;
- top: 0;
- width: 12px;
- height: 100%;
- cursor: col-resize;
- z-index: 10;
- user-select: none;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .th-resize::after {
- content: '';
- position: absolute;
- right: 0;
- top: 20%;
- width: 1px;
- height: 60%;
- background: #e0e0e0;
- border-radius: 2px;
- transition: background 0.2s;
- }
- .th-resize:hover::after,
- .th-resize.active::after {
- background: #1890ff;
- }
- .monitor-btn.monitoring {
- background: #ff4d4f;
- color: #fff;
- }
- .action-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
}
@@ -1008,48 +1031,35 @@ class DataMonitor extends HTMLElement {
@@ -1070,75 +1080,114 @@ class DataMonitor extends HTMLElement {
};
});
- // 渲染表格内容
- this.renderTable();
+ // 添加列宽调整功能
+ const table = this.shadowRoot.querySelector('.data-table');
+ const resizers = table.querySelectorAll('.resizer');
+
+ resizers.forEach(resizer => {
+ let startX, startWidth;
- // 初始化图表
- 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) => {
+ resizer.addEventListener('mousedown', (e) => {
e.preventDefault();
- this.dragColIndex = parseInt(handle.getAttribute('data-col'));
- this.dragStartX = e.clientX;
- this.dragStartWidth = this.colWidths[this.dragColIndex];
+ 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';
- 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');
- };
+ resizer.classList.add('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();
+
+ // 添加删除按钮的事件委托
+ 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);
}
- };
- 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 = '';
+ });
+
+ // 添加绘图按钮的事件委托
+ 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);
}
- };
+ });
+ }
+
+ /**
+ * @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);
+ }
}
disconnectedCallback() {
this.isActive = false;
+ // 清理所有图表窗口
+ this.chartWindows.forEach(window => {
+ window.remove();
+ });
+ this.chartWindows.clear();
// 组件销毁时清理定时器
this.stopDataUpdateTimer();
}
}
-customElements.define('data-monitor', DataMonitor);
\ No newline at end of file
+customElements.define('data-monitor', DataMonitor);
+