strava-frontend/src/pages/workouts/WorkoutItem.vue

321 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<h1 class="page-title">Tренировка</h1>
<div v-if="workoutItem">
<div id="workout-container">
<div id="workout-map">
<yandex-map v-model="map" :settings="{
location: {
center: mapCenter,
zoom: 9,
},
}" width="100%" height="350px">
<yandex-map-default-scheme-layer />
<yandex-map-default-features-layer />
<yandex-map-feature :settings="{
geometry: {
type: 'LineString',
coordinates: lineCoordinates,
},
style: {
stroke: [{ color: '#007afce6', width: 4 }],
},
}" />
<yandex-map-default-marker v-if="currentCoordinates" :settings="{
coordinates: currentCoordinates,
}" />
</yandex-map>
</div>
<div id="workout-short-data">
<h3>{{ workoutItem.name }}</h3>
<div class="workout-item-params" v-if="workoutItem.power">
<div class="workout-item-params-name">Средняя мощность:</div>
<div class="workout-item-params-value">{{ Math.floor(workoutItem.power) }} Вт</div>
</div>
<div class="workout-item-params" v-if="workoutItem.max_power">
<div class="workout-item-params-name">Максимальная мощность:</div>
<div class="workout-item-params-value">{{ Math.floor(workoutItem.max_power) }} Вт</div>
</div>
<div class="workout-item-params" v-if="workoutItem.distantion">
<div class="workout-item-params-name">Расстояние:</div>
<div class="workout-item-params-value">{{ distConvert(workoutItem.distantion) }} км</div>
</div>
<div class="workout-item-params" v-if="workoutItem.temperature">
<div class="workout-item-params-name">Температура:</div>
<div class="workout-item-params-value">{{ workoutItem.temperature }} °C</div>
</div>
<div class="workout-item-params" v-if="workoutItem.heart_rate">
<div class="workout-item-params-name">Средний пульс:</div>
<div class="workout-item-params-value">{{ Math.floor(workoutItem.heart_rate) }} уд./ мин.</div>
</div>
<div class="workout-item-params" v-if="workoutItem.max_heart_rate">
<div class="workout-item-params-name">Максимальный пульс:</div>
<div class="workout-item-params-value">{{ Math.floor(workoutItem.max_heart_rate) }} уд./ мин.</div>
</div>
<div class="workout-item-params" v-if="workoutItem.speed">
<div class="workout-item-params-name">Средняя скорость:</div>
<div class="workout-item-params-value">{{ speedConvert(workoutItem.speed) }} км / ч</div>
</div>
<div class="workout-item-params" v-if="workoutItem.max_speed">
<div class="workout-item-params-name">Максимальная скорость:</div>
<div class="workout-item-params-value">{{ speedConvert(workoutItem.max_speed || 0) }} км / ч</div>
</div>
</div>
</div>
<VaButton v-on:click="resetChartZoom()" preset="secondary" color="textPrimary" id="zoom-botton">
<VaZoomOut />
</VaButton>
<!-- @vue-ignore -->
<LineWithLineChart
id="chart"
ref="chart"
:options="chartOptions"
:plugins="chartPlugins"
:data="data"
pointerAxies="currentCoordinates"
>
</LineWithLineChart>
</div>
<div v-else>
<VaInnerLoading
loading
:size="60"
>
</VaInnerLoading>
</div>
</template>
<script setup lang="ts">
import VaZoomOut from "../../components/icons/VaZoomOut.vue";
import { useRoute } from 'vue-router'
import 'chartjs-adapter-moment';
import { AxiosResponse, AxiosInstance } from "axios";
import { ref, inject, shallowRef } from 'vue'
import { useToast } from "vuestic-ui/web-components";
import zoomPlugin, { resetZoom } from 'chartjs-plugin-zoom';
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
LineElement,
LinearScale,
CategoryScale,
LogarithmicScale,
PointElement,
TimeScale
} from 'chart.js'
import LineWithLineChart from './components/LineWithLineChart.js'
import type { YMap } from '@yandex/ymaps3-types';
import { YandexMap, YandexMapDefaultSchemeLayer, YandexMapFeature, YandexMapDefaultFeaturesLayer, YandexMapDefaultMarker } from 'vue-yandex-maps';
import { WorkoutItem, distConvert, speedConvert } from "./Definitions.vue";
//Можно использовать для различных преобразований
const map = shallowRef<null | YMap>(null);
const chart = ref();
let mapCenter = ref<Array<number>>([37.617644, 55.755819]);
let lineCoordinates = ref<Array<Array<number>>>([]);
let currentCoordinates = ref<Array<number> | null>([]);
ChartJS.register(
Title,
Tooltip,
Legend,
LineElement,
CategoryScale,
LogarithmicScale,
LinearScale,
PointElement,
zoomPlugin,
TimeScale
)
const { init } = useToast();
const route = useRoute()
type ChartDataset = {
"radius": number,
"label": string,
"backgroundColor": string,
"borderColor": string,
"data": Array<number>,
}
type ChartData = {
"labels": Array<string>,
"datasets": Array<ChartDataset>,
}
type afterEventEvent = {
"type": string
}
type afterEventArgs = {
"event": afterEventEvent,
}
let workoutItem = ref<WorkoutItem>();
const data = ref<ChartData>({
labels: [],
datasets: [],
})
const chartPlugins = [
{
id: 'eventPlugin',
afterEvent(chart: any, args: afterEventArgs, opts: any) {
if (args.event.type == "mouseout") {
currentCoordinates.value = null;
}
}
}
];
const chartOptions = {
responsive: true,
scales: {
yAxes: {
type: 'logarithmic',
position: 'right',
stacked: true,
ticks: {
beginAtZero: true
},
gridLines: {
display: true
}
},
x: {
type: 'time',
time: {
displayFormats: { hour: 'HH:mm' }
}
}
},
plugins: {
tooltip: {
enabled: true,
intersect: false,
footerMarginTop: 10,
displayColors: false,
callbacks: {
label: function (context: any) {
currentCoordinates.value = lineCoordinates.value[context.dataIndex];
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.x !== null) {
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed.y);
}
return ["Скорость: " + data.value.datasets[0].data[context.dataIndex],
"Пульс: " + data.value.datasets[1].data[context.dataIndex],
"Мощность: " + data.value.datasets[2].data[context.dataIndex],
];
}
}
},
zoom: {
zoom: {
drag: {
enabled: true,
},
pinch: {
enabled: true
},
mode: 'x',
}
}
}
}
const msToKmh = (ms: number) => ms * 3.6;
const axiosAuth = inject('axiosAuth') as AxiosInstance;
const resetChartZoom = () => {
resetZoom(chart.value.chart);
}
const initWorkout = (id: string) => {
axiosAuth
.get(`/api/v0/workouts/${id}`)
.then((response: AxiosResponse) => {
workoutItem.value = response.data.workout;
let latitude = [];
let longitude = [];
let times = [];
let speed = [];
let heart_rate = [];
let power = [];
for (let i in response.data.results) {
response.data.results[i].lang;
response.data.results[i].lang;
times.push(response.data.results[i].time);
speed.push(msToKmh(response.data.results[i].speed));
power.push(response.data.results[i].power);
heart_rate.push(response.data.results[i].heart_rate);
latitude.push(response.data.results[i].latitude);
longitude.push(response.data.results[i].longitude);
lineCoordinates.value.push([response.data.results[i].longitude, response.data.results[i].latitude]);
}
mapCenter.value = [longitude[0], latitude[0]];
data.value = {
labels: times,
datasets: [
{ radius: 0, label: 'Скорость', borderColor: '#00aa00', backgroundColor: '#00aa00', data: speed },
{ radius: 0, label: 'Пульс', borderColor: '#990000', backgroundColor: '#990000', data: heart_rate, },
{ radius: 0, label: 'Мощность', borderColor: '#cccccc', backgroundColor: '#cccccc', data: power, },
]
}
})
.catch((error: any) => {
console.log(error);
init({
message: "Что-то пошло не так.",
color: "error",
});
});
};
initWorkout(route.params.id as string);
</script>
<style>
#zoom-botton {
display: block;
margin-top: 20px;
margin-bottom: -35px;
margin-left: 20px;
}
#workout-container {
display: flex;
width: 100%;
justify-content: space-between;
}
#workout-map {
width: 70%;
}
#workout-short-data {
width: 30%;
padding: 0 0 0 20px;
}
h3 {
margin-bottom: 10px;
font-size: 20px;
font-family: sans-serif;
}
.workout-item-params {
display: flex;
width: 100%;
justify-content:left;
color: #333;
padding: 0 0 5px 0
}
.workout-item-params-name {
font-weight: bold;
}
.workout-item-params-value {
padding: 0 0 0 5px
}
</style>