This commit is contained in:
Владимир Фёдоров 2025-06-17 03:04:08 +07:00
parent c52e9a6cc6
commit 7ebae67378
2 changed files with 213 additions and 156 deletions

View File

@ -23,7 +23,18 @@
color: white;
}
.button-custom:hover {
.button-custom-inline {
margin: 10px;
background-color: var(--main-color);
font-weight: 600;
color: white;
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 15px;
font-size: 14px;
}
.button-custom:hover, .button-custom-inline:hover {
background-color: var(--second-color);
}

View File

@ -1,45 +1,45 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { getApiUrl } from './net';
import router from '@/router';
import VueQrcode from '@chenfengyuan/vue-qrcode';
import { onMounted, ref } from 'vue';
import { getApiUrl } from './net';
import router from '@/router';
import VueQrcode from '@chenfengyuan/vue-qrcode';
type Application = {
id: number
name: string
}
type Application = {
id: number
name: string
}
type Team = {
id: number
name: string
password: string
url: string
spendTime: number
applications: Application[]
}
type Team = {
id: number
name: string
password: string
url: string
spendTime: number
applications: Application[]
}
type Teams = {
teams: Team[]
}
type Teams = {
teams: Team[]
}
type Game = {
state: string
startAt: string
endAt: string
}
type Game = {
state: string
startAt: string
endAt: string
}
const qrurl = ref("-")
const qrteam = ref("-")
const qrurl = ref("-")
const qrteam = ref("-")
const gameState = ref("")
const game = ref<Game>()
const gameState = ref("")
const game = ref<Game>()
const teams = ref<Teams>({teams: []})
const teams = ref<Teams>({ teams: [] })
function getTeams() {
fetch(
getApiUrl("/teams")
)
function getTeams() {
fetch(
getApiUrl("/teams")
)
.then(response => response.json())
.then(data => {
teams.value = data
@ -47,62 +47,62 @@
.catch(error => {
console.error('Ошибка:', error)
});
}
}
function gaveApplication(teamId: number, id: number) {
fetch(
getApiUrl("/teams/" + teamId + "/applications"),
{
method: "POST",
body: JSON.stringify({
"applications": [{"id": id}]
})
}
)
.then(() => {})
.catch(error => {
console.error('Ошибка:', error)
});
}
const teamName = ref("")
function addTeam() {
fetch(
getApiUrl("/teams"),
{
method: "POST",
body: JSON.stringify({
"teams": [{"name": teamName.value}]
})
}
)
.then(() => {teamName.value = ""})
.catch(error => {
console.error('Ошибка:', error)
});
}
interface QROptions {
width?: number;
margin?: number;
color?: {
dark: string;
light: string;
};
}
const qrOptions = ref<QROptions>({
width: 100,
margin: 1,
color: {
dark: '#000000',
light: 'f0f0f0'
function gaveApplication(teamId: number, id: number) {
fetch(
getApiUrl("/teams/" + teamId + "/applications"),
{
method: "POST",
body: JSON.stringify({
"applications": [{ "id": id }]
})
}
});
)
.then(() => { })
.catch(error => {
console.error('Ошибка:', error)
});
}
function getGame() {
fetch(
getApiUrl("/game")
)
const teamName = ref("")
function addTeam() {
fetch(
getApiUrl("/teams"),
{
method: "POST",
body: JSON.stringify({
"teams": [{ "name": teamName.value }]
})
}
)
.then(() => { teamName.value = "" })
.catch(error => {
console.error('Ошибка:', error)
});
}
interface QROptions {
width?: number;
margin?: number;
color?: {
dark: string;
light: string;
};
}
const qrOptions = ref<QROptions>({
width: 100,
margin: 1,
color: {
dark: '#000000',
light: 'f0f0f0'
}
});
function getGame() {
fetch(
getApiUrl("/game")
)
.then(response => response.json())
.then(data => {
game.value = data
@ -119,44 +119,77 @@
.catch(error => {
console.error('Ошибка:', error)
});
}
}
function startGame() {
gameState.value = "Загрузка..."
fetch(
getApiUrl("/game/start"),
{method: "POST"}
)
function startGame() {
gameState.value = "Загрузка..."
fetch(
getApiUrl("/game/start"),
{ method: "POST" }
)
.catch(error => {
console.error('Ошибка:', error)
});
}
}
function stopGame() {
gameState.value = "Загрузка..."
fetch(
getApiUrl("/game/stop"),
{method: "POST"}
)
function stopGame() {
gameState.value = "Загрузка..."
fetch(
getApiUrl("/game/stop"),
{ method: "POST" }
)
.catch(error => {
console.error('Ошибка:', error)
});
}
}
let intervalId = 0
onMounted(() => {
function base64ToBytes(base64: string): Uint8Array {
const binaryString = atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
function downloadQrCodesFile() {
fetch(
getApiUrl("/teams/pdf")
)
.then(response => response.json())
.then(data => {
const b = base64ToBytes(data.result)
downloadFile(b, 'teams_qr_code.pdf', 'application/pdf;teams_qr_code.pdf')
})
.catch(error => {
console.error('Ошибка:', error)
});
}
function downloadFile(bytes: Uint8Array, fileName: string, mimeType: string) {
const blob = new Blob([bytes], { type: mimeType });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
link.click();
URL.revokeObjectURL(url);
};
let intervalId = 0
onMounted(() => {
getTeams()
intervalId = setInterval(() => {
getTeams()
getGame()
}, 2000);
intervalId = setInterval(() => {
getTeams()
getGame()
}, 2000);
router.beforeEach((to, from, next) => {
clearInterval(intervalId);
next();
});
router.beforeEach((to, from, next) => {
clearInterval(intervalId);
next();
});
});
</script>
<template>
@ -165,12 +198,7 @@
</div>
<div class="qr">
<VueQrcode
:value="qrurl"
:options="qrOptions"
tag="svg"
class="qr-code"
/>
<VueQrcode :value="qrurl" :options="qrOptions" tag="svg" class="qr-code" />
<div>
{{ qrteam }}
</div>
@ -181,33 +209,34 @@
<a v-on:click="stopGame" class="button-menu">Остановить</a>
</div>
<table class="table-custom">
<table>
<thead>
<tr>
<th></th>
<th>Название команды</th>
<th>Поездки</th>
<th>Приложения</th>
<th>Действия</th>
<th><button v-on:click="downloadQrCodesFile" class="button-custom-inline">Скачать qr-ы</button></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>{{ team.spendTime }}</td>
<td>
<div v-for="application in team.applications" :key="application.id">
{{ application.name }} <button class="link-button" @click="gaveApplication(team.id, application.id)">Выдано</button>
</div>
</td>
<td>
<a v-on:click="qrurl = team.url, qrteam = team.name">QR</a>
</td>
<td>
{{ number + 1 }}
</td>
<td class="team-name">{{ team.name }}
<a :href="team.url" target="_blank">[url]</a>
</td>
<td>{{ team.spendTime }}</td>
<td>
<div v-for="application in team.applications" :key="application.id">
{{ application.name }} <button class="link-button"
@click="gaveApplication(team.id, application.id)">Выдано</button>
</div>
</td>
<td>
<a v-on:click="qrurl = team.url, qrteam = team.name">QR</a>
</td>
</tr>
</tbody>
</table>
@ -229,45 +258,57 @@
<style scoped>
body {
font-family: Arial, sans-serif;
margin: 20px;
font-family: Arial, sans-serif;
margin: 20px;
}
.buttons-block {
padding-top: 20px;
}
.button-menu {
margin: 5px;
}
table {
width: 700px;
border-collapse: collapse;
margin: 30px auto;
border: 1px solid #444444;
width: 700px;
border-collapse: collapse;
margin: 30px auto;
border: 1px solid #444444;
}
th, td {
padding: 12px;
text-align: left;
th,
td {
padding: 12px;
text-align: left;
}
th {
background-color: var(--main-color);
color: white;
font-weight: bold;
background-color: var(--main-color);
color: white;
font-weight: bold;
}
tr:nth-child(odd) {
background-color: rgb(239, 239, 239);
background-color: rgb(239, 239, 239);
}
tr:nth-child(even) {
background-color: rgb(255, 255, 255);
background-color: rgb(255, 255, 255);
}
tr:hover {
background-color: rgb(207, 207, 207);
}
.time {
white-space: nowrap;
white-space: nowrap;
}
.team-name {
font-weight: 600;
}
.link-button {
/* Основные стили */
display: inline;
@ -277,7 +318,8 @@ tr:hover {
margin: 0;
font: inherit;
cursor: pointer;
color: var(--main-color); /* Стандартный цвет ссылки */
color: var(--main-color);
/* Стандартный цвет ссылки */
text-decoration: underline;
font-weight: 600;
@ -293,20 +335,24 @@ tr:hover {
/* Состояние при наведении */
.link-button:hover {
color: var(--second-color); /* Немного темнее */
color: var(--second-color);
/* Немного темнее */
text-decoration: none;
}
/* Состояние при активации (нажатии) */
.link-button:active {
color: #003366; /* Еще темнее */
color: #003366;
/* Еще темнее */
}
/* Состояние фокуса (для доступности) */
.link-button:focus {
outline: none; /* Убираем стандартный outline */
outline: none;
/* Убираем стандартный outline */
text-decoration: none;
box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.3); /* Кастомный фокус */
box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.3);
/* Кастомный фокус */
}
.form-block {