class DataCollection extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.monitorStatus = 0; // 0-未监控,1-监控中,2-错误 this.statusTimer = null; this.scriptFile = null; // 存储上传的脚本文件 this.structData = null; // 存储结构体数据 this.outputFile = null; // 存储输出文件名 this.collectStatus = 0; // 0-未加载脚本、1-已加载脚本、2-数据采集中、3-采集完成 this.isActive = false; // 组件是否激活 this.chartWindows = new Map(); // 存储打开的图表窗口 // 保存事件处理函数的引用 this.handleClick = (e) => { const btn = e.target.closest('#downloadBtn'); if (btn) { this.handleDownload(); return; } if (e.target.id === 'loadScriptBtn') { this.handleLoadScript(); } else if (e.target.id === 'startCollectBtn') { this.handleStartCollect(); } }; this.handleChange = (e) => { if (e.target.id === 'fileInput') { this.handleFileSelect(e); } }; // 确保 FloatingChartWindow 组件已注册 if (!customElements.get('floating-chart-window')) { const script = document.createElement('script'); script.src = './components/FloatingChartWindow.js'; document.head.appendChild(script); } } 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: '' }; } } async loadInterfaces() { try { const { configurationId } = this.getCurrentSelection(); if (!configurationId) { console.warn('未找到配置ID'); this.interfaces = []; return; } const response = await fetch(`/api/interface/list?systemName=XNSim&confID=${configurationId}`); const data = await response.json(); this.interfaces = data; } catch (error) { console.error('加载接口数据失败:', error); this.interfaces = []; } } async connectedCallback() { this.isActive = true; await this.loadInterfaces(); this.render(); this.startStatusTimer(); } startStatusTimer() { if (this.statusTimer) { clearInterval(this.statusTimer); } this.statusTimer = setInterval(async () => { try { // 检查监控状态 const monitorRes = await fetch('/api/dds-monitor/status'); if (!monitorRes.ok) throw new Error('网络错误'); const monitorData = await monitorRes.json(); if (monitorData.isInitialized) { this.monitorStatus = 1; } else { this.monitorStatus = 0; } // 如果正在采集中,检查采集状态 if (this.collectStatus === 2) { const collectRes = await fetch('/api/data-collect/status'); if (!collectRes.ok) throw new Error('网络错误'); const collectData = await collectRes.json(); if (collectData.status === 0) { // 采集完成 // 模拟点击停止采集按钮 const startCollectBtn = this.shadowRoot.getElementById('startCollectBtn'); if (startCollectBtn) { startCollectBtn.click(); } } } this.updateMonitorStatus(); } catch (e) { this.monitorStatus = 2; } }, 1000); } disconnectedCallback() { this.isActive = false; // 清理所有图表窗口 this.chartWindows.forEach((window, windowId) => { // 触发关闭事件,确保所有清理工作都完成 window.dispatchEvent(new CustomEvent('close')); window.remove(); }); this.chartWindows.clear(); // 组件销毁时清理定时器 if (this.statusTimer) { clearInterval(this.statusTimer); this.statusTimer = null; } } updateMonitorStatus() { const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); const statusText = this.shadowRoot.getElementById('statusText'); if (statusIndicator) { statusIndicator.classList.remove('active', 'inactive', 'error'); switch (this.monitorStatus) { case 1: statusIndicator.classList.add('active'); break; case 2: statusIndicator.classList.add('error'); break; default: statusIndicator.classList.add('inactive'); } } if (statusText) { switch (this.monitorStatus) { case 1: statusText.textContent = '监控中'; break; case 2: statusText.textContent = '监控错误'; break; default: statusText.textContent = '未监控'; } } } handleLoadScript() { if (this.scriptFile) { // 如果已经加载了脚本,则执行卸载操作 this.unloadScript(); } else { // 否则执行加载操作 const fileInput = this.shadowRoot.getElementById('fileInput'); fileInput.click(); } } async unloadScript() { try { if (this.scriptFile) { // 调用后端API删除文件 const response = await fetch(`/api/filesystem/upload/${this.scriptFile.name}`, { method: 'DELETE' }); if (!response.ok) { throw new Error('删除文件失败'); } const result = await response.json(); if (!result.success) { throw new Error(result.message || '删除文件失败'); } } // 清空脚本文件 this.scriptFile = null; this.structData = null; this.outputFile = null; // 清空输入框 const scriptFileInput = this.shadowRoot.getElementById('scriptFileInput'); const outputFileInput = this.shadowRoot.getElementById('outputFileInput'); const startCollectBtn = this.shadowRoot.getElementById('startCollectBtn'); const downloadBtn = this.shadowRoot.getElementById('downloadBtn'); scriptFileInput.value = ''; outputFileInput.value = ''; startCollectBtn.disabled = true; // 禁用下载按钮 if (downloadBtn) downloadBtn.disabled = true; // 更新按钮文本 const loadScriptBtn = this.shadowRoot.getElementById('loadScriptBtn'); loadScriptBtn.textContent = '载入脚本'; // 重新渲染以清空采集列表 this.render(); } catch (error) { console.error('卸载脚本失败:', error); alert('卸载脚本失败: ' + error.message); } } async handleFileSelect(event) { const file = event.target.files[0]; if (!file) return; // 检查文件扩展名 if (!file.name.toLowerCase().endsWith('.dcs')) { alert('请选择.dcs格式的文件'); return; } try { // 创建FormData对象 const formData = new FormData(); formData.append('file', file); // 发送文件到后端 const response = await fetch('/api/filesystem/upload', { method: 'POST', body: formData }); if (!response.ok) { throw new Error('上传失败'); } const result = await response.json(); if (result.success) { this.scriptFile = file; const scriptFileInput = this.shadowRoot.getElementById('scriptFileInput'); scriptFileInput.value = file.name; // 读取文件内容 const fileContent = await file.text(); const lines = fileContent.split('\n'); let duration = 60; // 默认值 let frequency = 100; // 默认值 let outputFile = 'collect_result.csv'; // 默认值 let structData = {}; // 存储结构体数据 let missingInterfaces = []; // 存储不存在的接口 let invalidInterfaces = []; // 存储无效的接口 for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); // 跳过注释行 if (line.startsWith('!')) continue; // 解析采集列表 if (line.startsWith('define collect_list')) { let listContent = ''; let j = i; // 收集直到遇到结束双引号 while (j < lines.length) { const nextLine = lines[j].trim(); listContent += nextLine + ' '; if (nextLine.endsWith('"')) { break; } j++; } // 提取双引号中的内容 const matches = listContent.match(/"([^"]*)"/); if (matches && matches[1]) { // 按'-'分割,并处理每个项目 const collectList = matches[1] .split('-') .map(item => item.trim()) .filter(item => item && !item.startsWith('!')); // 过滤掉空项和注释 // 验证每个接口 for (const interfaceName of collectList) { // 提取接口名称和数组索引 const match = interfaceName.match(/^(.+?)\((\d+)(?:_(\d+))?\)$/); if (!match) { // 如果不是数组格式,直接检查接口是否存在 const interfaceInfo = this.interfaces.find(item => item.InterfaceName === interfaceName ); if (!interfaceInfo) { missingInterfaces.push(interfaceName); } else { // 添加到结构体数据 if (!structData[interfaceInfo.ModelStructName]) { structData[interfaceInfo.ModelStructName] = {}; } structData[interfaceInfo.ModelStructName][interfaceName] = [0, 0]; } } else { // 处理数组格式的接口 const baseInterfaceName = match[1]; const index1 = match[2] ? parseInt(match[2], 10) - 1 : null; const index2 = match[3] ? parseInt(match[3], 10) - 1 : null; const interfaceInfo = this.interfaces.find(item => item.InterfaceName === baseInterfaceName ); if (!interfaceInfo) { missingInterfaces.push(interfaceName); } else { // 检查数组索引是否越界 if (index1 !== null) { if (index2 !== null) { // 二维数组 if (index1 >= interfaceInfo.InterfaceArraySize_1 || index2 >= interfaceInfo.InterfaceArraySize_2 || index1 < 0 || index2 < 0) { invalidInterfaces.push(`${interfaceName} 的数组索引超出范围`); continue; } } else { // 一维数组 if (index1 >= interfaceInfo.InterfaceArraySize_1 || index1 < 0) { invalidInterfaces.push(`${interfaceName} 的数组索引超出范围`); continue; } } } // 添加到结构体数据 if (!structData[interfaceInfo.ModelStructName]) { structData[interfaceInfo.ModelStructName] = {}; } if (!structData[interfaceInfo.ModelStructName][baseInterfaceName]) { structData[interfaceInfo.ModelStructName][baseInterfaceName] = [ interfaceInfo.InterfaceArraySize_1 || 0, interfaceInfo.InterfaceArraySize_2 || 0 ]; } } } } } } // 解析采集时长和频率 if (line.startsWith('for')) { const matches = line.match(/for\s+(\d+)\s+at\s+(\d+)/); if (matches) { duration = parseInt(matches[1]); frequency = parseInt(matches[2]); // 保存采集参数 this.collectDuration = duration; this.collectFreq = frequency; } } // 解析输出文件名 if (line.startsWith('put/extend/all result')) { const matches = line.match(/result\s+(\w+)/); if (matches) { outputFile = matches[1] + '.csv'; } } } // 检查是否有错误 if (missingInterfaces.length > 0 || invalidInterfaces.length > 0) { const errorMessages = []; if (missingInterfaces.length > 0) { errorMessages.push(`以下接口在系统中不存在:\n${missingInterfaces.join('\n')}`); } if (invalidInterfaces.length > 0) { errorMessages.push(`以下接口的数组索引无效:\n${invalidInterfaces.join('\n')}`); } throw new Error(errorMessages.join('\n\n')); } // 更新UI const collectFreqInput = this.shadowRoot.getElementById('collectFreqInput'); const collectDurationInput = this.shadowRoot.getElementById('collectDurationInput'); const outputFileInput = this.shadowRoot.getElementById('outputFileInput'); const startCollectBtn = this.shadowRoot.getElementById('startCollectBtn'); if (collectFreqInput) { collectFreqInput.value = frequency; } if (collectDurationInput) { collectDurationInput.value = duration; } if (outputFileInput) { outputFileInput.value = outputFile; } if (startCollectBtn) { startCollectBtn.disabled = false; } // 更新按钮文本 const loadScriptBtn = this.shadowRoot.getElementById('loadScriptBtn'); loadScriptBtn.textContent = '卸载脚本'; // 存储结构体数据供后续使用 this.structData = structData; this.outputFile = outputFile; // 保存输出文件名 // 重新渲染组件以显示采集列表 this.render(); } else { throw new Error(result.message || '上传失败'); } } catch (error) { console.error('上传文件失败:', error); alert('上传文件失败: ' + error.message); } } render() { // 移除旧的事件监听器 this.shadowRoot.removeEventListener('click', this.handleClick); this.shadowRoot.removeEventListener('change', this.handleChange); // 树型控件分组 const groupedInterfaces = (this.interfaces || []).reduce((groups, item) => { const group = groups[item.ModelStructName] || []; group.push(item); groups[item.ModelStructName] = group; return groups; }, {}); // 获取结构体数据中的接口 const collectGroups = this.structData || {}; this.shadowRoot.innerHTML = `