From 51d3ff73e75ae09e8b7454428b1394c2bf477b88 Mon Sep 17 00:00:00 2001 From: jinchao <383321154@qq.com> Date: Tue, 17 Jun 2025 11:14:33 +0800 Subject: [PATCH] =?UTF-8?q?V0.22.3.250617=5Falpha:=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=87=87=E9=9B=86=E9=A1=B5=E9=9D=A2=E7=9A=84?= =?UTF-8?q?=E7=BB=98=E5=9B=BE=E4=B8=8E=E4=B8=8B=E8=BD=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Release/database/XNSim.db | Bin 1224704 -> 1224704 bytes XNSimHtml/assets/icons/png/download.png | Bin 0 -> 2606 bytes XNSimHtml/assets/icons/png/download_b.png | Bin 0 -> 2742 bytes XNSimHtml/components/FloatingChartWindow.js | 60 ++- XNSimHtml/components/data-collection.js | 514 ++++++++++++++++++-- XNSimHtml/routes/filesystem.js | 103 ++++ 6 files changed, 621 insertions(+), 56 deletions(-) create mode 100644 XNSimHtml/assets/icons/png/download.png create mode 100644 XNSimHtml/assets/icons/png/download_b.png diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index fb097dc06935509a87b83d4add9100fb98483ebe..ce1090bedf8f7a06a4ef49380ff817dbb7284e8f 100644 GIT binary patch delta 384 zcmZp8;MMTJYl1Z6`iU~mjO!Z{S`!$zCNM2|&uq(^J)QjnQ#=!I_V%<7Ow7F8T+EV; z26{$Ddd9Zf%lcSY LZa0t=x-bm@1)P`? delta 212 zcmZp8;MMTJYl1Z6nu#*bjB6SbS`!$zCNM2|&uqh6Hl6(gQ~dUf4@}Iw69agsXa8m5 z++P2e>7^iZ;|-VTXZV;c)lCfy%nVE|Ele$q^m6jkjSP%Tbq&mP4b4If%&kn#tc)%7 z%#6&VrWbNE%TJf(X69=D!p{uEEI`Z(#B4y!4#XTl%sKrFKbL?QC#L}e-(9{cJ`UbQ uo}1j?x#hX0avE$_1j@7q#__(A%IE+0Tq!gmo15mP^cC#il`7=q7|V8fw0_C5y}!m z6+xC!q9}-t@Q9Q!ECpFgAOi@>5>iSKqbw;)B7}swoj+lo`DLD&AI|xn^PT5?-}9Xx zzW2-c%8&B7wxKov0H4#SUV&)r`g7qnp?h`k(^52Gq5~-&z|%fsDFEQ#)4bqgaiMc1 z&X4ATi0R2+pN}|m4?3~h;z8nl)vnb_Irci{BAJwFkln6DI$w_b{MUn-FYA2-$Kmqe zPZtxxGGhNtD(NOq_jy2jaB%PiyZPpmJI5uK3BylZKbCGJ@_W~ZXIB|)rZ>^riL>0xmL;r&@oJ^^7VlK-# z3wK6-2)nUJ!hkevR)aFII4Y0#i6M)oN@fRX1#Z4is?q(nliijytkd!@tgxLuxs14Bu}b9yX`iC4;*+ zl`_*IT1x*R#4Dq^8Wm9E0(D$QHp5(?JPORvrPEP?bt2Cou4V^9!uU+#bS>)YOyJB_ z6#xGR{!jcM9vh>Wp{*gj0F1v!d5-9Z|&U*v8pz1>dK@8=6e@YC5Gltg zVmXxd>_F{CaZPur(1#<56{!qq3Tk}(;-is2nvcYn6snTsttEY)9Q;_U!qIoFBdwfa z5>;Nvj((TAL+HVw(OHUI>e?sXR842%Yk^yyYg%I(r1xzqU%XfPFoTj~ymH(jVT{TV znB$?YcKYPJKX*C5O-QmWiepM1iKz_}0<}IqARz*6FRQWj%Fnhu3*Mj(Z z?~-p84%W_(kIR_ zp81!;>r^c68tKvYha%g&9?MB;%_%Es$@{zE2l{PHJoDGv@wpiaQFu4iZ zzZgQ+PYuW)L;aoJ4*XH^s9}**Cf&t4(y-F?kD1M7K8q9PmxNv;lS{Aa6hh`gd4`*s z$q;7U@cCG!`A4hOTC@ZIgn0PyA+BWJ-TC*q21U_`b^6_t4`7x!l+0f38mw(gPso<| z*hgg7Sy3*8ZA#}l(>~S;nL`P&LR@rzol5K& zzT)1a4HG2qH>UVXEerOCnwE6I>>$PTT8B0fggt_WMa{dUB?TU*?Q8FPY85{}3&I@P zt-mj~zWpG>4B`;z418FV0K$hw3fU9%IO#jMI|afGNSo3ly$K-xbdethh4?94U91s^ z`e`6W8^R$m4GWVYDE}`755ERr0&kGLMVJZUber~Gm{;Ylzu!(z@DyydHm#~ z19abq<=qpw;wrU9#WZq~E3u@p7A_|Uq64wp1hX1ebFy9OwnPb+^oH8tH}Ha6ddl^X zst-)nvT%i@EJo}`W@O`EIQne^de{Ig>Tf=5YjjGuDAF;qNI~uM^-Hr9sCu~fZ~xtw aveSkuq4G`Z2O`k{5TJScc|G+AL;eZxFN>}K literal 0 HcmV?d00001 diff --git a/XNSimHtml/assets/icons/png/download_b.png b/XNSimHtml/assets/icons/png/download_b.png new file mode 100644 index 0000000000000000000000000000000000000000..9d064f3538729a0434bc7a3cf62f4be5976da9d9 GIT binary patch literal 2742 zcmeHJ`&Uy*628I6TtwvpA^{?~gdrLc85Myb2sfaFAfl+CpkWb)j5zXK2=YiU28cWY zV#ddSFd~s*fQ$ozfFp7k6NJj%F9Xy^=lbK~a#UcFf_$lfSc;znfHm=cKFHBj#?;L~ zetC#l?#xY_zV2*8!%F+wfzzX(QY|S2}N2ao(GT64#?DF;QrFJ=ymT;S&tmq6di54&T@rs@MSkvbpB{AC<2AWkYDXToG zf^_b>-`WfW&>Q+1idcmKPnH7QSvNR-lOEW)kcF=G;X}Sz;mEs|%ZXF~DZ~G`TMfdr zO)fzPL70EVyD@qb23w=t-*14yCM13*tFz65lV8&)Hd*k&^^zZxu~3!3io6*Mb@kmk z(I^18e^0O-=K)+-iqlz{u~)HMY*iWL1#QlambmGGK|O^+Q$6tQ*t5D5zQBJ{=@HS# zc25Sq_1Md?T#CxHbL}`?53Iy)DP)~mD#s~VqSo_TsZ{!j**NTSwjt`>7a#AxJ4&A% zggTk_R`@5BrwbVt#$J%^Gaca~+}-hm67n%PWa$f$e`{U@PY)1~f(!ONHhhzxoCI*t z>2qr1uIrv$+^P_PH)Q8^29H#Ay^`7TcW|&f^u_pGn4a}FS1#>RQ@t+~`58RD_9SIJ zA_PPU*vlqo!;0YU72_^kOCp}T1w&VYL_1ltHBzvQ;^W6%v4{19-Nrujx^o^}+yaV! z)1UA!Uu^lp<0EO<9TO=2{|5eVydZd2-6p{)hp6CvgWL5_IN|j3@%Ww-YNLjV30wYb zXn#0{_nhSDc{Pds*Lku1YY5@>cw~H-4Y;=bR2qplYZ-Abc{zMN-Zg$?xRF^Ar$sc+ zGq5!+!SuPpGZ+to&!+}>+;F{LL% zCyI|v7sCDOJHpsYJN6!Vk=W#Ial;iIE)cq1_oW@~;(lr#kCT7jRyVj<%n5F4Zy>Q1*~r7HsNSELJnj zBUjFY3X$pk_QMZ2*Mdf!1xiBP(O%E+GJv~H8l&hJtF8Gv*k=_T*ldw-sZi;vbFdCK zHSsDoh+qtF6*=jO#sVE`v@gy*Do!fb1?shvNGSKJhxC$}TxhhZ%XmTN*-Lqp-nZ*@ zvv09oIXf~^`R!Lm*6y3AG=Ix0j*Q<^Rm%%1T~pQP=@DXYNkzxR#Dvw5OvbjLy1LH& zoWEz!p12#5=tnnO9H+!^7OemDmb%TT8y6Tr`9(N?D*>;P5BCn47{I+I3dV5T%%>hY z*|!>J9_hY!?H{&B-dFZ48o+#o;bhx%^#jgz*8S+QX##ccZh>--lSl9Ng8;ITDH6Ya z^(t*#qtV1sL6;Ec^w`WytsqGy)zR+8~OA%00si!!dT-{_;dY=NO|mi7NYrIEEU7vhFI(0E8%EcUQG zK$hr$#em%eN7eV5Sc$#_sS-kCdFF#lT8T&~&xRQ6!rb{HoO0@qZ`?x`iZzM)2xH6* zoTqMa`Ct)d|EaMK!Xg_#o2SwzO>KCN46_eW(q(@`mtkf$$b~C6mj-=I0~M>TdCPk# zo?Bw>KFu@6B3*%+d86+2*6a=^F3dj;1!AxW6gYG0cYAdEVK8+K402P`X5OCqa<{1a z2!KFc&(qCk@?en2b}H9Hnaf8Xq=ulVx!ZFayddn{`_ab_h#;3wD69rhlIo@Hqh<)? zv+|w$YScjYhpagmp9h>14!^v^Y_je4E6>LR#CU@pL#SweI^+P|L(ZlrC-MrZt_2V1&9l-%Q`Kh?RUV8?Yv&&&`!Y(9q+mizz z4b2ZBZc&UjJGDuTbwU8G*@tKH0B(z$%M<#4^ { + const btn = e.target.closest('#downloadBtn'); + if (btn) { + this.handleDownload(); + return; + } if (e.target.id === 'loadScriptBtn') { this.handleLoadScript(); } else if (e.target.id === 'startCollectBtn') { @@ -24,6 +30,13 @@ class DataCollection extends HTMLElement { 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() { @@ -73,23 +86,47 @@ class DataCollection extends HTMLElement { } 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) { + // 检查监控状态 + 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; } - this.updateMonitorStatus(); }, 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; @@ -162,21 +199,15 @@ class DataCollection extends HTMLElement { // 清空输入框 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'); + const downloadBtn = this.shadowRoot.getElementById('downloadBtn'); scriptFileInput.value = ''; - collectFreqInput.value = '100'; - collectDurationInput.value = '60'; outputFileInput.value = ''; - - // 启用输入框 - collectFreqInput.disabled = false; - collectDurationInput.disabled = false; - outputFileInput.disabled = false; startCollectBtn.disabled = true; + // 禁用下载按钮 + if (downloadBtn) downloadBtn.disabled = true; // 更新按钮文本 const loadScriptBtn = this.shadowRoot.getElementById('loadScriptBtn'); @@ -226,7 +257,7 @@ class DataCollection extends HTMLElement { const lines = fileContent.split('\n'); let duration = 60; // 默认值 - let frequency = 60; // 默认值 + let frequency = 100; // 默认值 let outputFile = 'collect_result.csv'; // 默认值 let structData = {}; // 存储结构体数据 let missingInterfaces = []; // 存储不存在的接口 @@ -332,6 +363,9 @@ class DataCollection extends HTMLElement { if (matches) { duration = parseInt(matches[1]); frequency = parseInt(matches[2]); + // 保存采集参数 + this.collectDuration = duration; + this.collectFreq = frequency; } } @@ -364,15 +398,12 @@ class DataCollection extends HTMLElement { 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; @@ -385,13 +416,6 @@ class DataCollection extends HTMLElement { // 存储结构体数据供后续使用 this.structData = structData; this.outputFile = outputFile; // 保存输出文件名 - - console.log('文件解析成功:', { - duration, - frequency, - outputFile, - structData - }); // 重新渲染组件以显示采集列表 this.render(); @@ -501,14 +525,16 @@ class DataCollection extends HTMLElement { color: #fff; font-size: 14px; cursor: pointer; - transition: background 0.2s; + transition: all 0.3s; } .action-btn:hover { background: #40a9ff; } .action-btn:disabled { background: #d9d9d9; + color: rgba(0, 0, 0, 0.25); cursor: not-allowed; + box-shadow: none; } /* 卸载脚本按钮样式 */ .action-btn.unload { @@ -517,6 +543,10 @@ class DataCollection extends HTMLElement { .action-btn.unload:hover { background: #ff7875; } + .action-btn.unload:disabled { + background: #d9d9d9; + color: rgba(0, 0, 0, 0.25); + } /* 停止采集按钮样式 */ .action-btn.stop { background: #faad14; @@ -524,6 +554,29 @@ class DataCollection extends HTMLElement { .action-btn.stop:hover { background: #ffc53d; } + .action-btn.stop:disabled { + background: #d9d9d9; + color: rgba(0, 0, 0, 0.25); + } + /* 图标按钮样式 */ + .action-btn.icon-btn { + background: none; + border: none; + padding: 0 2px; + width: 28px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + margin-left: 0; + } + .action-btn.icon-btn:disabled { + opacity: 0.4; + } + .action-btn.icon-btn:hover:not(:disabled) { + background: #f0f0f0; + border-radius: 4px; + } .input-row { display: flex; flex-direction: column; @@ -536,6 +589,12 @@ class DataCollection extends HTMLElement { min-width: 0; width: 100%; } + .input-group > div { + display: flex; + align-items: center; + gap: 4px; + width: 100%; + } .input-label { font-size: 13px; color: #666; @@ -549,7 +608,6 @@ class DataCollection extends HTMLElement { align-items: center; } .input-box { - flex: 1; width: 100%; padding: 0 8px; border: 1px solid #d9d9d9; @@ -678,15 +736,20 @@ class DataCollection extends HTMLElement {
采集频率 (Hz)
- +
采集时长 (秒)
- +
输出文件
- +
+ + +
@@ -700,8 +763,9 @@ class DataCollection extends HTMLElement {
${Object.entries(interfaces).map(([interfaceName, [size1, size2]]) => ` -
+
${interfaceName} + ${size1 > 0 ? `[${size1}${size2 > 0 ? `][${size2}` : ''}]` : ''}
`).join('')}
@@ -719,6 +783,16 @@ class DataCollection extends HTMLElement { // 更新监控状态 this.updateMonitorStatus(); + + // 在树型控件部分添加双击事件处理 + this.shadowRoot.querySelectorAll('.interface-item').forEach(itemEl => { + itemEl.ondblclick = (e) => { + const name = itemEl.getAttribute('data-interfacename'); + const struct = itemEl.getAttribute('data-modelstructname'); + console.log('双击接口:', name, struct); // 添加调试日志 + this.handleInterfaceDblClick(name, struct); + }; + }); } // 重新激活组件 @@ -728,10 +802,132 @@ class DataCollection extends HTMLElement { this.startStatusTimer(); } + async loadCollectResult() { + try { + const response = await fetch('/api/filesystem/read', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + filename: 'collect_result.csv' + }) + }); + + if (!response.ok) { + throw new Error(`读取文件失败: ${response.status} ${response.statusText}`); + } + + const result = await response.json(); + if (!result.success) { + throw new Error(result.message || '读取文件失败'); + } + + // 解析CSV数据 + const csvData = result.data; + if (!csvData) { + throw new Error('CSV数据为空'); + } + + const lines = csvData.split('\n'); + if (lines.length < 2) { + throw new Error('CSV文件格式错误:数据行数不足'); + } + + // 获取接口名称(第一行)并解析数组索引 + const interfaceNames = lines[0].split(',').map(name => { + const trimmedName = name.trim(); + // 匹配接口名称和数组索引 + const match = trimmedName.match(/^(.+?)\((\d+)(?:_(\d+))?\)$/); + if (match) { + return { + fullName: trimmedName, + baseName: match[1], + index1: parseInt(match[2], 10) - 1, // 转换为0基索引 + index2: match[3] ? parseInt(match[3], 10) - 1 : null // 转换为0基索引 + }; + } + return { + fullName: trimmedName, + baseName: trimmedName, + index1: null, + index2: null + }; + }); + + // 按接口名称整理数据 + const organizedData = {}; + + // 处理数据行 + for (let i = 1; i < lines.length; i++) { + const values = lines[i].split(',').map(value => parseFloat(value.trim())); + if (values.length === interfaceNames.length) { + // 第一列是时间 + const time = values[0]; + + interfaceNames.forEach(({ fullName, baseName, index1, index2 }, valueIndex) => { + if (valueIndex === 0) return; // 跳过时间列 + + if (index1 !== null) { + // 数组接口 + if (!organizedData[baseName]) { + organizedData[baseName] = { + isArray: true, + data: [], + times: [] // 添加时间数组 + }; + } + if (index2 !== null) { + // 二维数组 + if (!organizedData[baseName].data[index1]) { + organizedData[baseName].data[index1] = []; + } + if (!organizedData[baseName].data[index1][index2]) { + organizedData[baseName].data[index1][index2] = []; + } + organizedData[baseName].data[index1][index2].push(values[valueIndex]); + } else { + // 一维数组 + if (!organizedData[baseName].data[index1]) { + organizedData[baseName].data[index1] = []; + } + organizedData[baseName].data[index1].push(values[valueIndex]); + } + } else { + // 非数组接口 + if (!organizedData[fullName]) { + organizedData[fullName] = { + isArray: false, + data: [], + times: [] // 添加时间数组 + }; + } + organizedData[fullName].data.push(values[valueIndex]); + + // 添加时间到对应的接口数据中 + if (!organizedData[fullName].times.includes(time)) { + organizedData[fullName].times.push(time); + } + } + + // 添加时间到数组接口数据中 + if (index1 !== null && !organizedData[baseName].times.includes(time)) { + organizedData[baseName].times.push(time); + } + }); + } + } + + return organizedData; + } catch (error) { + console.error('加载采集结果失败:', error); + throw error; + } + } + async handleStartCollect() { // 检查监控状态 if (this.monitorStatus !== 1) { - alert('请先启动监控'); return; } @@ -742,6 +938,8 @@ class DataCollection extends HTMLElement { } const startCollectBtn = this.shadowRoot.getElementById('startCollectBtn'); + const loadScriptBtn = this.shadowRoot.getElementById('loadScriptBtn'); + const downloadBtn = this.shadowRoot.getElementById('downloadBtn'); // 如果正在采集,则停止采集 if (this.collectStatus === 2) { @@ -754,11 +952,25 @@ class DataCollection extends HTMLElement { if (result.success) { // 更新采集状态 - this.collectStatus = 1; // 改为已加载脚本状态 + this.collectStatus = 3; // 改为采集完成状态 // 更新按钮状态 startCollectBtn.textContent = '开始采集'; startCollectBtn.disabled = false; startCollectBtn.classList.remove('stop'); + // 启用卸载脚本按钮 + loadScriptBtn.disabled = false; + // 启用下载按钮 + if (downloadBtn) downloadBtn.disabled = false; + + // 加载采集结果 + try { + const collectData = await this.loadCollectResult(); + // 存储采集数据 + this.collectData = collectData; + } catch (error) { + console.error('加载采集数据失败:', error); + alert('加载采集数据失败: ' + error.message); + } } else { throw new Error(result.message); } @@ -791,6 +1003,10 @@ class DataCollection extends HTMLElement { // 更新按钮状态 startCollectBtn.textContent = '停止采集'; startCollectBtn.classList.add('stop'); + // 禁用卸载脚本按钮 + loadScriptBtn.disabled = true; + // 禁用下载按钮 + if (downloadBtn) downloadBtn.disabled = true; } else { throw new Error(result.message); } @@ -799,6 +1015,238 @@ class DataCollection extends HTMLElement { alert('启动采集失败: ' + error.message); } } + + async handleInterfaceDblClick(interfaceName, modelStructName) { + // 只有在采集完成状态才允许绘图 + if (this.collectStatus !== 3) { + return; + } + + // 检查是否已经存在该接口的图表窗口 + const windowId = `${interfaceName}_${modelStructName}`; + if (this.chartWindows.has(windowId)) { + // 如果窗口已存在,将其置顶 + const window = this.chartWindows.get(windowId); + window.setAttribute('z-index', Date.now()); + return; + } + + try { + // 获取左侧面板和工具栏的位置信息 + const leftPanel = this.shadowRoot.querySelector('.left-panel'); + const toolbar = this.shadowRoot.querySelector('.toolbar'); + const leftPanelRect = leftPanel.getBoundingClientRect(); + const toolbarRect = toolbar.getBoundingClientRect(); + + // 计算可用区域 + const minX = leftPanelRect.right; + const maxX = window.innerWidth; + const minY = toolbarRect.bottom; + const maxY = window.innerHeight; + + // 创建图表数据 + const chartData = { + labels: [], + datasets: [] + }; + + // 创建图表配置 + const chartOptions = { + responsive: true, + maintainAspectRatio: false, + animation: false, + elements: { + point: { + radius: 0 + }, + line: { + tension: 0 + } + }, + scales: { + y: { + beginAtZero: false, + display: true, + ticks: { + callback: function(value) { + 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, + type: 'linear', + ticks: { + callback: function(value) { + return value.toFixed(1); + } + } + } + }, + plugins: { + legend: { + display: true, + position: 'top' + }, + tooltip: { + enabled: true, + mode: 'index', + intersect: false, + callbacks: { + label: function(context) { + console.log('Tooltip context:', context); + console.log('Raw data:', context.raw); + + if (!context.raw) { + console.log('No raw data'); + return `${context.dataset.label}: N/A`; + } + + if (typeof context.raw.y === 'undefined') { + console.log('No y value in raw data'); + return `${context.dataset.label}: N/A`; + } + + const value = context.raw.y; + console.log('Value type:', typeof value, 'Value:', value); + + if (typeof value !== 'number') { + console.log('Value is not a number'); + return `${context.dataset.label}: N/A`; + } + + 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('constraints', JSON.stringify({ + minX: minX, + maxX: maxX, + minY: minY, + maxY: maxY + })); + + // 再设置属性 + floatingWindow.setAttribute('title', interfaceName); + floatingWindow.setAttribute('initial-position', JSON.stringify({ + x: minX + 20, // 在左侧面板右侧留出20px的间距 + y: minY + 20 // 在工具栏下方留出20px的间距 + })); + floatingWindow.setAttribute('initial-size', JSON.stringify({ width: 400, height: 300 })); + floatingWindow.setAttribute('chart-data', JSON.stringify(chartData)); + floatingWindow.setAttribute('chart-options', JSON.stringify(chartOptions)); + + // 更新图表数据 + if (this.collectData && this.collectData[interfaceName]) { + const interfaceData = this.collectData[interfaceName]; + if (interfaceData.isArray) { + // 数组接口 + if (interfaceData.data[0] && Array.isArray(interfaceData.data[0][0])) { + // 二维数组 + const datasets = []; + interfaceData.data.forEach((row, i) => { + row.forEach((values, j) => { + if (values && values.length > 0) { + datasets.push({ + label: `${interfaceName}[${i+1}][${j+1}]`, + data: values, + borderColor: `rgb(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)})`, + tension: 0 + }); + } + }); + }); + // 更新图表数据 + floatingWindow.updateChartData({ + labels: interfaceData.times, + datasets: datasets + }); + } else { + // 一维数组 + const datasets = []; + interfaceData.data.forEach((values, i) => { + if (values && values.length > 0) { + datasets.push({ + label: `${interfaceName}[${i+1}]`, + data: values, + borderColor: `rgb(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)})`, + tension: 0 + }); + } + }); + // 更新图表数据 + floatingWindow.updateChartData({ + labels: interfaceData.times, + datasets: datasets + }); + } + } else { + // 非数组接口 + if (interfaceData.data && interfaceData.data.length > 0) { + // 更新图表数据 + floatingWindow.updateChartData({ + labels: interfaceData.times, + datasets: [{ + label: interfaceName, + data: interfaceData.data, + borderColor: 'rgb(75, 192, 192)', + tension: 0 + }] + }); + } + } + } + + // 添加关闭事件处理 + floatingWindow.addEventListener('close', () => { + this.chartWindows.delete(windowId); + }); + } catch (error) { + console.error('创建图表窗口失败:', error); + alert('创建图表窗口失败,请查看控制台了解详情'); + } + } + + async handleDownload() { + if (!this.outputFile) { + alert('没有可下载的文件'); + return; + } + + try { + // 创建一个隐藏的a标签用于下载 + const link = document.createElement('a'); + link.href = `/api/filesystem/download?filename=${encodeURIComponent(this.outputFile)}`; + link.download = this.outputFile; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } catch (error) { + console.error('下载文件失败:', error); + alert('下载文件失败: ' + error.message); + } + } } customElements.define('data-collection', DataCollection); \ No newline at end of file diff --git a/XNSimHtml/routes/filesystem.js b/XNSimHtml/routes/filesystem.js index 84ceb77..051f992 100644 --- a/XNSimHtml/routes/filesystem.js +++ b/XNSimHtml/routes/filesystem.js @@ -358,4 +358,107 @@ router.get('/validate-csv-headers', async (req, res) => { } }); +// 读取CSV文件内容 +router.post('/read', async (req, res) => { + try { + const { filename } = req.body; + if (!filename) { + return res.status(400).json({ + success: false, + message: '未提供文件名' + }); + } + + // 获取上传目录路径 + const uploadPath = await getUploadPath(); + const filePath = path.join(uploadPath, filename); + + // 检查文件是否存在 + try { + await fsPromises.access(filePath); + } catch (error) { + return res.status(404).json({ + success: false, + message: '文件不存在' + }); + } + + // 读取文件内容 + const content = await fsPromises.readFile(filePath, 'utf8'); + + res.json({ + success: true, + data: content + }); + } catch (error) { + console.error('读取CSV文件失败:', error); + res.status(500).json({ + success: false, + message: '读取CSV文件失败: ' + error.message + }); + } +}); + +// 文件下载 +router.get('/download', async (req, res) => { + try { + const { filename } = req.query; + if (!filename) { + return res.status(400).json({ + success: false, + message: '未提供文件名' + }); + } + + // 获取上传目录路径 + const uploadPath = await getUploadPath(); + const filePath = path.join(uploadPath, filename); + + // 安全检查:确保文件在上传目录内 + if (!filePath.startsWith(uploadPath)) { + return res.status(403).json({ + success: false, + message: '无权访问该文件' + }); + } + + // 检查文件是否存在 + try { + await fsPromises.access(filePath); + } catch (error) { + return res.status(404).json({ + success: false, + message: '文件不存在' + }); + } + + // 设置响应头 + res.setHeader('Content-Disposition', `attachment; filename=${encodeURIComponent(filename)}`); + res.setHeader('Content-Type', 'application/octet-stream'); + + // 创建文件流并发送 + const fileStream = fs.createReadStream(filePath); + fileStream.pipe(res); + + // 处理流错误 + fileStream.on('error', (error) => { + console.error('文件下载失败:', error); + if (!res.headersSent) { + res.status(500).json({ + success: false, + message: '文件下载失败: ' + error.message + }); + } + }); + } catch (error) { + console.error('文件下载失败:', error); + if (!res.headersSent) { + res.status(500).json({ + success: false, + message: '文件下载失败: ' + error.message + }); + } + } +}); + module.exports = router; \ No newline at end of file