start public workouts
Gitea Actions Demo / build_and_push (push) Successful in 15m21s
Details
Gitea Actions Demo / build_and_push (push) Successful in 15m21s
Details
This commit is contained in:
parent
5bdf717731
commit
6fe580d2f1
|
|
@ -74,6 +74,7 @@ import { useColors } from "vuestic-ui";
|
||||||
|
|
||||||
import navigationRoutes, { type INavigationRoute } from "./NavigationRoutes";
|
import navigationRoutes, { type INavigationRoute } from "./NavigationRoutes";
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Sidebar",
|
name: "Sidebar",
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -137,7 +138,7 @@ export default defineComponent({
|
||||||
t,
|
t,
|
||||||
iconColor,
|
iconColor,
|
||||||
textColor,
|
textColor,
|
||||||
arrowDirection,
|
arrowDirection
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,42 +4,54 @@ export interface INavigationRoute {
|
||||||
meta: { icon: string };
|
meta: { icon: string };
|
||||||
children?: INavigationRoute[];
|
children?: INavigationRoute[];
|
||||||
}
|
}
|
||||||
|
const authRoutes = [
|
||||||
|
{
|
||||||
|
name: "dashboard",
|
||||||
|
displayName: "menu.dashboard",
|
||||||
|
meta: {
|
||||||
|
icon: "vuestic-iconset-dashboard",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'workouts',
|
||||||
|
displayName: 'menu.workouts',
|
||||||
|
meta: {
|
||||||
|
icon: 'folder_shared',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'list_workouts',
|
||||||
|
displayName: 'menu.list_workouts',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'workout_item',
|
||||||
|
displayName: 'Тренировка',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'upload_workouts',
|
||||||
|
displayName: 'menu.upload_workouts',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as INavigationRoute[]
|
||||||
|
|
||||||
|
const publicRoutes = [
|
||||||
|
{
|
||||||
|
name: "dashboard",
|
||||||
|
displayName: "menu.dashboard",
|
||||||
|
meta: {
|
||||||
|
icon: "vuestic-iconset-dashboard",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as INavigationRoute[]
|
||||||
|
|
||||||
|
// localStorage.getItem('token')
|
||||||
export default {
|
export default {
|
||||||
root: {
|
root: {
|
||||||
name: "/",
|
name: "/",
|
||||||
displayName: "navigationRoutes.home",
|
displayName: "navigationRoutes.home",
|
||||||
},
|
},
|
||||||
routes: [
|
routes: localStorage.getItem('token') ? authRoutes : publicRoutes,
|
||||||
{
|
|
||||||
name: "dashboard",
|
|
||||||
displayName: "menu.dashboard",
|
|
||||||
meta: {
|
|
||||||
icon: "vuestic-iconset-dashboard",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'workouts',
|
|
||||||
displayName: 'menu.workouts',
|
|
||||||
meta: {
|
|
||||||
icon: 'folder_shared',
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'list_workouts',
|
|
||||||
displayName: 'menu.list_workouts',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'workout_item',
|
|
||||||
displayName: 'Тренировка',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'upload_workouts',
|
|
||||||
displayName: 'menu.upload_workouts',
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
] as INavigationRoute[],
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
"auth": "Auth",
|
"auth": "Auth",
|
||||||
"buttons": "Buttons",
|
"buttons": "Buttons",
|
||||||
"timelines": "Timelines",
|
"timelines": "Timelines",
|
||||||
"dashboard": "Статистика",
|
"dashboard": "Лента",
|
||||||
"billing": "Billing",
|
"billing": "Billing",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"preferences": "Настройки Аккаунта",
|
"preferences": "Настройки Аккаунта",
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onBeforeUnmount, onMounted, ref, computed } from "vue";
|
import { onBeforeUnmount, onMounted, ref, computed } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { onBeforeRouteUpdate } from "vue-router";
|
import { onBeforeRouteUpdate } from "vue-router";
|
||||||
import { useBreakpoint } from "vuestic-ui";
|
import { useBreakpoint } from "vuestic-ui";
|
||||||
|
|
@ -79,7 +79,7 @@ const onResize = () => {
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
if (!token) {
|
if (!token && useRoute().path != "/dashboard") {
|
||||||
useRouter().push({ name: "login" });
|
useRouter().push({ name: "login" });
|
||||||
}
|
}
|
||||||
window.addEventListener("resize", onResize);
|
window.addEventListener("resize", onResize);
|
||||||
|
|
|
||||||
15
src/main.ts
15
src/main.ts
|
|
@ -11,11 +11,16 @@ import axios from 'axios';
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { createYmaps } from 'vue-yandex-maps';
|
import { createYmaps } from 'vue-yandex-maps';
|
||||||
|
|
||||||
const HOST = "https://cycle-rider.ru";
|
// const HOST = "https://cycle-rider.ru";
|
||||||
// const HOST = "http://localhost:8000";
|
const HOST = "http://localhost:8000";
|
||||||
|
|
||||||
axios.defaults.baseURL = HOST;
|
axios.defaults.baseURL = HOST;
|
||||||
|
|
||||||
|
const axiosPublic= axios.create ({
|
||||||
|
baseURL : HOST,
|
||||||
|
timeout: 60000,
|
||||||
|
});
|
||||||
|
|
||||||
const axiosAuth = axios.create ({
|
const axiosAuth = axios.create ({
|
||||||
baseURL : HOST,
|
baseURL : HOST,
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
|
|
@ -23,13 +28,11 @@ const axiosAuth = axios.create ({
|
||||||
|
|
||||||
axiosAuth.interceptors.request.use(
|
axiosAuth.interceptors.request.use(
|
||||||
function (config) {
|
function (config) {
|
||||||
// Do something before request is sent
|
const token = localStorage.getItem('token')
|
||||||
const token = localStorage.getItem('token') //do not store token on localstorage!!!
|
|
||||||
config.headers.Authorization = `Bearer ${token}`;
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
function (error) {
|
function (error) {
|
||||||
// Do something with request error
|
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -76,9 +79,11 @@ function responseErrorHandler(response: any) {
|
||||||
return httpErrorHandler(response)
|
return httpErrorHandler(response)
|
||||||
}
|
}
|
||||||
axiosAuth.interceptors.response.use(responseHandler, responseErrorHandler)
|
axiosAuth.interceptors.response.use(responseHandler, responseErrorHandler)
|
||||||
|
axiosPublic.interceptors.response.use(responseHandler, responseErrorHandler)
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
app.provide('axiosAuth', axiosAuth);
|
app.provide('axiosAuth', axiosAuth);
|
||||||
|
app.provide('axiosPublic', axiosPublic);
|
||||||
app.use(stores);
|
app.use(stores);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="news-feed">
|
||||||
|
<template v-if="workoutItems.length==0">
|
||||||
|
<div>Публичные тренировки не найдены</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="workoutItems.length>0" v-for="(item, index) in workoutItems" :key="item.id">
|
||||||
|
<article class="news-item">
|
||||||
|
<div class="news-item-header" v-on:click="openWorkout(item.id)">
|
||||||
|
<h3>{{ item.name }}</h3>
|
||||||
|
</div>
|
||||||
|
<ul class="metadata">
|
||||||
|
<li v-if="item.speed"><span>Скорость:</span> {{ speedConvert(item.speed) }} км/ч</li>
|
||||||
|
<li v-if="item.heart_rate"><span>Пульс:</span> {{ item.heart_rate }} уд /мин</li>
|
||||||
|
<li v-if="item.cadence"><span>Каденс:</span> {{ item.cadence }} об/мин</li>
|
||||||
|
<li v-if="item.distantion"><span>Расстояние:</span> {{ distConvert(item.distantion) }} км</li>
|
||||||
|
<li v-if="item.duraion_sec"><span>Продолжительность:</span> {{ secondsToDuration(item.duraion_sec) }} </li>
|
||||||
|
<li v-if="item.power"><span>Мощность:</span> {{ Math.round(item.power) }} Вт</li>
|
||||||
|
</ul>
|
||||||
|
<div v-if="item.attachment"
|
||||||
|
class="image-container"
|
||||||
|
:style="{'background-image': `url(${item.attachment.url})` }"
|
||||||
|
v-on:click="openWorkout(item.id)"></div>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import { AxiosResponse, AxiosInstance } from "axios";
|
||||||
|
import { ref, inject } from 'vue';
|
||||||
|
import { useToast } from "vuestic-ui/web-components";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { WorkoutItem, Attachment, secondsToDuration, distConvert, speedConvert } from "./Definitions.vue";
|
||||||
|
|
||||||
|
|
||||||
|
const { push } = useRouter();
|
||||||
|
const axiosPublic= inject('axiosPublic') as AxiosInstance;
|
||||||
|
let workoutItems = ref<Array<WorkoutItem>>([]);
|
||||||
|
const { init } = useToast();
|
||||||
|
|
||||||
|
|
||||||
|
const openWorkout = (id: string) => {
|
||||||
|
push({ name: "workout_item", params: {id: id}}).catch((error) => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initWorkouts = () => {
|
||||||
|
axiosPublic
|
||||||
|
.get(`/api/v0/public/workouts`)
|
||||||
|
.then((response: AxiosResponse) => {
|
||||||
|
workoutItems.value = response.data.results;
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
init({
|
||||||
|
message: "Что-то пошло не так.",
|
||||||
|
color: "error",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
initWorkouts();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.image-container {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 300px;
|
||||||
|
width: 100%;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-feed {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-item-header {
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
}
|
||||||
|
.news-item-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.news-item h3 {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3d3d3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata {
|
||||||
|
border-top: #9c9a9a 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata li {
|
||||||
|
list-style: none;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata span {
|
||||||
|
font-size: 0.9em;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,338 @@
|
||||||
|
<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: false,
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: false
|
||||||
|
},
|
||||||
|
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 show_data = [];
|
||||||
|
if (data.value.datasets[0].data[context.dataIndex]) {
|
||||||
|
show_data.push("Скорость: " + Math.floor(data.value.datasets[0].data[context.dataIndex]));
|
||||||
|
}
|
||||||
|
if (data.value.datasets[1].data[context.dataIndex]) {
|
||||||
|
show_data.push("Пульс: " + data.value.datasets[1].data[context.dataIndex]);
|
||||||
|
}
|
||||||
|
if (data.value.datasets[2].data[context.dataIndex]) {
|
||||||
|
show_data.push("Мощность: " + data.value.datasets[2].data[context.dataIndex]);
|
||||||
|
}
|
||||||
|
if (Math.floor(data.value.datasets[3].data[context.dataIndex])) {
|
||||||
|
show_data.push("Подъем: " + Math.floor(data.value.datasets[3].data[context.dataIndex]));
|
||||||
|
}
|
||||||
|
return show_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
zoom: {
|
||||||
|
drag: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
pinch: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
mode: 'x',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const msToKmh = (ms: number) => ms * 3.6;
|
||||||
|
const axiosPublic= inject('axiosPublic') as AxiosInstance;
|
||||||
|
|
||||||
|
const resetChartZoom = () => {
|
||||||
|
resetZoom(chart.value.chart);
|
||||||
|
}
|
||||||
|
|
||||||
|
const initWorkout = (id: string) => {
|
||||||
|
axiosPublic
|
||||||
|
.get(`/api/v0/public/workouts/${id}`)
|
||||||
|
.then((response: AxiosResponse) => {
|
||||||
|
workoutItem.value = response.data.workout;
|
||||||
|
let times = [];
|
||||||
|
let speed = [];
|
||||||
|
let heart_rate = [];
|
||||||
|
let power = [];
|
||||||
|
let coords = [];
|
||||||
|
let elevation = [];
|
||||||
|
for (let i in response.data.results) {
|
||||||
|
times.push(response.data.results[i].timestamp);
|
||||||
|
coords.push([response.data.results[i].longitude, response.data.results[i].latitude]);
|
||||||
|
if (response.data.results[i].elevation) {
|
||||||
|
elevation.push(response.data.results[i].elevation);
|
||||||
|
}
|
||||||
|
if (response.data.results[i].speed) {
|
||||||
|
speed.push(msToKmh(response.data.results[i].speed));
|
||||||
|
}
|
||||||
|
if (response.data.results[i].power) {
|
||||||
|
power.push(response.data.results[i].power);
|
||||||
|
}
|
||||||
|
if (response.data.results[i].heart_rate) {
|
||||||
|
heart_rate.push(response.data.results[i].heart_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lineCoordinates.value = coords
|
||||||
|
mapCenter.value = [coords[0][0], coords[0][1]];
|
||||||
|
let datasets = [];
|
||||||
|
if (speed) {
|
||||||
|
datasets.push({ radius: 0, label: 'Скорость', borderColor: '#00aa00', backgroundColor: '#00aa00', data: speed });
|
||||||
|
}
|
||||||
|
if (heart_rate) {
|
||||||
|
datasets.push({ radius: 0, label: 'Пульс', borderColor: '#990000', backgroundColor: '#990000', data: heart_rate, });
|
||||||
|
}
|
||||||
|
if (power) {
|
||||||
|
datasets.push({ radius: 0, label: 'Мощность', borderColor: '#cccccc', backgroundColor: '#cccccc', data: power, });
|
||||||
|
}
|
||||||
|
if (elevation) {
|
||||||
|
datasets.push( { radius: 0, label: 'Подъем', borderColor: '#000', backgroundColor: '#000', data: elevation, });
|
||||||
|
}
|
||||||
|
data.value = {
|
||||||
|
labels: times,
|
||||||
|
datasets: datasets
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.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>
|
||||||
|
|
@ -19,7 +19,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
name: "dashboard",
|
name: "dashboard",
|
||||||
path: "dashboard",
|
path: "dashboard",
|
||||||
component: () => import("../pages/admin/dashboard/Dashboard.vue"),
|
component: () => import("../pages/workouts/Feed.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "workouts",
|
name: "workouts",
|
||||||
|
|
@ -39,6 +39,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
name: "workout_item",
|
name: "workout_item",
|
||||||
path: "workouts/:id",
|
path: "workouts/:id",
|
||||||
component: () => import("../pages/workouts/WorkoutItem.vue"),
|
component: () => import("../pages/workouts/WorkoutItem.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "workout_public_item",
|
||||||
|
path: "public/workouts/:id",
|
||||||
|
component: () => import("../pages/workouts/WorkoutPublicItem.vue"),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue