|
|
@@ -0,0 +1,986 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="zh">
|
|
|
+
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
+ <title>DIPM_resultStatistics</title>
|
|
|
+ <style>
|
|
|
+ body {
|
|
|
+ font-family: Arial, sans-serif;
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ min-height: 100vh;
|
|
|
+ background-color: #f4f4f9;
|
|
|
+ }
|
|
|
+
|
|
|
+ .container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ width: 90%;
|
|
|
+ max-width: 800px;
|
|
|
+ background: #fff;
|
|
|
+ padding: 1.5rem;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .tabs {
|
|
|
+ display: flex;
|
|
|
+ border-bottom: 1px solid #ddd;
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tab {
|
|
|
+ padding: 0.5rem 1rem;
|
|
|
+ cursor: pointer;
|
|
|
+ border: 1px solid transparent;
|
|
|
+ border-bottom: none;
|
|
|
+ border-radius: 4px 4px 0 0;
|
|
|
+ margin-right: 0.5rem;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ font-size: 0.9rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tab.active {
|
|
|
+ border-color: #ddd;
|
|
|
+ border-bottom-color: #fff;
|
|
|
+ background-color: #fff;
|
|
|
+ color: #007bff;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tab-content {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tab-content.active {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ gap: 2rem;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .input-section,
|
|
|
+ .results-section {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: space-between;
|
|
|
+ }
|
|
|
+
|
|
|
+ h1 {
|
|
|
+ font-size: 1.5rem;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .input-group {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 1rem;
|
|
|
+ height: 100%;
|
|
|
+ justify-content: space-between;
|
|
|
+ }
|
|
|
+
|
|
|
+ input[type="number"] {
|
|
|
+ padding: 0.5rem;
|
|
|
+ font-size: 1rem;
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ border-radius: 4px;
|
|
|
+ transition: border-color 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ input[type="number"]:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #007bff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .results {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 0.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .result-item {
|
|
|
+ font-size: 1rem;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .result-item:nth-child(4) {
|
|
|
+ color: #0f72db;
|
|
|
+ }
|
|
|
+
|
|
|
+ .result-item:nth-child(5) {
|
|
|
+ color: #11b9b9;
|
|
|
+ }
|
|
|
+
|
|
|
+ #stdDev {
|
|
|
+ color: #0f72db;
|
|
|
+ }
|
|
|
+
|
|
|
+ #rsd {
|
|
|
+ color: #11b9b9;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stability-container,
|
|
|
+ .error-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 1rem;
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stability-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 0.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stability-row span {
|
|
|
+ width: 60px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress-container {
|
|
|
+ display: inline-flex;
|
|
|
+ position: relative;
|
|
|
+ padding: 0.2rem 0.5rem;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: linear-gradient(to right, #007bff var(--progress-percent), #e0e0e0 var(--progress-percent));
|
|
|
+ background-size: 100% 100%;
|
|
|
+ transition: background-position 0.3s ease;
|
|
|
+ margin-left: 1em;
|
|
|
+ gap: 0.8rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress-container.completed {
|
|
|
+ background: linear-gradient(to right, #ff0000 100%, #e0e0e0 100%);
|
|
|
+ }
|
|
|
+
|
|
|
+ .stability-time-display {
|
|
|
+ font-size: 0.9rem;
|
|
|
+ color: #666;
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stability-time-display.warning {
|
|
|
+ color: #ff0000;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ .error-point-container {
|
|
|
+ border: 1px solid #eee;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 1rem;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .error-inputs {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 0.5rem;
|
|
|
+ margin: 0.5rem 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .error-input {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 80px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .button-container {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ width: 100%;
|
|
|
+ gap: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .export-btn,
|
|
|
+ .import-btn {
|
|
|
+ flex: 1;
|
|
|
+ padding: 1rem;
|
|
|
+ border: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ background-color: #007bff;
|
|
|
+ color: white;
|
|
|
+ font-size: 1rem;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: background-color 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .export-btn:hover,
|
|
|
+ .import-btn:hover {
|
|
|
+ background-color: #0056b3;
|
|
|
+ }
|
|
|
+
|
|
|
+ .error-result {
|
|
|
+ margin-top: 0.5rem;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ .error-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-container {
|
|
|
+ margin-top: 1.5rem;
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 200px;
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding-left: 1%;
|
|
|
+ }
|
|
|
+
|
|
|
+ canvas {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 600px) {
|
|
|
+ .container {
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-container {
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .input-section,
|
|
|
+ .results-section {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ h1 {
|
|
|
+ font-size: 1.2rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ input[type="number"] {
|
|
|
+ font-size: 0.9rem;
|
|
|
+ padding: 0.4rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .error-inputs {
|
|
|
+ flex-direction: row;
|
|
|
+ }
|
|
|
+
|
|
|
+ .error-input {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+
|
|
|
+<body>
|
|
|
+ <audio id="alertSound" src="alert.mp3" preload="auto"></audio>
|
|
|
+ <div class="container">
|
|
|
+ <div class="tabs">
|
|
|
+ <div class="tab active" data-tab="repeatability">重复性</div>
|
|
|
+ <div class="tab" data-tab="stability">稳定性</div>
|
|
|
+ <div class="tab" data-tab="error">示值误差</div>
|
|
|
+ <div class="tab" data-tab="data">数据</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 重复性标签页 -->
|
|
|
+ <div id="repeatability" class="tab-content active">
|
|
|
+ <div class="content-container">
|
|
|
+ <div class="input-section">
|
|
|
+ <h1>输入数据</h1>
|
|
|
+ <div class="input-group">
|
|
|
+ <input type="number" placeholder="1: " oninput="calculateRepeatability()">
|
|
|
+ <input type="number" placeholder="2: " oninput="calculateRepeatability()">
|
|
|
+ <input type="number" placeholder="3: " oninput="calculateRepeatability()">
|
|
|
+ <input type="number" placeholder="4: " oninput="calculateRepeatability()">
|
|
|
+ <input type="number" placeholder="5: " oninput="calculateRepeatability()">
|
|
|
+ <input type="number" placeholder="6: " oninput="calculateRepeatability()">
|
|
|
+ <input type="number" placeholder="7: (可选)"
|
|
|
+ oninput="calculateRepeatability()">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="results-section">
|
|
|
+ <h1>计算结果</h1>
|
|
|
+ <div class="results">
|
|
|
+ <div class="result-item">极差R: <span id="range">-</span></div>
|
|
|
+ <div class="result-item">平均值x̄: <span id="mean">-</span></div>
|
|
|
+ <div class="result-item">方差s<sup>2</sup>: <span id="variance">-</span></div>
|
|
|
+ <div class="result-item">标准差s: <span id="stdDev">-</span></div>
|
|
|
+ <div class="result-item">相对标准偏差RSD: <span id="rsd">-</span></div>
|
|
|
+ </div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <canvas id="lineChart"></canvas>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 稳定性标签页 -->
|
|
|
+ <div id="stability" class="tab-content">
|
|
|
+ <div class="content-container">
|
|
|
+ <div class="input-section">
|
|
|
+ <h1>稳定性测试
|
|
|
+ <div class="progress-container">
|
|
|
+ <span id="current-time" class="stability-time-display"></span>
|
|
|
+ <span class="stability-time-display"> · </span>
|
|
|
+ <span id="countdown" class="stability-time-display">10:00</span>
|
|
|
+ </div>
|
|
|
+ </h1>
|
|
|
+ <div class="input-group">
|
|
|
+ <div class="stability-row">
|
|
|
+ <span id="stability-time-text-1">0: </span>
|
|
|
+ <input type="number" placeholder="输入值" id="stability-value-1"
|
|
|
+ oninput="recordTimeAndCalculateStability(1)">
|
|
|
+ </div>
|
|
|
+ <div class="stability-row">
|
|
|
+ <span id="stability-time-text-2">10: </span>
|
|
|
+ <input type="number" placeholder="输入值" id="stability-value-2"
|
|
|
+ oninput="recordTimeAndCalculateStability(2)">
|
|
|
+ </div>
|
|
|
+ <div class="stability-row">
|
|
|
+ <span id="stability-time-text-3">20: </span>
|
|
|
+ <input type="number" placeholder="输入值" id="stability-value-3"
|
|
|
+ oninput="recordTimeAndCalculateStability(3)">
|
|
|
+ </div>
|
|
|
+ <div class="stability-row">
|
|
|
+ <span id="stability-time-text-4">30: </span>
|
|
|
+ <input type="number" placeholder="输入值" id="stability-value-4"
|
|
|
+ oninput="recordTimeAndCalculateStability(4)">
|
|
|
+ </div>
|
|
|
+ <div class="stability-row">
|
|
|
+ <span id="stability-time-text-5">40: </span>
|
|
|
+ <input type="number" placeholder="输入值" id="stability-value-5"
|
|
|
+ oninput="recordTimeAndCalculateStability(5)">
|
|
|
+ </div>
|
|
|
+ <div class="stability-row">
|
|
|
+ <span id="stability-time-text-6">50: </span>
|
|
|
+ <input type="number" placeholder="输入值" id="stability-value-6"
|
|
|
+ oninput="recordTimeAndCalculateStability(6)">
|
|
|
+ </div>
|
|
|
+ <div class="stability-row">
|
|
|
+ <span id="stability-time-text-7">60: </span>
|
|
|
+ <input type="number" placeholder="输入值" id="stability-value-7"
|
|
|
+ oninput="recordTimeAndCalculateStability(7)">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="results-section">
|
|
|
+ <h1>稳定性结果</h1>
|
|
|
+ <div class="results">
|
|
|
+ <div class="result-item">最大值: <span id="stability-max">-</span></div>
|
|
|
+ <div class="result-item">最小值: <span id="stability-min">-</span></div>
|
|
|
+ <div class="result-item">平均值: <span id="stability-mean">-</span></div>
|
|
|
+ <div class="result-item">极值差: <span id="stability-range">-</span></div>
|
|
|
+ </div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <canvas id="stabilityChart"></canvas>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+ <!-- 示值误差标签页 -->
|
|
|
+ <div id="error" class="tab-content">
|
|
|
+ <div class="content-container">
|
|
|
+ <div class="input-section" style="width: 100%;">
|
|
|
+ <h1>示值误差测试</h1>
|
|
|
+ <div class="error-container">
|
|
|
+ <div class="error-point-container">
|
|
|
+ <h3>-400m℃</h3>
|
|
|
+ <div class="error-inputs">
|
|
|
+ <input type="number" placeholder="测试1" class="error-input"
|
|
|
+ data-point="-400" data-index="0" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试2" class="error-input"
|
|
|
+ data-point="-400" data-index="1" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试3" class="error-input"
|
|
|
+ data-point="-400" data-index="2" oninput="calculateError()">
|
|
|
+ </div>
|
|
|
+ <div class="error-result">平均值: <span class="error-avg"
|
|
|
+ data-point="-400">-</span></div>
|
|
|
+ <div class="error-result">示值误差: <span class="error-value"
|
|
|
+ data-point="-400">-</span></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="error-point-container">
|
|
|
+ <h3>-408m℃</h3>
|
|
|
+ <div class="error-inputs">
|
|
|
+ <input type="number" placeholder="测试1" class="error-input"
|
|
|
+ data-point="-408" data-index="0" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试2" class="error-input"
|
|
|
+ data-point="-408" data-index="1" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试3" class="error-input"
|
|
|
+ data-point="-408" data-index="2" oninput="calculateError()">
|
|
|
+ </div>
|
|
|
+ <div class="error-result">平均值: <span class="error-avg"
|
|
|
+ data-point="-408">-</span></div>
|
|
|
+ <div class="error-result">示值误差: <span class="error-value"
|
|
|
+ data-point="-408">-</span></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="error-point-container">
|
|
|
+ <h3>-450m℃</h3>
|
|
|
+ <div class="error-inputs">
|
|
|
+ <input type="number" placeholder="测试1" class="error-input"
|
|
|
+ data-point="-450" data-index="0" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试2" class="error-input"
|
|
|
+ data-point="-450" data-index="1" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试3" class="error-input"
|
|
|
+ data-point="-450" data-index="2" oninput="calculateError()">
|
|
|
+ </div>
|
|
|
+ <div class="error-result">平均值: <span class="error-avg"
|
|
|
+ data-point="-450">-</span></div>
|
|
|
+ <div class="error-result">示值误差: <span class="error-value"
|
|
|
+ data-point="-450">-</span></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="error-point-container">
|
|
|
+ <h3>-500m℃</h3>
|
|
|
+ <div class="error-inputs">
|
|
|
+ <input type="number" placeholder="测试1" class="error-input"
|
|
|
+ data-point="-500" data-index="0" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试2" class="error-input"
|
|
|
+ data-point="-500" data-index="1" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试3" class="error-input"
|
|
|
+ data-point="-500" data-index="2" oninput="calculateError()">
|
|
|
+ </div>
|
|
|
+ <div class="error-result">平均值: <span class="error-avg"
|
|
|
+ data-point="-500">-</span></div>
|
|
|
+ <div class="error-result">示值误差: <span class="error-value"
|
|
|
+ data-point="-500">-</span></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="error-point-container">
|
|
|
+ <h3>-512m℃</h3>
|
|
|
+ <div class="error-inputs">
|
|
|
+ <input type="number" placeholder="测试1" class="error-input"
|
|
|
+ data-point="-512" data-index="0" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试2" class="error-input"
|
|
|
+ data-point="-512" data-index="1" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试3" class="error-input"
|
|
|
+ data-point="-512" data-index="2" oninput="calculateError()">
|
|
|
+ </div>
|
|
|
+ <div class="error-result">平均值: <span class="error-avg"
|
|
|
+ data-point="-512">-</span></div>
|
|
|
+ <div class="error-result">示值误差: <span class="error-value"
|
|
|
+ data-point="-512">-</span></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="error-point-container">
|
|
|
+ <h3>-520m℃</h3>
|
|
|
+ <div class="error-inputs">
|
|
|
+ <input type="number" placeholder="测试1" class="error-input"
|
|
|
+ data-point="-520" data-index="0" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试2" class="error-input"
|
|
|
+ data-point="-520" data-index="1" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试3" class="error-input"
|
|
|
+ data-point="-520" data-index="2" oninput="calculateError()">
|
|
|
+ </div>
|
|
|
+ <div class="error-result">平均值: <span class="error-avg"
|
|
|
+ data-point="-520">-</span></div>
|
|
|
+ <div class="error-result">示值误差: <span class="error-value"
|
|
|
+ data-point="-520">-</span></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="error-point-container">
|
|
|
+ <h3>-557m℃</h3>
|
|
|
+ <div class="error-inputs">
|
|
|
+ <input type="number" placeholder="测试1" class="error-input"
|
|
|
+ data-point="-557" data-index="0" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试2" class="error-input"
|
|
|
+ data-point="-557" data-index="1" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试3" class="error-input"
|
|
|
+ data-point="-557" data-index="2" oninput="calculateError()">
|
|
|
+ </div>
|
|
|
+ <div class="error-result">平均值: <span class="error-avg"
|
|
|
+ data-point="-557">-</span></div>
|
|
|
+ <div class="error-result">示值误差: <span class="error-value"
|
|
|
+ data-point="-557">-</span></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="error-point-container">
|
|
|
+ <h3>-600m℃</h3>
|
|
|
+ <div class="error-inputs">
|
|
|
+ <input type="number" placeholder="测试1" class="error-input"
|
|
|
+ data-point="-600" data-index="0" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试2" class="error-input"
|
|
|
+ data-point="-600" data-index="1" oninput="calculateError()">
|
|
|
+ <input type="number" placeholder="测试3" class="error-input"
|
|
|
+ data-point="-600" data-index="2" oninput="calculateError()">
|
|
|
+ </div>
|
|
|
+ <div class="error-result">平均值: <span class="error-avg"
|
|
|
+ data-point="-600">-</span></div>
|
|
|
+ <div class="error-result">示值误差: <span class="error-value"
|
|
|
+ data-point="-600">-</span></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="chart-container">
|
|
|
+ <canvas id="errorChart"></canvas>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 导出/导入标签页 -->
|
|
|
+ <div id="data" class="tab-content">
|
|
|
+ <div class="content-container">
|
|
|
+ <div class="button-container">
|
|
|
+ <button class="export-btn" onclick="exportData()">导出数据</button>
|
|
|
+ <button class="import-btn"
|
|
|
+ onclick="document.getElementById('importFile').click()">导入数据</button>
|
|
|
+ <input type="file" id="importFile" accept=".json,.txt" onchange="importData()"
|
|
|
+ style="display:none">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div style="text-align: center; margin-top: 1rem; color: #666; font-size: 0.8rem;">Ver
|
|
|
+ 1.0.01.250419a1</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <script src="./chart.js"></script>
|
|
|
+ <script>
|
|
|
+ const ctx = document.getElementById('lineChart').getContext('2d');
|
|
|
+ const stabilityCtx = document.getElementById('stabilityChart').getContext('2d');
|
|
|
+ const errorCtx = document.getElementById('errorChart').getContext('2d');
|
|
|
+ let chartInstance = null;
|
|
|
+ let stabilityChartInstance = null;
|
|
|
+ let errorChartInstance = null;
|
|
|
+
|
|
|
+ // 存储稳定性测试的时间记录
|
|
|
+ let stabilityTimes = [];
|
|
|
+ let lastStabilityTime = null;
|
|
|
+
|
|
|
+ // 标签页切换功能
|
|
|
+ document.querySelectorAll('.tab').forEach(tab => {
|
|
|
+ tab.addEventListener('click', () => {
|
|
|
+ // 移除所有标签和内容的active类
|
|
|
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
|
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
|
|
+
|
|
|
+ // 添加当前标签和对应内容的active类
|
|
|
+ tab.classList.add('active');
|
|
|
+ const tabId = tab.getAttribute('data-tab');
|
|
|
+ document.getElementById(tabId).classList.add('active');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 重复性计算函数 --------------------------------------------------
|
|
|
+ function calculateRepeatability() {
|
|
|
+ const inputs = Array.from(document.querySelectorAll('#repeatability input[type="number"]'))
|
|
|
+ .map(input => parseFloat(input.value))
|
|
|
+ .filter(value => !isNaN(value));
|
|
|
+
|
|
|
+ if (inputs.length === 0) {
|
|
|
+ document.getElementById('mean').textContent = '-';
|
|
|
+ document.getElementById('variance').textContent = '-';
|
|
|
+ document.getElementById('stdDev').textContent = '-';
|
|
|
+ document.getElementById('range').textContent = '-';
|
|
|
+ updateRepeatabilityChart([]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算最大值与最小值的差
|
|
|
+ const range = Math.max(...inputs) - Math.min(...inputs);
|
|
|
+
|
|
|
+ // 计算平均值
|
|
|
+ const mean = inputs.reduce((sum, val) => sum + val, 0) / inputs.length;
|
|
|
+
|
|
|
+ // 计算方差
|
|
|
+ const variance = inputs.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / inputs.length;
|
|
|
+
|
|
|
+ // 计算标准差
|
|
|
+ const stdDev = Math.sqrt(variance);
|
|
|
+
|
|
|
+ // 计算RSD
|
|
|
+ const rsd = (stdDev / mean) * 100;
|
|
|
+
|
|
|
+ // 更新结果显示
|
|
|
+ document.getElementById('range').textContent = range.toFixed(2);
|
|
|
+ document.getElementById('mean').textContent = mean.toFixed(2);
|
|
|
+ document.getElementById('variance').textContent = variance.toFixed(2);
|
|
|
+ document.getElementById('stdDev').textContent = stdDev.toFixed(2);
|
|
|
+ document.getElementById('rsd').textContent = rsd.toFixed(2) + '%';
|
|
|
+
|
|
|
+ // 更新折线图
|
|
|
+ updateRepeatabilityChart(inputs);
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateRepeatabilityChart(data) {
|
|
|
+ if (chartInstance) {
|
|
|
+ chartInstance.destroy(); // 销毁之前的图表实例
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建标签(1, 2, 3, ...)用于X轴
|
|
|
+ const labels = Array.from({ length: data.length }, (_, i) => i + 1);
|
|
|
+
|
|
|
+ chartInstance = new Chart(ctx, {
|
|
|
+ type: 'line',
|
|
|
+ data: {
|
|
|
+ labels: labels,
|
|
|
+ datasets: [{
|
|
|
+ label: '重复性测试',
|
|
|
+ data: data,
|
|
|
+ borderColor: 'rgb(75, 192, 192)',
|
|
|
+ backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
|
+ tension: 0.1,
|
|
|
+ pointRadius: 5,
|
|
|
+ pointHoverRadius: 7
|
|
|
+ }]
|
|
|
+ },
|
|
|
+ options: {
|
|
|
+ responsive: true,
|
|
|
+ scales: {
|
|
|
+ y: {
|
|
|
+ beginAtZero: false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ plugins: {
|
|
|
+ legend: {
|
|
|
+ display: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 稳定性测试函数 --------------------------------------------------
|
|
|
+ // 更新当前时间显示
|
|
|
+ function updateCurrentTime() {
|
|
|
+ const now = new Date();
|
|
|
+ const timeString = now.toLocaleTimeString();
|
|
|
+ const currentTimeElement = document.getElementById('current-time');
|
|
|
+ currentTimeElement.textContent = timeString;
|
|
|
+
|
|
|
+ // 检查是否需要显示警告(与上次稳定性测试时间间隔超过10分钟)
|
|
|
+ if (lastStabilityTime) {
|
|
|
+ const maxTime = 599; // 10分钟 = 600秒
|
|
|
+ let timeDiff = now - lastStabilityTime;
|
|
|
+
|
|
|
+ // 检查所有输入框是否都有值
|
|
|
+ const allFilled = Array.from(document.querySelectorAll('#stability input[type="number"]'))
|
|
|
+ .every(input => input.value);
|
|
|
+
|
|
|
+ if (allFilled) {
|
|
|
+ // 重置为默认状态
|
|
|
+ document.getElementById('countdown').textContent = '10:00';
|
|
|
+ document.querySelector('.progress-container').style.setProperty('--progress-percent', '0%');
|
|
|
+
|
|
|
+ // 停止警报铃声
|
|
|
+ document.getElementById('alertSound').pause();
|
|
|
+ document.getElementById('alertSound').currentTime = 0;
|
|
|
+ timeDiff = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ const remainingTime = Math.max(0, maxTime - Math.floor(timeDiff / 1000));
|
|
|
+ const progressPercent = Math.min(100, Math.max(0, (maxTime - remainingTime) / maxTime * 100));
|
|
|
+ document.querySelector('.progress-container').style.setProperty('--progress-percent', progressPercent + '%');
|
|
|
+ const minutes = Math.floor(remainingTime / 60);
|
|
|
+ const seconds = remainingTime % 60;
|
|
|
+ const countdownElement = document.getElementById('countdown');
|
|
|
+ countdownElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
|
|
+
|
|
|
+ if (remainingTime <= 0) {
|
|
|
+ currentTimeElement.classList.add('warning');
|
|
|
+ countdownElement.classList.add('warning');
|
|
|
+ } else {
|
|
|
+ currentTimeElement.classList.remove('warning');
|
|
|
+ countdownElement.classList.remove('warning');
|
|
|
+ }
|
|
|
+ if (remainingTime <= 3) {
|
|
|
+ document.getElementById('alertSound').play();
|
|
|
+ } else {
|
|
|
+ document.getElementById('alertSound').pause();
|
|
|
+ document.getElementById('alertSound').currentTime = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 每秒更新一次时间
|
|
|
+ setTimeout(updateCurrentTime, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化时启动时间更新
|
|
|
+ updateCurrentTime();
|
|
|
+
|
|
|
+ // 记录时间并计算稳定性
|
|
|
+ function recordTimeAndCalculateStability(index) {
|
|
|
+ const now = new Date();
|
|
|
+ const timeString = now.toLocaleTimeString();
|
|
|
+
|
|
|
+ // 更新时间显示
|
|
|
+ document.getElementById(`stability-time-text-${index}`).textContent = `${timeString} `;
|
|
|
+
|
|
|
+ // 记录最后一次测试时间
|
|
|
+ lastStabilityTime = now;
|
|
|
+ stabilityTimes[index - 1] = timeString;
|
|
|
+
|
|
|
+ // 计算稳定性
|
|
|
+ calculateStability();
|
|
|
+ }
|
|
|
+
|
|
|
+ function calculateStability() {
|
|
|
+ const values = [];
|
|
|
+ const times = [];
|
|
|
+
|
|
|
+ // 收集所有稳定性测试的值和时间
|
|
|
+ for (let i = 1; i <= 7; i++) {
|
|
|
+ const valueInput = document.getElementById(`stability-value-${i}`);
|
|
|
+ if (valueInput && valueInput.value) {
|
|
|
+ values.push(parseFloat(valueInput.value));
|
|
|
+ times.push(stabilityTimes[i - 1] || `测试${i}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (values.length === 0) {
|
|
|
+ document.getElementById('stability-range').textContent = '-';
|
|
|
+ document.getElementById('stability-max').textContent = '-';
|
|
|
+ document.getElementById('stability-min').textContent = '-';
|
|
|
+ document.getElementById('stability-mean').textContent = '-';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算极值差
|
|
|
+ const max = Math.max(...values);
|
|
|
+ const min = Math.min(...values);
|
|
|
+ const range = max - min;
|
|
|
+ const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
|
|
|
+
|
|
|
+ // 更新显示
|
|
|
+ document.getElementById('stability-range').textContent = range.toFixed(2);
|
|
|
+ document.getElementById('stability-max').textContent = max.toFixed(2);
|
|
|
+ document.getElementById('stability-min').textContent = min.toFixed(2);
|
|
|
+ document.getElementById('stability-mean').textContent = mean.toFixed(2);
|
|
|
+
|
|
|
+ // 更新稳定性图表
|
|
|
+ updateStabilityChart(times, values);
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateStabilityChart(times, values) {
|
|
|
+ if (!stabilityCtx || values.length === 0) return;
|
|
|
+
|
|
|
+ // 如果已存在图表实例,先销毁
|
|
|
+ if (stabilityChartInstance) {
|
|
|
+ stabilityChartInstance.destroy();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建新的图表
|
|
|
+ stabilityChartInstance = new Chart(stabilityCtx, {
|
|
|
+ type: 'line',
|
|
|
+ data: {
|
|
|
+ labels: times,
|
|
|
+ datasets: [{
|
|
|
+ label: '稳定性测试',
|
|
|
+ data: values,
|
|
|
+ borderColor: 'rgb(75, 192, 192)',
|
|
|
+ backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
|
+ tension: 0.1,
|
|
|
+ pointRadius: 5,
|
|
|
+ pointHoverRadius: 7
|
|
|
+ }]
|
|
|
+ },
|
|
|
+ options: {
|
|
|
+ responsive: true,
|
|
|
+ scales: {
|
|
|
+ y: {
|
|
|
+ beginAtZero: false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ plugins: {
|
|
|
+ legend: {
|
|
|
+ display: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 示值误差测试函数 ------------------------------------------------
|
|
|
+ function calculateError() {
|
|
|
+ const errorPoints = ['-400', '-408', '-450', '-500', '-512', '-520', '-557', '-600'];
|
|
|
+ const errorData = [];
|
|
|
+
|
|
|
+ errorPoints.forEach(point => {
|
|
|
+ const inputs = document.querySelectorAll(`.error-input[data-point="${point}"]`);
|
|
|
+ const values = [];
|
|
|
+
|
|
|
+ inputs.forEach(input => {
|
|
|
+ if (input.value) {
|
|
|
+ values.push(parseFloat(input.value));
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (values.length > 0) {
|
|
|
+ // 计算平均值
|
|
|
+ const avg = values.reduce((sum, val) => sum + val, 0) / values.length;
|
|
|
+ document.querySelector(`.error-avg[data-point="${point}"]`).textContent = avg.toFixed(2);
|
|
|
+
|
|
|
+ // 计算示值误差 (测量值与标准值的差的绝对值)
|
|
|
+ const error = Math.abs(avg - (-1 * parseFloat(point)));
|
|
|
+ document.querySelector(`.error-value[data-point="${point}"]`).textContent = error.toFixed(2);
|
|
|
+
|
|
|
+ // 收集数据用于图表
|
|
|
+ errorData.push({
|
|
|
+ point: (-1 * parseFloat(point)),
|
|
|
+ avg: avg,
|
|
|
+ error: error
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新误差图表
|
|
|
+ updateErrorChart(errorData);
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateErrorChart(data) {
|
|
|
+ if (errorChartInstance) {
|
|
|
+ errorChartInstance.destroy();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (data.length === 0) return;
|
|
|
+
|
|
|
+ // 按照点位排序
|
|
|
+ data.sort((a, b) => a.point - b.point);
|
|
|
+
|
|
|
+ const labels = data.map(item => item.point);
|
|
|
+ const errorValues = data.map(item => item.error);
|
|
|
+
|
|
|
+ errorChartInstance = new Chart(errorCtx, {
|
|
|
+ type: 'line',
|
|
|
+ data: {
|
|
|
+ labels: labels,
|
|
|
+ datasets: [{
|
|
|
+ label: '示值误差',
|
|
|
+ data: errorValues,
|
|
|
+ borderColor: 'rgb(75, 192, 192)',
|
|
|
+ backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
|
+ tension: 0.1,
|
|
|
+ pointRadius: 5,
|
|
|
+ pointHoverRadius: 7
|
|
|
+ }]
|
|
|
+ },
|
|
|
+ options: {
|
|
|
+ responsive: true,
|
|
|
+ scales: {
|
|
|
+ y: {
|
|
|
+ beginAtZero: false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ plugins: {
|
|
|
+ legend: {
|
|
|
+ display: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ </script>
|
|
|
+</body>
|
|
|
+
|
|
|
+</html>
|
|
|
+
|
|
|
+<script>
|
|
|
+ function exportData() {
|
|
|
+ const data = {
|
|
|
+ repeatability: {
|
|
|
+ inputs: Array.from(document.querySelectorAll('#repeatability .input-group input')).map(input => input.value),
|
|
|
+ results: {
|
|
|
+ range: document.getElementById('range').innerText,
|
|
|
+ mean: document.getElementById('mean').innerText,
|
|
|
+ variance: document.getElementById('variance').innerText,
|
|
|
+ stdDev: document.getElementById('stdDev').innerText,
|
|
|
+ rsd: document.getElementById('rsd').innerText
|
|
|
+ }
|
|
|
+ },
|
|
|
+ stability: {
|
|
|
+ inputs: Array.from(document.querySelectorAll('#stability .input-group input')).map(input => input.value),
|
|
|
+ results: {
|
|
|
+ max: document.getElementById('stability-max').innerText,
|
|
|
+ min: document.getElementById('stability-min').innerText,
|
|
|
+ mean: document.getElementById('stability-mean').innerText,
|
|
|
+ range: document.getElementById('stability-range').innerText
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error: Array.from(document.querySelectorAll('.error-point-container')).map(container => ({
|
|
|
+ point: container.querySelector('h3').innerText,
|
|
|
+ inputs: Array.from(container.querySelectorAll('.error-input')).map(input => input.value),
|
|
|
+ avg: container.querySelector('.error-avg').innerText,
|
|
|
+ error: container.querySelector('.error-value').innerText
|
|
|
+ }))
|
|
|
+ };
|
|
|
+
|
|
|
+ const now = new Date();
|
|
|
+ const dateStr = now.toISOString().replace(/[-:]/g, '').replace('T', '_').substring(0, 15);
|
|
|
+ const projectName = document.title.replace(' ', '_');
|
|
|
+
|
|
|
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
|
+ const url = URL.createObjectURL(blob);
|
|
|
+ const a = document.createElement('a');
|
|
|
+ a.href = url;
|
|
|
+ a.download = `${projectName}_data_${dateStr}.json`;
|
|
|
+ a.click();
|
|
|
+ URL.revokeObjectURL(url);
|
|
|
+ }
|
|
|
+
|
|
|
+ function importData() {
|
|
|
+ const fileInput = document.getElementById('importFile');
|
|
|
+ const file = fileInput.files[0];
|
|
|
+ if (!file) return;
|
|
|
+
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = function (e) {
|
|
|
+ try {
|
|
|
+ const data = JSON.parse(e.target.result);
|
|
|
+ console.log(data);
|
|
|
+ console.log(data.error);
|
|
|
+
|
|
|
+
|
|
|
+ // 更新重复性数据
|
|
|
+ if (data.repeatability) {
|
|
|
+ const inputs = document.querySelectorAll('#repeatability input[type="number"]');
|
|
|
+ data.repeatability.inputs.forEach((value, index) => {
|
|
|
+ if (inputs[index]) inputs[index].value = value;
|
|
|
+ });
|
|
|
+ calculateRepeatability();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新稳定性数据
|
|
|
+ if (data.stability) {
|
|
|
+ const inputs = document.querySelectorAll('#stability input[type="number"]');
|
|
|
+
|
|
|
+ data.stability.inputs.forEach((value, index) => {
|
|
|
+ if (inputs[index]) inputs[index].value = value;
|
|
|
+ });
|
|
|
+ calculateStability();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新示值误差数据
|
|
|
+ if (data.error) {
|
|
|
+ data.error.forEach(item => {
|
|
|
+ // 将point转换为字符串形式以确保匹配
|
|
|
+ const point = item.point.toString().replace(/m℃$/, '');
|
|
|
+ const inputs = document.querySelectorAll(`.error-input[data-point="${point}"]`);
|
|
|
+ item.inputs.forEach((value, index) => {
|
|
|
+ if (inputs[index]) inputs[index].value = value;
|
|
|
+ });
|
|
|
+ });
|
|
|
+ calculateError();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ alert('导入失败!');
|
|
|
+ console.error(error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ reader.readAsText(file);
|
|
|
+ }
|
|
|
+</script>
|
|
|
+</body>
|
|
|
+
|
|
|
+</html>
|