Skip to content

Commit

Permalink
Add Charts to SpeedTest
Browse files Browse the repository at this point in the history
  • Loading branch information
jason5ng32 committed Feb 2, 2025
1 parent 13d237d commit 76ae6d7
Show file tree
Hide file tree
Showing 6 changed files with 534 additions and 46 deletions.
242 changes: 196 additions & 46 deletions frontend/components/SpeedTest.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,35 @@
</p>
</div>
</div>

<div id="result"></div>

<!-- 在结果区域上方添加图表 -->
<div class="speed-charts-container mb-4 jn-slide-in" v-show="state.speedTest.status !== 'idle'">
<div class="row">
<div class="col-md-6 col-lg-3">
<div class="chart-wrapper">
<canvas ref="downloadChart"></canvas>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="chart-wrapper">
<canvas ref="uploadChart"></canvas>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="chart-wrapper">
<canvas ref="latencyChart"></canvas>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="chart-wrapper">
<canvas ref="jitterChart"></canvas>
</div>
</div>
</div>
</div>

<div class="row alert alert-success m-1 p-2 " :data-bs-theme="isDarkMode ? 'dark' : ''"
v-if="state.speedTest.status === 'finished' && state.speedTest.hasScores">
<p id="score" class="speedtest-p"><i class="bi bi-calendar2-check"></i>&nbsp;
Expand Down Expand Up @@ -133,7 +162,6 @@
{{ t('speedtest.resultNote') }}
</p>
</div>
<div id="result"></div>
</div>
</div>
</div>
Expand All @@ -142,14 +170,15 @@
</template>

