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.handleClick = (e) => { 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); } }; } 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 res = await fetch('/api/dds-monitor/status'); if (!res.ok) throw new Error('网络错误'); const data = await res.json(); if (data.isInitialized) { this.monitorStatus = 1; } else { this.monitorStatus = 0; } } catch (e) { this.monitorStatus = 2; } this.updateMonitorStatus(); }, 1000); } disconnectedCallback() { this.isActive = false; 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 collectFreqInput = this.shadowRoot.getElementById('collectFreqInput'); const collectDurationInput = this.shadowRoot.getElementById('collectDurationInput'); const outputFileInput = this.shadowRoot.getElementById('outputFileInput'); const startCollectBtn = this.shadowRoot.getElementById('startCollectBtn'); scriptFileInput.value = ''; collectFreqInput.value = '100'; collectDurationInput.value = '60'; outputFileInput.value = ''; // 启用输入框 collectFreqInput.disabled = false; collectDurationInput.disabled = false; outputFileInput.disabled = false; startCollectBtn.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 = 60; // 默认值 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]); } } // 解析输出文件名 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; collectFreqInput.disabled = true; } if (collectDurationInput) { collectDurationInput.value = duration; collectDurationInput.disabled = true; } if (outputFileInput) { outputFileInput.value = outputFile; outputFileInput.disabled = true; } if (startCollectBtn) { startCollectBtn.disabled = false; } // 更新按钮文本 const loadScriptBtn = this.shadowRoot.getElementById('loadScriptBtn'); loadScriptBtn.textContent = '卸载脚本'; // 存储结构体数据供后续使用 this.structData = structData; this.outputFile = outputFile; // 保存输出文件名 console.log('文件解析成功:', { duration, frequency, outputFile, structData }); // 重新渲染组件以显示采集列表 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 = `
未监控
脚本文件
采集频率 (Hz)
采集时长 (秒)
输出文件
采集列表:
${Object.entries(collectGroups).map(([groupName, interfaces]) => `
${groupName}
${Object.entries(interfaces).map(([interfaceName, [size1, size2]]) => `
${interfaceName}
`).join('')}
`).join('')}
`; // 添加新的事件监听器 this.shadowRoot.addEventListener('click', this.handleClick); this.shadowRoot.addEventListener('change', this.handleChange); // 更新监控状态 this.updateMonitorStatus(); } // 重新激活组件 reactivate() { if (this.isActive) return; // 如果已经激活,直接返回 this.isActive = true; this.startStatusTimer(); } async handleStartCollect() { // 检查监控状态 if (this.monitorStatus !== 1) { alert('请先启动监控'); return; } // 检查是否已加载脚本 if (!this.scriptFile) { alert('请先加载脚本'); return; } const startCollectBtn = this.shadowRoot.getElementById('startCollectBtn'); // 如果正在采集,则停止采集 if (this.collectStatus === 2) { try { const response = await fetch('/api/data-collect/stop', { method: 'POST' }); const result = await response.json(); if (result.success) { // 更新采集状态 this.collectStatus = 1; // 改为已加载脚本状态 // 更新按钮状态 startCollectBtn.textContent = '开始采集'; startCollectBtn.disabled = false; startCollectBtn.classList.remove('stop'); } else { throw new Error(result.message); } } catch (error) { console.error('停止采集失败:', error); alert('停止采集失败: ' + error.message); } return; } // 开始采集 try { // 调用后端接口启动采集 const response = await fetch('/api/data-collect/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ collectDataInfo: JSON.stringify(this.structData), dcsFilePath: this.scriptFile.name }) }); const result = await response.json(); if (result.success) { // 更新采集状态 this.collectStatus = 2; // 设置为采集中 // 更新按钮状态 startCollectBtn.textContent = '停止采集'; startCollectBtn.classList.add('stop'); } else { throw new Error(result.message); } } catch (error) { console.error('启动采集失败:', error); alert('启动采集失败: ' + error.message); } } } customElements.define('data-collection', DataCollection);