Skip to content

Commit

Permalink
feat: 添加分析单个赛段档位使用占比功能
Browse files Browse the repository at this point in the history
1. 添加分析单个赛段档位使用占比功能(计时赛/锦标赛)
  • Loading branch information
XCLHove committed May 2, 2024
1 parent fb20875 commit c58e5af
Show file tree
Hide file tree
Showing 9 changed files with 424 additions and 15 deletions.
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
locales
locales
32 changes: 30 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ea-wrc-club-manager",
"private": true,
"version": "0.1.0",
"version": "0.2.0",
"scripts": {
"dev": "vite",
"build": "vite build && electron-builder",
Expand All @@ -11,6 +11,7 @@
"@electron-toolkit/utils": "^3.0.0",
"@vueuse/core": "^10.9.0",
"axios": "^1.6.8",
"echarts": "^5.5.0",
"element-plus": "^2.6.3",
"node-xlsx": "^0.24.0",
"pinia": "^2.1.7",
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
* [X] 查看锦标赛赛段排名
* [X] 查看计时赛赛段排名
* [X] 统计锦标赛单个分站排名
* [X] 导出锦标赛单个分站完赛赛段数为Excel
* [X] 分析单个赛段档位使用占比(计时赛/锦标赛)

## 仓库地址

Expand Down
30 changes: 30 additions & 0 deletions src/api/clubApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Order, SortBy } from "@/interfaces/Search.ts";
import { ClubDetail, Championship } from "@/interfaces/ClubDetail.ts";
import { ChampionshipStageLeaderboard } from "@/interfaces/ChampionshipStageLeaderboard.ts";
import { Platform } from "@/interfaces/Platform.ts";
import { PerformanceData } from "@/interfaces/PerformanceData.ts";

export const pageMyClubs = ({
page,
Expand Down Expand Up @@ -165,3 +166,32 @@ export const getChampionship = (championshipID: string) => {
return data;
});
};

export const performanceAnalysis = (() => {
const cache = new Map<string, PerformanceData>();

return ({
leaderboardId,
playerId,
}: {
leaderboardId: string;
playerId: string;
}) => {
const cacheId = `${leaderboardId}-${playerId}`;
if (cache.has(cacheId)) {
return Promise.resolve(cache.get(cacheId));
}

return request
.get("/wrc2023Stats/performanceAnalysis/ghost", {
params: {
WrcRivalLeaderboardId: leaderboardId,
WrcRivalPlayerId: playerId,
},
})
.then(({ data }) => {
cache.set(cacheId, data);
return data;
}) as Promise<PerformanceData>;
};
})();
164 changes: 164 additions & 0 deletions src/components/Analysis.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { PerformanceData, Rival } from "@/interfaces/PerformanceData";
import { performanceAnalysis } from "@/api/clubApi";
import * as echarts from "echarts";
import { debounce } from "@/utils/debounce/debounce";
import { useWindowSize } from "@vueuse/core";
import { elPrompt } from "@/utils/elPrompt";
const height = (() => {
const { height } = useWindowSize();
return computed(() => {
return height.value - 50;
});
})();
const visible = defineModel("visible", {
required: true,
default: false,
});
const data = defineModel<{
leaderboardId: string;
playerId: string;
location: string;
stage: string;
car: string;
}>("data", {
required: true,
default: null,
});
watch(
data.value,
debounce(() => {
visible.value = true;
loadData();
}, 0.2),
);
const close = () => {
visible.value = false;
};
const gearAnalysisPie = ref<HTMLDivElement>();
const gearAnalysisHistogram = ref<HTMLDivElement>();
let rivalData = {} as Rival;
const loading = ref(false);
const loadData = () => {
if (!data.value) return;
if (!data.value.playerId) {
elPrompt.warning("该玩家未打开‘游戏偏好:公开幽灵车’选项", 3);
return;
}
loading.value = true;
performanceAnalysis({
leaderboardId: data.value.leaderboardId,
playerId: data.value.playerId,
})
.then((data: PerformanceData) => {
rivalData = data.data.rival;
analysisGear();
})
.finally(() => {
loading.value = false;
});
};
const analysisGear = (() => {
let gearChartPie: echarts.ECharts = null;
let gearChartHistogram: echarts.ECharts = null;
return () => {
if (!gearChartPie) {
gearChartPie = echarts.init(gearAnalysisPie.value);
}
if (!gearChartHistogram) {
gearChartHistogram = echarts.init(gearAnalysisHistogram.value);
}
const map = new Map<number, number>();
rivalData.gear.forEach((gear) => {
const count = map.get(gear) || 0;
map.set(gear, count + 1);
});
const pieData = [];
const histogramData = {
x: [],
y: [],
};
map.forEach((count, gear) => {
pieData.push({
value: count,
name: gear,
});
histogramData.x.push(`${gear}档`);
histogramData.y.push(count / rivalData.gear.length);
});
const pieOption: Parameters<typeof gearChartPie.setOption>[0] = {
title: {
text: "档位占比",
subtext: `${data.value.location} - ${data.value.stage} - ${data.value.car}`,
left: "center",
},
tooltip: {
trigger: "item",
formatter: "{d}%",
},
series: [
{
name: "档位",
type: "pie",
radius: "50%",
data: pieData,
label: {
show: true,
formatter: "{b}档 {d}%",
},
},
],
};
const histogramOption: Parameters<typeof gearChartPie.setOption>[0] = {
xAxis: {
data: histogramData.x,
},
yAxis: {},
series: [
{
type: "bar",
data: histogramData.y,
},
],
};
gearChartPie.setOption(pieOption);
gearChartHistogram.setOption(histogramOption);
};
})();
</script>