<script setup>
import { reactive, computed, onMounted, markRaw } from 'vue';
import { reactive, computed, onMounted, markRaw, ref, onUnmounted } from 'vue';
import { useMainStore } from '@/store';
import { useI18n } from 'vue-i18n';
import { trackEvent } from '@/utils/use-analytics';
import { isValidIP } from '@/utils/valid-ip.js';
import getCountryName from '@/utils/country-name.js';
import getColoCountry from '@/utils/speedtest-colos.js';
import SpeedTestEngine from '@cloudflare/speedtest';
import useSpeedTestCharts from '@/utils/use-speedtest-charts.js';
const { t } = useI18n();
const store = useMainStore();
Expand Down Expand Up @@ -187,11 +216,22 @@ const state = reactive({
package: {
download: { bytes: 50e6, count: 4 },
upload: { bytes: 15e6, count: 4 },
latency: { count: 20 }
latency: { count: 30 }
}
}
});
const {
downloadChart,
uploadChart,
latencyChart,
jitterChart,
updateCharts,
initStartingPoints,
destroyCharts,
resetChartData
} = useSpeedTestCharts(t);
// 连接数据处理
const connectionMethods = {
async getIPFromSpeedTest() {
Expand Down Expand Up @@ -286,32 +326,80 @@ const engineMethods = {
},
updateSpeedInRealTime() {
const rawData = testEngine.results.raw;
if (rawData.download?.results) {
const downloadKeys = Object.keys(rawData.download.results);
if (downloadKeys.length > 0) {
const lastDownloadKey = downloadKeys[downloadKeys.length - 1];
const downloadTimings = rawData.download.results[lastDownloadKey].timings;
if (downloadTimings.length > 0) {
state.speedTest.downloadSpeed = parseFloat((downloadTimings[downloadTimings.length - 1].bps / 1000000).toFixed(2));
try {
const rawData = testEngine?.results?.raw;
if (!rawData) return;
// 处理延迟数据和计算抖动
if (rawData.latency?.started) {
if (rawData.latency?.results?.timings?.length > 0) {
const latencyTimings = rawData.latency.results.timings;
state.speedTest.latency = parseFloat(latencyTimings[latencyTimings.length - 1].ping.toFixed(2));
if (latencyTimings.length >= 2) {
const differences = [];
for (let i = 1; i < latencyTimings.length; i++) {
differences.push(Math.abs(latencyTimings[i].ping - latencyTimings[i - 1].ping));
}
const mean = differences.reduce((a, b) => a + b, 0) / differences.length;
const variance = differences.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / differences.length;
state.speedTest.jitter = parseFloat(Math.sqrt(variance).toFixed(2));
}
}
}
}
if (rawData.upload?.results) {
const uploadKeys = Object.keys(rawData.upload.results);
if (uploadKeys.length > 0) {
const lastUploadKey = uploadKeys[uploadKeys.length - 1];
const uploadTimings = rawData.upload.results[lastUploadKey].timings;
if (uploadTimings.length > 0) {
state.speedTest.uploadSpeed = parseFloat((uploadTimings[uploadTimings.length - 1].bps / 1000000).toFixed(2));
// 处理下载速度
if (rawData.download?.started) {
if (rawData.download.current?.timings?.length > 0) {
const timings = rawData.download.current.timings;
state.speedTest.downloadSpeed = parseFloat((timings[timings.length - 1].bps / 1000000).toFixed(2));
} else if (rawData.download?.results) {
const downloadKeys = Object.keys(rawData.download.results);
if (downloadKeys.length > 0) {
const lastDownloadKey = downloadKeys[downloadKeys.length - 1];
const downloadTimings = rawData.download.results[lastDownloadKey].timings;
if (downloadTimings?.length > 0) {
const latestTiming = downloadTimings[downloadTimings.length - 1];
state.speedTest.downloadSpeed = parseFloat((latestTiming.bps / 1000000).toFixed(2));
}
}
} else {
state.speedTest.downloadSpeed = 0;
}
}
}
if (rawData.latency?.results?.timings?.length > 0) {
const latencyTimings = rawData.latency.results.timings;
state.speedTest.latency = parseFloat(latencyTimings[latencyTimings.length - 1].ping.toFixed(2));
// 处理上传速度
if (rawData.upload?.started) {
if (rawData.upload.current?.timings?.length > 0) {
const timings = rawData.upload.current.timings;
state.speedTest.uploadSpeed = parseFloat((timings[timings.length - 1].bps / 1000000).toFixed(2));
} else if (rawData.upload?.results) {
const uploadKeys = Object.keys(rawData.upload.results);
if (uploadKeys.length > 0) {
const lastUploadKey = uploadKeys[uploadKeys.length - 1];
const uploadTimings = rawData.upload.results[lastUploadKey].timings;
if (uploadTimings?.length > 0) {
const latestTiming = uploadTimings[uploadTimings.length - 1];
state.speedTest.uploadSpeed = parseFloat((latestTiming.bps / 1000000).toFixed(2));
}
}
} else {
state.speedTest.uploadSpeed = 0;
}
}
// 更新完状态后立即更新图表
updateCharts(
state.speedTest.downloadSpeed,
state.speedTest.uploadSpeed,
state.speedTest.latency,
state.speedTest.jitter,
rawData
);
} catch (error) {
console.error('Error in updateSpeedInRealTime:', error);
}
},
Expand Down Expand Up @@ -418,40 +506,61 @@ const setupTestEngine = async () => {
};
const speedTestController = async () => {
if (state.speedTest.status === 'running') {
testEngine.pause();
state.speedTest.status = "paused";
return;
}
try {
if (state.speedTest.status === 'running') {
testEngine.pause();
state.speedTest.status = "paused";
return;
}
if (state.speedTest.status === 'paused') {
testEngine.play();
return;
}
if (state.speedTest.status === 'paused') {
testEngine.play();
return;
}
Object.assign(state.speedTest, {
downloadSpeed: 0,
uploadSpeed: 0,
latency: 0,
jitter: 0,
streamingScore: "-",
gamingScore: "-",
rtcScore: "-",
hasScores: false
});
// 重置图表数据
resetChartData();
destroyCharts();
// 初始化图表并设置起始点
await initStartingPoints(); // 直接使用 initStartingPoints,它会处理初始化
testEngine = engineMethods.reset();
await setupTestEngine();
Object.assign(state.speedTest, {
downloadSpeed: 0,
uploadSpeed: 0,
latency: 0,
jitter: 0,
streamingScore: "-",
gamingScore: "-",
rtcScore: "-",
hasScores: false
});
trackEvent('Section', 'StartClick', 'SpeedTest');
testEngine.play();
testEngine = engineMethods.reset();
if (!testEngine) {
console.error('Failed to initialize test engine');
return;
}
await setupTestEngine();
trackEvent('Section', 'StartClick', 'SpeedTest');
testEngine.play();
} catch (error) {
console.error('Error in speedTestController:', error);
state.speedTest.status = "error";
}
};
// 生命周期
onMounted(() => {
store.setMountingStatus('speedtest', true);
});
// 清理
onUnmounted(() => {
destroyCharts();
});
// 暴露方法
defineExpose({
speedTestController
Expand Down Expand Up @@ -499,4 +608,45 @@ defineExpose({
transform: translateX(20px);
opacity: 0;
}
.speed-charts-container {
margin: 20px 0;
}
.chart-wrapper {
position: relative;
height: 150px;
width: 100%;
margin-bottom: 15px;
}
@media (max-width: 768px) {
.chart-wrapper {
height: 100px;
margin-bottom: 20px;
}
}
@media (min-width: 769px) and (max-width: 991px) {
.chart-wrapper {
height: 100px;
margin-bottom: 25px;
}
}
.jn-slide-in {
animation: slide-in 0.2s ease-in forwards;
}
@keyframes slide-in {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
</style>
4 changes: 4 additions & 0 deletions frontend/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,10 @@
"type": "add",
"change": "Add Lite version"
},
{
"type": "add",
"change": "Speed test can now display data change charts"
},
{
"type": "improve",
"change": "Code efficiency optimization"
Expand Down
4 changes: 4 additions & 0 deletions frontend/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,10 @@
"type": "add",
"change": "Ajout de la version Lite"
},
{
"type": "add",
"change": "Le test de vitesse peut maintenant afficher des graphiques de données"
},
{
"type": "improve",
"change": "Optimisation de la performance du code"
Expand Down
4 changes: 4 additions & 0 deletions frontend/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,10 @@
"type": "add",
"change": "新增 Lite 版本"
},
{
"type": "add",
"change": "网速测试可以看到数据变化图表"
},
{
"type": "improve",
"change": "代码效率优化"
Expand Down
Loading

0 comments on commit 76ae6d7

Please sign in to comment.