index.html 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  1. <!DOCTYPE html>
  2. <html lang="zh">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>DIPM_resultStatistics</title>
  7. <style>
  8. body {
  9. font-family: Arial, sans-serif;
  10. margin: 0;
  11. padding: 0;
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. min-height: 100vh;
  16. background-color: #f4f4f9;
  17. }
  18. .container {
  19. display: flex;
  20. flex-direction: column;
  21. width: 90%;
  22. max-width: 800px;
  23. background: #fff;
  24. padding: 1.5rem;
  25. border-radius: 8px;
  26. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  27. }
  28. .tabs {
  29. display: flex;
  30. border-bottom: 1px solid #ddd;
  31. margin-bottom: 1.5rem;
  32. }
  33. .tab {
  34. padding: 0.5rem 1rem;
  35. cursor: pointer;
  36. border: 1px solid transparent;
  37. border-bottom: none;
  38. border-radius: 4px 4px 0 0;
  39. margin-right: 0.5rem;
  40. transition: all 0.3s ease;
  41. font-size: 0.9rem;
  42. }
  43. .tab.active {
  44. border-color: #ddd;
  45. border-bottom-color: #fff;
  46. background-color: #fff;
  47. color: #007bff;
  48. font-weight: bold;
  49. }
  50. .tab-content {
  51. display: none;
  52. }
  53. .tab-content.active {
  54. display: block;
  55. }
  56. .content-container {
  57. display: flex;
  58. flex-direction: row;
  59. gap: 2rem;
  60. width: 100%;
  61. }
  62. .input-section,
  63. .results-section {
  64. flex: 1;
  65. display: flex;
  66. flex-direction: column;
  67. justify-content: space-between;
  68. }
  69. h1 {
  70. font-size: 1.5rem;
  71. margin-bottom: 1rem;
  72. }
  73. .input-group {
  74. display: flex;
  75. flex-direction: column;
  76. gap: 1rem;
  77. height: 100%;
  78. justify-content: space-between;
  79. }
  80. input[type="number"] {
  81. padding: 0.5rem;
  82. font-size: 1rem;
  83. border: 1px solid #ccc;
  84. border-radius: 4px;
  85. transition: border-color 0.3s;
  86. }
  87. input[type="number"]:focus {
  88. outline: none;
  89. border-color: #007bff;
  90. }
  91. .results {
  92. display: flex;
  93. flex-direction: column;
  94. gap: 0.5rem;
  95. }
  96. .result-item {
  97. font-size: 1rem;
  98. color: #333;
  99. }
  100. .result-item:nth-child(4) {
  101. color: #0f72db;
  102. }
  103. .result-item:nth-child(5) {
  104. color: #11b9b9;
  105. }
  106. #stdDev {
  107. color: #0f72db;
  108. }
  109. #rsd {
  110. color: #11b9b9;
  111. }
  112. .stability-container,
  113. .error-container {
  114. display: flex;
  115. flex-direction: column;
  116. gap: 1rem;
  117. margin-bottom: 1.5rem;
  118. }
  119. .stability-row {
  120. display: flex;
  121. align-items: center;
  122. gap: 0.5rem;
  123. }
  124. .stability-row span {
  125. width: 60px;
  126. }
  127. .progress-container {
  128. display: inline-flex;
  129. position: relative;
  130. padding: 0.2rem 0.5rem;
  131. border-radius: 4px;
  132. background: linear-gradient(to right, #007bff var(--progress-percent), #e0e0e0 var(--progress-percent));
  133. background-size: 100% 100%;
  134. transition: background-position 0.3s ease;
  135. margin-left: 1em;
  136. gap: 0.8rem;
  137. }
  138. .progress-container.completed {
  139. background: linear-gradient(to right, #ff0000 100%, #e0e0e0 100%);
  140. }
  141. .stability-time-display {
  142. font-size: 0.9rem;
  143. color: #666;
  144. position: relative;
  145. z-index: 1;
  146. }
  147. .stability-time-display.warning {
  148. color: #ff0000;
  149. font-weight: bold;
  150. }
  151. .error-point-container {
  152. border: 1px solid #eee;
  153. border-radius: 4px;
  154. padding: 1rem;
  155. margin-bottom: 1rem;
  156. }
  157. .error-inputs {
  158. display: flex;
  159. flex-wrap: wrap;
  160. gap: 0.5rem;
  161. margin: 0.5rem 0;
  162. }
  163. .error-input {
  164. flex: 1;
  165. min-width: 80px;
  166. }
  167. .button-container {
  168. display: flex;
  169. justify-content: space-between;
  170. width: 100%;
  171. gap: 1rem;
  172. }
  173. .export-btn,
  174. .import-btn {
  175. flex: 1;
  176. padding: 1rem;
  177. border: none;
  178. border-radius: 4px;
  179. background-color: #007bff;
  180. color: white;
  181. font-size: 1rem;
  182. cursor: pointer;
  183. transition: background-color 0.3s;
  184. }
  185. .export-btn:hover,
  186. .import-btn:hover {
  187. background-color: #0056b3;
  188. }
  189. .error-result {
  190. margin-top: 0.5rem;
  191. font-weight: bold;
  192. }
  193. .error-container {
  194. display: flex;
  195. flex-direction: column;
  196. gap: 1rem;
  197. }
  198. .chart-container {
  199. margin-top: 1.5rem;
  200. position: relative;
  201. width: 100%;
  202. height: 200px;
  203. border: 1px solid #ddd;
  204. border-radius: 4px;
  205. padding-left: 1%;
  206. }
  207. canvas {
  208. width: 100%;
  209. height: 100%;
  210. }
  211. @media (max-width: 600px) {
  212. .container {
  213. flex-direction: column;
  214. }
  215. .content-container {
  216. flex-direction: column;
  217. }
  218. .input-section,
  219. .results-section {
  220. width: 100%;
  221. }
  222. h1 {
  223. font-size: 1.2rem;
  224. }
  225. input[type="number"] {
  226. font-size: 0.9rem;
  227. padding: 0.4rem;
  228. }
  229. .error-inputs {
  230. flex-direction: row;
  231. }
  232. .error-input {
  233. flex: 1;
  234. }
  235. }
  236. </style>
  237. </head>
  238. <body>
  239. <audio id="alertSound" src="alert.mp3" preload="auto"></audio>
  240. <div class="container">
  241. <div class="tabs">
  242. <div class="tab active" data-tab="repeatability">重复性</div>
  243. <div class="tab" data-tab="stability">稳定性</div>
  244. <div class="tab" data-tab="error">示值误差</div>
  245. <div class="tab" data-tab="data">数据</div>
  246. </div>
  247. <!-- 重复性标签页 -->
  248. <div id="repeatability" class="tab-content active">
  249. <div class="content-container">
  250. <div class="input-section">
  251. <h1>输入数据</h1>
  252. <div class="input-group">
  253. <input type="number" placeholder="1: " oninput="calculateRepeatability()">
  254. <input type="number" placeholder="2: " oninput="calculateRepeatability()">
  255. <input type="number" placeholder="3: " oninput="calculateRepeatability()">
  256. <input type="number" placeholder="4: " oninput="calculateRepeatability()">
  257. <input type="number" placeholder="5: " oninput="calculateRepeatability()">
  258. <input type="number" placeholder="6: " oninput="calculateRepeatability()">
  259. <input type="number" placeholder="7: (可选)"
  260. oninput="calculateRepeatability()">
  261. </div>
  262. </div>
  263. <div class="results-section">
  264. <h1>计算结果</h1>
  265. <div class="results">
  266. <div class="result-item">极差R: <span id="range">-</span></div>
  267. <div class="result-item">平均值x̄: <span id="mean">-</span></div>
  268. <div class="result-item">方差s<sup>2</sup>: <span id="variance">-</span></div>
  269. <div class="result-item">标准差s: <span id="stdDev">-</span></div>
  270. <div class="result-item">相对标准偏差RSD: <span id="rsd">-</span></div>
  271. </div>
  272. <div class="chart-container">
  273. <canvas id="lineChart"></canvas>
  274. </div>
  275. </div>
  276. </div>
  277. </div>
  278. <!-- 稳定性标签页 -->
  279. <div id="stability" class="tab-content">
  280. <div class="content-container">
  281. <div class="input-section">
  282. <h1>稳定性测试
  283. <div class="progress-container">
  284. <span id="current-time" class="stability-time-display"></span>
  285. <span class="stability-time-display"> · </span>
  286. <span id="countdown" class="stability-time-display">10:00</span>
  287. </div>
  288. </h1>
  289. <div class="input-group">
  290. <div class="stability-row">
  291. <span id="stability-time-text-1">0: </span>
  292. <input type="number" placeholder="输入值" id="stability-value-1"
  293. oninput="recordTimeAndCalculateStability(1)">
  294. </div>
  295. <div class="stability-row">
  296. <span id="stability-time-text-2">10: </span>
  297. <input type="number" placeholder="输入值" id="stability-value-2"
  298. oninput="recordTimeAndCalculateStability(2)">
  299. </div>
  300. <div class="stability-row">
  301. <span id="stability-time-text-3">20: </span>
  302. <input type="number" placeholder="输入值" id="stability-value-3"
  303. oninput="recordTimeAndCalculateStability(3)">
  304. </div>
  305. <div class="stability-row">
  306. <span id="stability-time-text-4">30: </span>
  307. <input type="number" placeholder="输入值" id="stability-value-4"
  308. oninput="recordTimeAndCalculateStability(4)">
  309. </div>
  310. <div class="stability-row">
  311. <span id="stability-time-text-5">40: </span>
  312. <input type="number" placeholder="输入值" id="stability-value-5"
  313. oninput="recordTimeAndCalculateStability(5)">
  314. </div>
  315. <div class="stability-row">
  316. <span id="stability-time-text-6">50: </span>
  317. <input type="number" placeholder="输入值" id="stability-value-6"
  318. oninput="recordTimeAndCalculateStability(6)">
  319. </div>
  320. <div class="stability-row">
  321. <span id="stability-time-text-7">60: </span>
  322. <input type="number" placeholder="输入值" id="stability-value-7"
  323. oninput="recordTimeAndCalculateStability(7)">
  324. </div>
  325. </div>
  326. </div>
  327. <div class="results-section">
  328. <h1>稳定性结果</h1>
  329. <div class="results">
  330. <div class="result-item">最大值: <span id="stability-max">-</span></div>
  331. <div class="result-item">最小值: <span id="stability-min">-</span></div>
  332. <div class="result-item">平均值: <span id="stability-mean">-</span></div>
  333. <div class="result-item">极值差: <span id="stability-range">-</span></div>
  334. </div>
  335. <div class="chart-container">
  336. <canvas id="stabilityChart"></canvas>
  337. </div>
  338. </div>
  339. </div>
  340. </div>
  341. <!-- 示值误差标签页 -->
  342. <div id="error" class="tab-content">
  343. <div class="content-container">
  344. <div class="input-section" style="width: 100%;">
  345. <h1>示值误差测试</h1>
  346. <div class="error-container">
  347. <div class="error-point-container">
  348. <h3>-400m℃</h3>
  349. <div class="error-inputs">
  350. <input type="number" placeholder="测试1" class="error-input"
  351. data-point="-400" data-index="0" oninput="calculateError()">
  352. <input type="number" placeholder="测试2" class="error-input"
  353. data-point="-400" data-index="1" oninput="calculateError()">
  354. <input type="number" placeholder="测试3" class="error-input"
  355. data-point="-400" data-index="2" oninput="calculateError()">
  356. </div>
  357. <div class="error-result">平均值: <span class="error-avg"
  358. data-point="-400">-</span></div>
  359. <div class="error-result">示值误差: <span class="error-value"
  360. data-point="-400">-</span></div>
  361. </div>
  362. <div class="error-point-container">
  363. <h3>-408m℃</h3>
  364. <div class="error-inputs">
  365. <input type="number" placeholder="测试1" class="error-input"
  366. data-point="-408" data-index="0" oninput="calculateError()">
  367. <input type="number" placeholder="测试2" class="error-input"
  368. data-point="-408" data-index="1" oninput="calculateError()">
  369. <input type="number" placeholder="测试3" class="error-input"
  370. data-point="-408" data-index="2" oninput="calculateError()">
  371. </div>
  372. <div class="error-result">平均值: <span class="error-avg"
  373. data-point="-408">-</span></div>
  374. <div class="error-result">示值误差: <span class="error-value"
  375. data-point="-408">-</span></div>
  376. </div>
  377. <div class="error-point-container">
  378. <h3>-450m℃</h3>
  379. <div class="error-inputs">
  380. <input type="number" placeholder="测试1" class="error-input"
  381. data-point="-450" data-index="0" oninput="calculateError()">
  382. <input type="number" placeholder="测试2" class="error-input"
  383. data-point="-450" data-index="1" oninput="calculateError()">
  384. <input type="number" placeholder="测试3" class="error-input"
  385. data-point="-450" data-index="2" oninput="calculateError()">
  386. </div>
  387. <div class="error-result">平均值: <span class="error-avg"
  388. data-point="-450">-</span></div>
  389. <div class="error-result">示值误差: <span class="error-value"
  390. data-point="-450">-</span></div>
  391. </div>
  392. <div class="error-point-container">
  393. <h3>-500m℃</h3>
  394. <div class="error-inputs">
  395. <input type="number" placeholder="测试1" class="error-input"
  396. data-point="-500" data-index="0" oninput="calculateError()">
  397. <input type="number" placeholder="测试2" class="error-input"
  398. data-point="-500" data-index="1" oninput="calculateError()">
  399. <input type="number" placeholder="测试3" class="error-input"
  400. data-point="-500" data-index="2" oninput="calculateError()">
  401. </div>
  402. <div class="error-result">平均值: <span class="error-avg"
  403. data-point="-500">-</span></div>
  404. <div class="error-result">示值误差: <span class="error-value"
  405. data-point="-500">-</span></div>
  406. </div>
  407. <div class="error-point-container">
  408. <h3>-512m℃</h3>
  409. <div class="error-inputs">
  410. <input type="number" placeholder="测试1" class="error-input"
  411. data-point="-512" data-index="0" oninput="calculateError()">
  412. <input type="number" placeholder="测试2" class="error-input"
  413. data-point="-512" data-index="1" oninput="calculateError()">
  414. <input type="number" placeholder="测试3" class="error-input"
  415. data-point="-512" data-index="2" oninput="calculateError()">
  416. </div>
  417. <div class="error-result">平均值: <span class="error-avg"
  418. data-point="-512">-</span></div>
  419. <div class="error-result">示值误差: <span class="error-value"
  420. data-point="-512">-</span></div>
  421. </div>
  422. <div class="error-point-container">
  423. <h3>-520m℃</h3>
  424. <div class="error-inputs">
  425. <input type="number" placeholder="测试1" class="error-input"
  426. data-point="-520" data-index="0" oninput="calculateError()">
  427. <input type="number" placeholder="测试2" class="error-input"
  428. data-point="-520" data-index="1" oninput="calculateError()">
  429. <input type="number" placeholder="测试3" class="error-input"
  430. data-point="-520" data-index="2" oninput="calculateError()">
  431. </div>
  432. <div class="error-result">平均值: <span class="error-avg"
  433. data-point="-520">-</span></div>
  434. <div class="error-result">示值误差: <span class="error-value"
  435. data-point="-520">-</span></div>
  436. </div>
  437. <div class="error-point-container">
  438. <h3>-557m℃</h3>
  439. <div class="error-inputs">
  440. <input type="number" placeholder="测试1" class="error-input"
  441. data-point="-557" data-index="0" oninput="calculateError()">
  442. <input type="number" placeholder="测试2" class="error-input"
  443. data-point="-557" data-index="1" oninput="calculateError()">
  444. <input type="number" placeholder="测试3" class="error-input"
  445. data-point="-557" data-index="2" oninput="calculateError()">
  446. </div>
  447. <div class="error-result">平均值: <span class="error-avg"
  448. data-point="-557">-</span></div>
  449. <div class="error-result">示值误差: <span class="error-value"
  450. data-point="-557">-</span></div>
  451. </div>
  452. <div class="error-point-container">
  453. <h3>-600m℃</h3>
  454. <div class="error-inputs">
  455. <input type="number" placeholder="测试1" class="error-input"
  456. data-point="-600" data-index="0" oninput="calculateError()">
  457. <input type="number" placeholder="测试2" class="error-input"
  458. data-point="-600" data-index="1" oninput="calculateError()">
  459. <input type="number" placeholder="测试3" class="error-input"
  460. data-point="-600" data-index="2" oninput="calculateError()">
  461. </div>
  462. <div class="error-result">平均值: <span class="error-avg"
  463. data-point="-600">-</span></div>
  464. <div class="error-result">示值误差: <span class="error-value"
  465. data-point="-600">-</span></div>
  466. </div>
  467. <div class="chart-container">
  468. <canvas id="errorChart"></canvas>
  469. </div>
  470. </div>
  471. </div>
  472. </div>
  473. </div>
  474. <!-- 导出/导入标签页 -->
  475. <div id="data" class="tab-content">
  476. <div class="content-container">
  477. <div class="button-container">
  478. <button class="export-btn" onclick="exportData()">导出数据</button>
  479. <button class="import-btn"
  480. onclick="document.getElementById('importFile').click()">导入数据</button>
  481. <input type="file" id="importFile" accept=".json,.txt" onchange="importData()"
  482. style="display:none">
  483. </div>
  484. </div>
  485. <div style="text-align: center; margin-top: 1rem; color: #666; font-size: 0.8rem;">Ver
  486. 1.0.01.250419a1</div>
  487. </div>
  488. <script src="./chart.js"></script>
  489. <script>
  490. const ctx = document.getElementById('lineChart').getContext('2d');
  491. const stabilityCtx = document.getElementById('stabilityChart').getContext('2d');
  492. const errorCtx = document.getElementById('errorChart').getContext('2d');
  493. let chartInstance = null;
  494. let stabilityChartInstance = null;
  495. let errorChartInstance = null;
  496. // 存储稳定性测试的时间记录
  497. let stabilityTimes = [];
  498. let lastStabilityTime = null;
  499. // 标签页切换功能
  500. document.querySelectorAll('.tab').forEach(tab => {
  501. tab.addEventListener('click', () => {
  502. // 移除所有标签和内容的active类
  503. document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
  504. document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
  505. // 添加当前标签和对应内容的active类
  506. tab.classList.add('active');
  507. const tabId = tab.getAttribute('data-tab');
  508. document.getElementById(tabId).classList.add('active');
  509. });
  510. });
  511. // 重复性计算函数 --------------------------------------------------
  512. function calculateRepeatability() {
  513. const inputs = Array.from(document.querySelectorAll('#repeatability input[type="number"]'))
  514. .map(input => parseFloat(input.value))
  515. .filter(value => !isNaN(value));
  516. if (inputs.length === 0) {
  517. document.getElementById('mean').textContent = '-';
  518. document.getElementById('variance').textContent = '-';
  519. document.getElementById('stdDev').textContent = '-';
  520. document.getElementById('range').textContent = '-';
  521. updateRepeatabilityChart([]);
  522. return;
  523. }
  524. // 计算最大值与最小值的差
  525. const range = Math.max(...inputs) - Math.min(...inputs);
  526. // 计算平均值
  527. const mean = inputs.reduce((sum, val) => sum + val, 0) / inputs.length;
  528. // 计算方差
  529. const variance = inputs.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / inputs.length;
  530. // 计算标准差
  531. const stdDev = Math.sqrt(variance);
  532. // 计算RSD
  533. const rsd = (stdDev / mean) * 100;
  534. // 更新结果显示
  535. document.getElementById('range').textContent = range.toFixed(2);
  536. document.getElementById('mean').textContent = mean.toFixed(2);
  537. document.getElementById('variance').textContent = variance.toFixed(2);
  538. document.getElementById('stdDev').textContent = stdDev.toFixed(2);
  539. document.getElementById('rsd').textContent = rsd.toFixed(2) + '%';
  540. // 更新折线图
  541. updateRepeatabilityChart(inputs);
  542. }
  543. function updateRepeatabilityChart(data) {
  544. if (chartInstance) {
  545. chartInstance.destroy(); // 销毁之前的图表实例
  546. }
  547. // 创建标签(1, 2, 3, ...)用于X轴
  548. const labels = Array.from({ length: data.length }, (_, i) => i + 1);
  549. chartInstance = new Chart(ctx, {
  550. type: 'line',
  551. data: {
  552. labels: labels,
  553. datasets: [{
  554. label: '重复性测试',
  555. data: data,
  556. borderColor: 'rgb(75, 192, 192)',
  557. backgroundColor: 'rgba(75, 192, 192, 0.2)',
  558. tension: 0.1,
  559. pointRadius: 5,
  560. pointHoverRadius: 7
  561. }]
  562. },
  563. options: {
  564. responsive: true,
  565. scales: {
  566. y: {
  567. beginAtZero: false
  568. }
  569. },
  570. plugins: {
  571. legend: {
  572. display: false
  573. }
  574. }
  575. }
  576. });
  577. }
  578. // 稳定性测试函数 --------------------------------------------------
  579. // 更新当前时间显示
  580. function updateCurrentTime() {
  581. const now = new Date();
  582. const timeString = now.toLocaleTimeString();
  583. const currentTimeElement = document.getElementById('current-time');
  584. currentTimeElement.textContent = timeString;
  585. // 检查是否需要显示警告(与上次稳定性测试时间间隔超过10分钟)
  586. if (lastStabilityTime) {
  587. const maxTime = 599; // 10分钟 = 600秒
  588. let timeDiff = now - lastStabilityTime;
  589. // 检查所有输入框是否都有值
  590. const allFilled = Array.from(document.querySelectorAll('#stability input[type="number"]'))
  591. .every(input => input.value);
  592. if (allFilled) {
  593. // 重置为默认状态
  594. document.getElementById('countdown').textContent = '10:00';
  595. document.querySelector('.progress-container').style.setProperty('--progress-percent', '0%');
  596. // 停止警报铃声
  597. document.getElementById('alertSound').pause();
  598. document.getElementById('alertSound').currentTime = 0;
  599. timeDiff = 0;
  600. }
  601. const remainingTime = Math.max(0, maxTime - Math.floor(timeDiff / 1000));
  602. const progressPercent = Math.min(100, Math.max(0, (maxTime - remainingTime) / maxTime * 100));
  603. document.querySelector('.progress-container').style.setProperty('--progress-percent', progressPercent + '%');
  604. const minutes = Math.floor(remainingTime / 60);
  605. const seconds = remainingTime % 60;
  606. const countdownElement = document.getElementById('countdown');
  607. countdownElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
  608. if (remainingTime <= 0) {
  609. currentTimeElement.classList.add('warning');
  610. countdownElement.classList.add('warning');
  611. } else {
  612. currentTimeElement.classList.remove('warning');
  613. countdownElement.classList.remove('warning');
  614. }
  615. if (remainingTime <= 3) {
  616. document.getElementById('alertSound').play();
  617. } else {
  618. document.getElementById('alertSound').pause();
  619. document.getElementById('alertSound').currentTime = 0;
  620. }
  621. }
  622. // 每秒更新一次时间
  623. setTimeout(updateCurrentTime, 1000);
  624. }
  625. // 初始化时启动时间更新
  626. updateCurrentTime();
  627. // 记录时间并计算稳定性
  628. function recordTimeAndCalculateStability(index) {
  629. const now = new Date();
  630. const timeString = now.toLocaleTimeString();
  631. // 更新时间显示
  632. document.getElementById(`stability-time-text-${index}`).textContent = `${timeString} `;
  633. // 记录最后一次测试时间
  634. lastStabilityTime = now;
  635. stabilityTimes[index - 1] = timeString;
  636. // 计算稳定性
  637. calculateStability();
  638. }
  639. function calculateStability() {
  640. const values = [];
  641. const times = [];
  642. // 收集所有稳定性测试的值和时间
  643. for (let i = 1; i <= 7; i++) {
  644. const valueInput = document.getElementById(`stability-value-${i}`);
  645. if (valueInput && valueInput.value) {
  646. values.push(parseFloat(valueInput.value));
  647. times.push(stabilityTimes[i - 1] || `测试${i}`);
  648. }
  649. }
  650. if (values.length === 0) {
  651. document.getElementById('stability-range').textContent = '-';
  652. document.getElementById('stability-max').textContent = '-';
  653. document.getElementById('stability-min').textContent = '-';
  654. document.getElementById('stability-mean').textContent = '-';
  655. return;
  656. }
  657. // 计算极值差
  658. const max = Math.max(...values);
  659. const min = Math.min(...values);
  660. const range = max - min;
  661. const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
  662. // 更新显示
  663. document.getElementById('stability-range').textContent = range.toFixed(2);
  664. document.getElementById('stability-max').textContent = max.toFixed(2);
  665. document.getElementById('stability-min').textContent = min.toFixed(2);
  666. document.getElementById('stability-mean').textContent = mean.toFixed(2);
  667. // 更新稳定性图表
  668. updateStabilityChart(times, values);
  669. }
  670. function updateStabilityChart(times, values) {
  671. if (!stabilityCtx || values.length === 0) return;
  672. // 如果已存在图表实例,先销毁
  673. if (stabilityChartInstance) {
  674. stabilityChartInstance.destroy();
  675. }
  676. // 创建新的图表
  677. stabilityChartInstance = new Chart(stabilityCtx, {
  678. type: 'line',
  679. data: {
  680. labels: times,
  681. datasets: [{
  682. label: '稳定性测试',
  683. data: values,
  684. borderColor: 'rgb(75, 192, 192)',
  685. backgroundColor: 'rgba(75, 192, 192, 0.2)',
  686. tension: 0.1,
  687. pointRadius: 5,
  688. pointHoverRadius: 7
  689. }]
  690. },
  691. options: {
  692. responsive: true,
  693. scales: {
  694. y: {
  695. beginAtZero: false
  696. }
  697. },
  698. plugins: {
  699. legend: {
  700. display: false
  701. }
  702. }
  703. }
  704. });
  705. }
  706. // 示值误差测试函数 ------------------------------------------------
  707. function calculateError() {
  708. const errorPoints = ['-400', '-408', '-450', '-500', '-512', '-520', '-557', '-600'];
  709. const errorData = [];
  710. errorPoints.forEach(point => {
  711. const inputs = document.querySelectorAll(`.error-input[data-point="${point}"]`);
  712. const values = [];
  713. inputs.forEach(input => {
  714. if (input.value) {
  715. values.push(parseFloat(input.value));
  716. }
  717. });
  718. if (values.length > 0) {
  719. // 计算平均值
  720. const avg = values.reduce((sum, val) => sum + val, 0) / values.length;
  721. document.querySelector(`.error-avg[data-point="${point}"]`).textContent = avg.toFixed(2);
  722. // 计算示值误差 (测量值与标准值的差的绝对值)
  723. const error = Math.abs(avg - (-1 * parseFloat(point)));
  724. document.querySelector(`.error-value[data-point="${point}"]`).textContent = error.toFixed(2);
  725. // 收集数据用于图表
  726. errorData.push({
  727. point: (-1 * parseFloat(point)),
  728. avg: avg,
  729. error: error
  730. });
  731. }
  732. });
  733. // 更新误差图表
  734. updateErrorChart(errorData);
  735. }
  736. function updateErrorChart(data) {
  737. if (errorChartInstance) {
  738. errorChartInstance.destroy();
  739. }
  740. if (data.length === 0) return;
  741. // 按照点位排序
  742. data.sort((a, b) => a.point - b.point);
  743. const labels = data.map(item => item.point);
  744. const errorValues = data.map(item => item.error);
  745. errorChartInstance = new Chart(errorCtx, {
  746. type: 'line',
  747. data: {
  748. labels: labels,
  749. datasets: [{
  750. label: '示值误差',
  751. data: errorValues,
  752. borderColor: 'rgb(75, 192, 192)',
  753. backgroundColor: 'rgba(75, 192, 192, 0.2)',
  754. tension: 0.1,
  755. pointRadius: 5,
  756. pointHoverRadius: 7
  757. }]
  758. },
  759. options: {
  760. responsive: true,
  761. scales: {
  762. y: {
  763. beginAtZero: false
  764. }
  765. },
  766. plugins: {
  767. legend: {
  768. display: false
  769. }
  770. }
  771. }
  772. });
  773. }
  774. </script>
  775. </body>
  776. </html>
  777. <script>
  778. function exportData() {
  779. const data = {
  780. repeatability: {
  781. inputs: Array.from(document.querySelectorAll('#repeatability .input-group input')).map(input => input.value),
  782. results: {
  783. range: document.getElementById('range').innerText,
  784. mean: document.getElementById('mean').innerText,
  785. variance: document.getElementById('variance').innerText,
  786. stdDev: document.getElementById('stdDev').innerText,
  787. rsd: document.getElementById('rsd').innerText
  788. }
  789. },
  790. stability: {
  791. inputs: Array.from(document.querySelectorAll('#stability .input-group input')).map(input => input.value),
  792. results: {
  793. max: document.getElementById('stability-max').innerText,
  794. min: document.getElementById('stability-min').innerText,
  795. mean: document.getElementById('stability-mean').innerText,
  796. range: document.getElementById('stability-range').innerText
  797. }
  798. },
  799. error: Array.from(document.querySelectorAll('.error-point-container')).map(container => ({
  800. point: container.querySelector('h3').innerText,
  801. inputs: Array.from(container.querySelectorAll('.error-input')).map(input => input.value),
  802. avg: container.querySelector('.error-avg').innerText,
  803. error: container.querySelector('.error-value').innerText
  804. }))
  805. };
  806. const now = new Date();
  807. const dateStr = now.toISOString().replace(/[-:]/g, '').replace('T', '_').substring(0, 15);
  808. const projectName = document.title.replace(' ', '_');
  809. const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
  810. const url = URL.createObjectURL(blob);
  811. const a = document.createElement('a');
  812. a.href = url;
  813. a.download = `${projectName}_data_${dateStr}.json`;
  814. a.click();
  815. URL.revokeObjectURL(url);
  816. }
  817. function importData() {
  818. const fileInput = document.getElementById('importFile');
  819. const file = fileInput.files[0];
  820. if (!file) return;
  821. const reader = new FileReader();
  822. reader.onload = function (e) {
  823. try {
  824. const data = JSON.parse(e.target.result);
  825. console.log(data);
  826. console.log(data.error);
  827. // 更新重复性数据
  828. if (data.repeatability) {
  829. const inputs = document.querySelectorAll('#repeatability input[type="number"]');
  830. data.repeatability.inputs.forEach((value, index) => {
  831. if (inputs[index]) inputs[index].value = value;
  832. });
  833. calculateRepeatability();
  834. }
  835. // 更新稳定性数据
  836. if (data.stability) {
  837. const inputs = document.querySelectorAll('#stability input[type="number"]');
  838. data.stability.inputs.forEach((value, index) => {
  839. if (inputs[index]) inputs[index].value = value;
  840. });
  841. calculateStability();
  842. }
  843. // 更新示值误差数据
  844. if (data.error) {
  845. data.error.forEach(item => {
  846. // 将point转换为字符串形式以确保匹配
  847. const point = item.point.toString().replace(/m℃$/, '');
  848. const inputs = document.querySelectorAll(`.error-input[data-point="${point}"]`);
  849. item.inputs.forEach((value, index) => {
  850. if (inputs[index]) inputs[index].value = value;
  851. });
  852. });
  853. calculateError();
  854. }
  855. } catch (error) {
  856. alert('导入失败!');
  857. console.error(error);
  858. }
  859. };
  860. reader.readAsText(file);
  861. }
  862. </script>
  863. </body>
  864. </html>