<template>
<div>
<el-dialog fullscreen @close="close" v-model="visible">
<el-scrollbar :height="height">
<div class="analysis-container" v-loading="loading">
<div ref="gearAnalysisPie" class="gear-analysis"></div>
<div ref="gearAnalysisHistogram" class="gear-analysis"></div>
</div>
</el-scrollbar>
</el-dialog>
</div>
</template>

<style scoped lang="less">
.analysis-container {
.gear-analysis {
height: 300px;
}
}
</style>
86 changes: 86 additions & 0 deletions src/interfaces/PerformanceData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
export interface PerformanceData {
data: Data;
renderSettings: null;
}

export interface Data {
player: null;
rival: Rival;
}

export interface Rival {
brake: number[];
distance: number[];
gear: number[];
gearChar: null;
handBrake: number[];
lateral: null;
longitudinal: null;
maxBrake: number;
maxDistance: number;
maxGear: number;
maxLateral: number;
maxLongitudinal: number;
maxMillis: number;
maxProgress: number;
maxRPM: number;
maxSpeed: number;
maxSteering: number;
maxThrottle: number;
maxXPosition: number;
maxYPosition: number;
maxZPosition: number;
millis: number[];
minBrake: number;
minDistance: number;
minGear: number;
minLateral: number;
minLongitudinal: number;
minMillis: number;
minProgress: number;
minRPM: number;
minSpeed: number;
minSteering: number;
minThrottle: number;
minXPosition: number;
minYPosition: number;
minZPosition: number;
performanceAnalysisMetadata: PerformanceAnalysisMetadata;
position: Position[];
progress: null;
rearWing: null;
rotation: Rotation[];
rpm: null;
speed: number[];
steering: number[];
throttle: number[];
}

export interface PerformanceAnalysisMetadata {
lapTime: string;
mapId: string;
platform: number;
playerName: string;
referenceMass: number;
sectorIndex: number[];
sectorMillis: number[];
sectorTimes: string[];
teamId: number;
teamName: null;
vehicleClassId: number;
vehicleId: number;
vehicleName: string;
}

export interface Position {
x: number;
y: number;
z: number;
}

export interface Rotation {
w: number;
x: number;
y: number;
z: number;
}
Loading

0 comments on commit c58e5af

Please sign in to comment.