- test leader board
This commit is contained in:
Alexandrina-Kuzeleva
2025-11-21 16:28:33 +03:00
parent fa0325106a
commit 107e7a4e31
2 changed files with 269 additions and 0 deletions

View File

@@ -0,0 +1,264 @@
<template>
<div v-if="loading" class="text-center p-10 text-gray-600">
Загружаем таблицу лидеров...
</div>
<div v-else class="page-container">
<h1 class="page-title">Таблица лидеров по очкам</h1>
<!-- Блок с текущим пользователем и лучшим -->
<section class="user-results">
<!-- Ваш результат -->
<div class="box results-box">
<h2 class="box-heading">Ваш результат</h2>
<!-- Статусы -->
<template v-if="currentUserPosition">
<img
class="cup-img"
:src="statusIcon"
/>
<p class="status" :class="statusClass">{{ statusLabel }}</p>
</template>
<template v-else>
<img class="cup-img" src="/files/dart-board.svg">
<p class="status">Вы пока не в рейтинге</p>
</template>
<p class="points">{{ currentUserPoints }} баллов</p>
<template v-if="currentUserPosition > 1">
<p class="motivation-text">
До {{ currentUserPosition - 1 }}-го места нужно:
<strong>{{ pointsToNext }} баллов</strong>
</p>
</template>
</div>
<!-- Лучший участник -->
<div class="box results-box">
<h2 class="box-heading">Лучший участник</h2>
<template v-if="topUser">
<div class="initial-circle">
{{ topUser.user[0].toUpperCase() }}
</div>
<p class="username">
<a
class="user-link"
:href="`/lms/user/${topUser.username}`"
>
{{ topUser.full_name }}
</a>
<img class="cup-badge" src="/files/gold-cup.png">
</p>
<p class="points">{{ topUser.points }} баллов</p>
</template>
<template v-else>
<p class="no-leader">Нет лучшего участника</p>
</template>
</div>
</section>
<!-- Таблица лидеров -->
<section class="box">
<h2 class="box-heading">Топ лидеров</h2>
<div class="leaders-table-wrapper">
<table class="leaders-table">
<thead>
<tr>
<th>#</th>
<th>Пользователь</th>
<th>Баллы активности</th>
<th>Общая сумма</th>
<th>Бонус (МПГУ)</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in sorted"
:key="item.user"
:class="{'current-user': item.user === currentUserEmail}"
>
<td>{{ index + 1 }}</td>
<td>
<div class="flex items-center">
<div class="initial-circle-small">
{{ item.user[0].toUpperCase() }}
</div>
<div>
<a :href="`/lms/user/${item.username}`"
class="user-link">{{ item.full_name }}</a>
<!-- значки -->
<img v-if="index === 0" class="cup-badge" src="/files/gold-cup.png">
<img v-else-if="index === 1" class="cup-badge" src="/files/silver-cup.png">
<img v-else-if="index === 2" class="cup-badge" src="/files/bronze-cup.png">
</div>
</div>
</td>
<td>{{ item.points }}</td>
<td>{{ item.points }}</td>
<td>{{ item.bonus }}</td>
</tr>
</tbody>
</table>
<p class="stats-note">
* Бонус 1 балл за каждые 100 очков (максимум 10)
</p>
</div>
</section>
</div>
</template>
<script setup>
import { inject, ref, computed, onMounted } from "vue";
import { createResource } from "frappe-ui";
const $user = inject("$user");
const currentUserEmail = $user.data.email;
const loading = ref(true);
// Загружаем все Energy Point Log
const logs = createResource({
url: "frappe.client.get_list",
params: {
doctype: "Energy Point Log",
fields: ["name", "user", "points"],
limit_page_length: 2000
},
auto: false,
onSuccess() {
loading.value = false;
}
});
// Собираем баллы по пользователям
const userPoints = computed(() => {
const map = {};
if (!logs.data) return map;
logs.data.forEach(entry => {
if (!map[entry.user]) map[entry.user] = 0;
map[entry.user] += entry.points;
});
return map;
});
// Загружаем данные пользователей (имя + username)
const userCache = ref({});
async function loadUserProfile(email) {
if (userCache.value[email]) return userCache.value[email];
const profile = await frappe.call({
method: "frappe.client.get",
args: {
doctype: "User",
name: email
}
});
userCache.value[email] = {
full_name: profile.message.full_name,
username: profile.message.username
};
return userCache.value[email];
}
// Готовим сортированный массив
const sorted = ref([]);
async function buildSorted() {
const arr = [];
for (const [user, points] of Object.entries(userPoints.value)) {
const data = await loadUserProfile(user);
arr.push({
user,
points,
full_name: data.full_name,
username: data.username,
bonus: Math.min(Math.floor(points / 100), 10)
});
}
arr.sort((a, b) => b.points - a.points);
sorted.value = arr;
}
// Текущий пользователь
const currentUserPoints = computed(
() => userPoints.value[currentUserEmail] || 0
);
const currentUserPosition = computed(() => {
const index = sorted.value.findIndex(u => u.user === currentUserEmail);
return index === -1 ? null : index + 1;
});
const pointsToNext = computed(() => {
if (!currentUserPosition.value || currentUserPosition.value <= 1) return 0;
const next = sorted.value[currentUserPosition.value - 2];
return next.points - currentUserPoints.value;
});
// Топ-1 участник
const topUser = computed(() => sorted.value[0]);
// UI статусы
const statusLabel = computed(() => {
if (!currentUserPosition.value) return "Не в рейтинге";
if (currentUserPosition.value === 1) return "Чемпион";
if (currentUserPosition.value === 2) return "Второе место";
if (currentUserPosition.value === 3) return "Третье место";
if (currentUserPosition.value <= 5) return "В топ-5";
if (currentUserPosition.value <= 10) return "В топ-10";
return "Стремитесь выше";
});
const statusIcon = computed(() => {
if (currentUserPosition.value === 1) return "/files/gold-cup.png";
if (currentUserPosition.value === 2) return "/files/silver-cup.png";
if (currentUserPosition.value === 3) return "/files/bronze-cup.png";
return "/files/dart-board.svg";
});
const statusClass = computed(() => {
if (currentUserPosition.value === 1) return "gold";
if (currentUserPosition.value === 2) return "silver";
if (currentUserPosition.value === 3) return "bronze";
return "";
});
// Загружаем
onMounted(async () => {
await logs.fetch();
await buildSorted();
});
</script>
<style>
/* сюда полностью переносите ваш CSS — он совместим */
</style>

View File

@@ -35,6 +35,11 @@ const routes = [
name: 'MyPoints',
component: () => import('@/pages/MyPoints.vue'),
},
{
path: '/leaderboard',
name: 'LeaderBoard',
component: () => import('@/pages/LeaderBoard.vue'),
},
// End of test of page
{
path: '/courses',