265 lines
6.2 KiB
Vue
265 lines
6.2 KiB
Vue
<script setup lang="ts">
|
||
import { onMounted, ref } from 'vue';
|
||
import router from '@/router';
|
||
import type { Game, Team, Teams } from './models';
|
||
import { apiGetTeams, apiAddTeam, apiGetGame, apiStartGame, apiStopGame, apiGaveApplication, apiDownloadQrCodesFile } from './client';
|
||
import TeamQRCode from '../components/TeamQRCode.vue'
|
||
import HeaderBlock from './HeaderBlock.vue';
|
||
|
||
const qrurl = ref("-")
|
||
const qrteam = ref("-")
|
||
|
||
const gameState = ref("")
|
||
const game = ref<Game | undefined>()
|
||
|
||
const teams = ref<Teams>({ teams: [] })
|
||
|
||
const teamName = ref("")
|
||
async function addTeam() {
|
||
await apiAddTeam(teamName.value)
|
||
teamName.value = ""
|
||
}
|
||
|
||
async function startGame() {
|
||
gameState.value = "Загрузка..."
|
||
await apiStartGame()
|
||
}
|
||
|
||
async function stopGame() {
|
||
gameState.value = "Загрузка..."
|
||
await apiStopGame()
|
||
}
|
||
|
||
async function getGame() {
|
||
game.value = await apiGetGame()
|
||
if (game.value.state === "NEW") {
|
||
gameState.value = "Игра ещё не началась"
|
||
}
|
||
if (game.value.state === "RUN") {
|
||
gameState.value = "Игра идет с " + game.value?.startAt.substring(11)
|
||
}
|
||
if (game.value.state === "STOP") {
|
||
gameState.value = "Игра остановлена " + game.value?.startAt.substring(11) + " - " + game.value?.endAt.substring(11)
|
||
}
|
||
}
|
||
|
||
function compareTeams(a: Team, b: Team): number {
|
||
return b.applications.length - a.applications.length
|
||
}
|
||
|
||
let intervalId = 0
|
||
onMounted(async () => {
|
||
teams.value = await apiGetTeams()
|
||
|
||
intervalId = setInterval(async () => {
|
||
teams.value = await apiGetTeams()
|
||
teams.value.teams.sort(compareTeams)
|
||
await getGame()
|
||
}, 2000);
|
||
|
||
router.beforeEach((to, from, next) => {
|
||
clearInterval(intervalId);
|
||
next();
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<HeaderBlock>
|
||
<div>
|
||
Вечерний детектив - {{ gameState }}
|
||
</div>
|
||
<div class="buttons-block">
|
||
<button v-on:click="router.push('/editor')" class="button-menu button-custom-inline">Редактор</button>
|
||
<button v-on:click="startGame" class="button-menu button-custom-inline">Начать</button>
|
||
<button v-on:click="stopGame" class="button-menu button-custom-inline">Остановить</button>
|
||
<button v-on:click="apiDownloadQrCodesFile" class="button-menu button-custom-inline">Скачать qr‑ы</button>
|
||
</div>
|
||
</HeaderBlock>
|
||
|
||
<TeamQRCode :data="qrurl" :title="qrteam" />
|
||
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>№</th>
|
||
<th>Название команды</th>
|
||
<th>Поездки</th>
|
||
<th>Приложения</th>
|
||
<th>Qr</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="(team, number) in teams.teams" :key="team.name">
|
||
<td>
|
||
{{ number + 1 }}
|
||
</td>
|
||
<td class="team-name">{{ team.name }}
|
||
<a :href="team.url" target="_blank">[url]</a>
|
||
</td>
|
||
<td class="cell-center">{{ team.spendTime }}</td>
|
||
<td>
|
||
<div v-for="application in team.applications" :key="application.id">
|
||
{{ application.name }} <button class="link-button"
|
||
@click="apiGaveApplication(team.id, application.id)">Выдано</button>
|
||
</div>
|
||
</td>
|
||
<td class="cell-center">
|
||
<a v-on:click="qrurl = team.url, qrteam = team.name">QR</a>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<div class="form-custom form-block">
|
||
<div class="center-block-custom">
|
||
<form @submit.prevent="addTeam">
|
||
<div>
|
||
<input class="input-custom" v-model="teamName" type="text" placeholder="Название команды">
|
||
</div>
|
||
<div class="button-container">
|
||
<button class="button-custom" type="submit">Добавить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
</template>
|
||
|
||
<style scoped>
|
||
.buttons-block {
|
||
padding-top: 5px;
|
||
}
|
||
|
||
.button-menu {
|
||
margin: 5px 10px 5px 0;
|
||
}
|
||
|
||
table {
|
||
width: 700px;
|
||
border-collapse: collapse;
|
||
margin: 30px auto;
|
||
border: 1px solid #444444;
|
||
}
|
||
|
||
th,
|
||
td {
|
||
padding: 12px;
|
||
text-align: left;
|
||
}
|
||
|
||
th {
|
||
background-color: var(--main-color);
|
||
color: white;
|
||
font-weight: bold;
|
||
}
|
||
|
||
tr:nth-child(odd) {
|
||
background-color: rgb(239, 239, 239);
|
||
}
|
||
|
||
tr:nth-child(even) {
|
||
background-color: rgb(255, 255, 255);
|
||
}
|
||
|
||
tr:hover {
|
||
background-color: rgb(207, 207, 207);
|
||
}
|
||
|
||
.time {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.team-name {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.link-button {
|
||
/* Основные стили */
|
||
display: inline;
|
||
border: none;
|
||
background: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
font: inherit;
|
||
cursor: pointer;
|
||
color: var(--main-color);
|
||
/* Стандартный цвет ссылки */
|
||
text-decoration: underline;
|
||
font-weight: 600;
|
||
|
||
/* Убираем стандартное оформление кнопки */
|
||
-webkit-appearance: none;
|
||
-moz-appearance: none;
|
||
appearance: none;
|
||
|
||
/* Дополнительные свойства для лучшего отображения */
|
||
line-height: inherit;
|
||
text-align: left;
|
||
}
|
||
|
||
/* Состояние при наведении */
|
||
.link-button:hover {
|
||
color: var(--second-color);
|
||
/* Немного темнее */
|
||
text-decoration: none;
|
||
}
|
||
|
||
/* Состояние при активации (нажатии) */
|
||
.link-button:active {
|
||
color: #003366;
|
||
/* Еще темнее */
|
||
}
|
||
|
||
/* Состояние фокуса (для доступности) */
|
||
.link-button:focus {
|
||
outline: none;
|
||
/* Убираем стандартный outline */
|
||
text-decoration: none;
|
||
box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.3);
|
||
/* Кастомный фокус */
|
||
}
|
||
|
||
.form-block {
|
||
width: 700px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
a {
|
||
/* Основные стили */
|
||
color: var(--second-color);
|
||
text-decoration: none;
|
||
transition: all 0.2s ease;
|
||
cursor: pointer;
|
||
|
||
/* Подчеркивание только при наведении */
|
||
&:hover {
|
||
text-decoration: underline;
|
||
text-decoration-thickness: 2px;
|
||
text-underline-offset: 3px;
|
||
}
|
||
|
||
/* Состояние фокуса для доступности */
|
||
&:focus-visible {
|
||
outline: 2px solid #3182ce;
|
||
outline-offset: 2px;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
/* Отключенное состояние */
|
||
&[disabled] {
|
||
color: #a0aec0;
|
||
pointer-events: none;
|
||
cursor: not-allowed;
|
||
}
|
||
}
|
||
|
||
.button-container {
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.cell-center {
|
||
text-align: center;
|
||
}
|
||
</style>
|