/**
* @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]) => `
${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);