This commit is contained in:
2026-10-04 15:29:17 +07:00
parent 8ea7ba3062
commit 70d1f4956d
2 changed files with 220 additions and 439 deletions
+219 -438
View File
@@ -191,7 +191,9 @@ function isScreenPlanEditing() {
}
async function getScreenPlan() {
console.log("getScreenPlan")
if (isScreenPlanEditing() || screenPlanDirty.value) return
console.log("no skip getScreenPlan")
const requestVersion = ++screenPlanRequestVersion
const res = await client.GetScreenPlan({ gameID: Number(gameId) })
if (
@@ -870,13 +872,8 @@ useSimplePolling(() => {
</Icon>
Итоги
</n-button>
<n-button
v-if="activeTeamCount"
quaternary
:loading="isDownloadingQR"
:disabled="isDownloadingQR"
@click="downloadTeamQR()"
>
<n-button v-if="activeTeamCount" quaternary :loading="isDownloadingQR" :disabled="isDownloadingQR"
@click="downloadTeamQR()">
QR команд
</n-button>
<n-button quaternary @click="openSettings()">
@@ -885,12 +882,7 @@ useSimplePolling(() => {
</Icon>
Настройки
</n-button>
<n-button
v-if="(game.teams?.length || 0) == 0"
type="error"
quaternary
@click="showDeleteGameModal = true"
>
<n-button v-if="(game.teams?.length || 0) == 0" type="error" quaternary @click="showDeleteGameModal = true">
Удалить игру
</n-button>
@@ -951,263 +943,183 @@ useSimplePolling(() => {
</n-button>
</n-flex>
</div>
<div class="screen-plan">
<n-flex align="center" justify="space-between">
<p class="screens-title">План экранов</p>
<n-space>
<n-button v-if="!screenPlan && screenPlanMode === 'use'" size="small" @click="addScreenPlanStep">
Создать план
</n-button>
<template v-if="screenPlan && screenPlanMode === 'use'">
<n-button size="small" @click="editScreenPlan">Редактировать</n-button>
</template>
<template v-if="screenPlanMode === 'edit'">
<n-button size="small" :disabled="screenPlanSaving" @click="cancelScreenPlanEdit">
Отменить
<div class="screen-plan">
<n-flex align="center" justify="space-between">
<p class="screens-title">План экранов</p>
<n-space>
<n-button v-if="!screenPlan && screenPlanMode === 'use'" size="small" @click="addScreenPlanStep">
Создать план
</n-button>
<n-button
size="small"
type="primary"
:loading="screenPlanSaving"
:disabled="screenPlanSaving"
@click="saveScreenPlan"
>
Сохранить план
</n-button>
</template>
</n-space>
</n-flex>
<p v-if="!screenPlan && screenPlanMode === 'use'" class="screen-empty">План экранов ещё не создан.</p>
<template v-else-if="screenPlanMode === 'edit'">
<n-space v-for="(step, index) in screenPlanDraft" :key="getScreenPlanStepKey(step)" align="center" wrap>
<n-text>{{ index + 1 }}</n-text>
<n-select
v-model:value="step.mode"
:options="screenPlanModeOptions"
:disabled="screenPlanSaving"
size="small"
style="width: 180px"
@update:value="markScreenPlanDirty"
/>
<n-input
v-if="step.mode === 'SCREEN_MODE_TEXT'"
v-model:value="step.text"
:disabled="screenPlanSaving"
size="small"
placeholder="Текст экрана"
style="min-width: 240px"
@update:value="markScreenPlanDirty"
/>
<template v-if="step.mode === 'SCREEN_MODE_TIMER'">
<n-input
v-model:value="step.timerLabel"
:disabled="screenPlanSaving"
size="small"
placeholder="Надпись таймера"
@update:value="markScreenPlanDirty"
/>
<n-input-number
v-model:value="step.timerDurationSeconds"
:min="1"
:max="86400"
:disabled="screenPlanSaving"
size="small"
placeholder="Секунды"
@update:value="markScreenPlanDirty"
/>
</template>
<n-button
size="small"
:disabled="screenPlanSaving || index === 0"
@click="moveScreenPlanStep(index, -1)"
>
↑
</n-button>
<n-button
size="small"
:disabled="screenPlanSaving || index === screenPlanDraft.length - 1"
@click="moveScreenPlanStep(index, 1)"
>
↓
</n-button>
<n-button
size="small"
type="error"
ghost
:disabled="screenPlanSaving"
@click="removeScreenPlanStep(index)"
>
Удалить
</n-button>
</n-space>
<n-space>
<n-button size="small" :disabled="screenPlanSaving" @click="addScreenPlanStep">
Добавить шаг
</n-button>
<n-button
v-if="screenPlanDraft.length"
size="small"
:disabled="screenPlanSaving"
@click="screenPlanDraft = []; markScreenPlanDirty()"
>
Удалить план
</n-button>
</n-space>
</template>
<template v-else-if="screenPlan">
<n-space v-for="(step, index) in screenPlan.steps || []" :key="index" align="center" wrap>
<n-text>{{ index + 1 }}</n-text>
<n-text>{{ screenPlanModeOptions.find((option) => option.value === step.mode)?.label }}
<template v-if="step.mode === 'SCREEN_MODE_TEXT'">: {{ step.text }}</template>
<template v-else-if="step.mode === 'SCREEN_MODE_TIMER'">
: {{ step.timerLabel }} ({{ step.timerDurationSeconds }} с)
<template v-if="screenPlan && screenPlanMode === 'use'">
<n-button size="small" @click="editScreenPlan">Редактировать</n-button>
</template>
</n-text>
<n-button
size="small"
:type="screenPlan.currentStep === index ? 'primary' : 'default'"
:disabled="screenPlan.currentStep === index"
@click="setScreenPlanStep(index)"
>
Показать
</n-button>
<template v-if="screenPlan.currentStep === index">
<template v-if="screenPlanMode === 'edit'">
<n-button size="small" :disabled="screenPlanSaving" @click="cancelScreenPlanEdit">
Отменить
</n-button>
<n-button size="small" type="primary" :loading="screenPlanSaving" :disabled="screenPlanSaving"
@click="saveScreenPlan">
Сохранить план
</n-button>
</template>
</n-space>
</n-flex>
<p v-if="!screenPlan && screenPlanMode === 'use'" class="screen-empty">План экранов ещё не создан.</p>
<template v-else-if="screenPlanMode === 'edit'">
<n-space v-for="(step, index) in screenPlanDraft" :key="getScreenPlanStepKey(step)" align="center" wrap>
<n-text>{{ index + 1 }}</n-text>
<n-select v-model:value="step.mode" :options="screenPlanModeOptions" :disabled="screenPlanSaving"
size="small" style="width: 180px" @update:value="markScreenPlanDirty" />
<n-input v-if="step.mode === 'SCREEN_MODE_TEXT'" v-model:value="step.text" :disabled="screenPlanSaving"
size="small" placeholder="Текст экрана" style="min-width: 240px"
@update:value="markScreenPlanDirty" />
<template v-if="step.mode === 'SCREEN_MODE_TIMER'">
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_TIMER_START')">
Старт
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_TIMER_PAUSE')">
Пауза
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_TIMER_RESUME')">
Продолжить
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_TIMER_RESET')">
Сбросить
</n-button>
<n-input v-model:value="step.timerLabel" :disabled="screenPlanSaving" size="small"
placeholder="Надпись таймера" @update:value="markScreenPlanDirty" />
<n-input-number v-model:value="step.timerDurationSeconds" :min="1" :max="86400"
:disabled="screenPlanSaving" size="small" placeholder="Секунды"
@update:value="markScreenPlanDirty" />
</template>
<template v-else-if="step.mode === 'SCREEN_MODE_INTRO' || step.mode === 'SCREEN_MODE_CONCLUSION'">
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_PLAY')">
Запуск
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_PAUSE')">
Пауза
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_RESUME')">
Продолжить
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_STOP')">
Стоп
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_REPLAY')">
Повтор
</n-button>
<n-input-number v-model:value="screenVolume" :min="0" :max="100" :step="5" size="small" />
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_VOLUME')">
Громкость
</n-button>
<n-input-number v-model:value="screenPosition" :min="0" :step="5" size="small" />
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_SEEK')">
Перемотать
</n-button>
<n-button size="small" :disabled="screenPlanSaving || index === 0"
@click="moveScreenPlanStep(index, -1)">
↑
</n-button>
<n-button size="small" :disabled="screenPlanSaving || index === screenPlanDraft.length - 1"
@click="moveScreenPlanStep(index, 1)">
↓
</n-button>
<n-button size="small" type="error" ghost :disabled="screenPlanSaving"
@click="removeScreenPlanStep(index)">
Удалить
</n-button>
</n-space>
<n-space>
<n-button size="small" :disabled="screenPlanSaving" @click="addScreenPlanStep">
Добавить шаг
</n-button>
<n-button v-if="screenPlanDraft.length" size="small" :disabled="screenPlanSaving"
@click="screenPlanDraft = []; markScreenPlanDirty()">
Удалить план
</n-button>
</n-space>
</template>
<template v-else-if="screenPlan">
<n-space v-for="(step, index) in screenPlan.steps || []" :key="index" align="center" wrap>
<n-text>{{ index + 1 }}</n-text>
<n-text>{{screenPlanModeOptions.find((option) => option.value === step.mode)?.label}}
<template v-if="step.mode === 'SCREEN_MODE_TEXT'">: {{ step.text }}</template>
<template v-else-if="step.mode === 'SCREEN_MODE_TIMER'">
: {{ step.timerLabel }} ({{ step.timerDurationSeconds }} с)
</template>
</n-text>
<n-button size="small" :type="screenPlan.currentStep === index ? 'primary' : 'default'"
:disabled="screenPlan.currentStep === index" @click="setScreenPlanStep(index)">
Показать
</n-button>
<template v-if="screenPlan.currentStep === index">
<template v-if="step.mode === 'SCREEN_MODE_TIMER'">
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_TIMER_START')">
Старт
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_TIMER_PAUSE')">
Пауза
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_TIMER_RESUME')">
Продолжить
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_TIMER_RESET')">
Сбросить
</n-button>
</template>
<template v-else-if="step.mode === 'SCREEN_MODE_INTRO' || step.mode === 'SCREEN_MODE_CONCLUSION'">
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_PLAY')">
Запуск
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_PAUSE')">
Пауза
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_RESUME')">
Продолжить
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_STOP')">
Стоп
</n-button>
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_REPLAY')">
Повтор
</n-button>
<n-input-number v-model:value="screenVolume" :min="0" :max="100" :step="5" size="small" />
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_VOLUME')">
Громкость
</n-button>
<n-input-number v-model:value="screenPosition" :min="0" :step="5" size="small" />
<n-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_SEEK')">
Перемотать
</n-button>
</template>
</template>
</template>
</n-space>
</template>
</div>
</n-space>
</template>
</div>
</template>
</div>
<div class="map-preview-layout">
<div class="map-block">
<n-flex align="center" justify="space-between">
<p class="screens-title">Карта помещения</p>
<n-space>
<n-button size="small" @click="changeMapScale(-0.1)">−</n-button>
<n-text>{{ Math.round(mapScale * 100) }}%</n-text>
<n-button size="small" @click="changeMapScale(0.1)">+</n-button>
<n-button size="small" @click="resetMapView">Сбросить</n-button>
</n-space>
</n-flex>
<div class="map-field" @pointerdown="startMapPan" @wheel="zoomMap">
<div
class="map-canvas"
:style="{
<div class="map-block">
<n-flex align="center" justify="space-between">
<p class="screens-title">Карта помещения</p>
<n-space>
<n-button size="small" @click="changeMapScale(-0.1)">−</n-button>
<n-text>{{ Math.round(mapScale * 100) }}%</n-text>
<n-button size="small" @click="changeMapScale(0.1)">+</n-button>
<n-button size="small" @click="resetMapView">Сбросить</n-button>
</n-space>
</n-flex>
<div class="map-field" @pointerdown="startMapPan" @wheel="zoomMap">
<div class="map-canvas" :style="{
transform: `translate(${mapOffset.x}px, ${mapOffset.y}px) scale(${mapScale})`,
}"
>
<div
v-for="item in mapItems"
v-show="!item.hidden"
:key="`${item.kind}-${item.itemID}`"
class="map-item"
:class="[
item.kind === 'screen'
? 'map-screen'
: item.kind === 'organizer_desk'
? 'map-organizer-desk'
: 'map-team',
{
'map-team-needs-applications':
item.kind === 'team' && teamsNeedingApplications.has(item.itemID),
},
]"
:style="{ left: `${item.x || 0}px`, top: `${item.y || 0}px` }"
@pointerdown.stop="item.kind !== 'team' && startMapDrag(item, $event)"
>
<div
v-if="item.kind === 'team'"
class="map-team-handle"
@pointerdown.stop="startMapDrag(item, $event)"
/>
<span>{{ item.label }}</span>
<n-space
v-if="item.kind === 'team' && teamsNeedingApplications.has(item.itemID)"
class="map-team-applications"
vertical
>
<n-button
v-for="application in teamsNeedingApplications.get(item.itemID)?.applications"
:key="application.name"
size="small"
@click="giveApplication(teamsNeedingApplications.get(item.itemID)!, application)"
>
Выдать: {{ application.name }}
</n-button>
</n-space>
<small v-if="item.kind === 'screen'"
>{{ item.status }}{{ item.soundEnabled ? ' · звук' : '' }}</small
>
<button
class="map-item-hide"
type="button"
@pointerdown.stop
@click.stop="toggleMapItem(item)"
>
×
</button>
}">
<div v-for="item in mapItems" v-show="!item.hidden" :key="`${item.kind}-${item.itemID}`" class="map-item"
:class="[
item.kind === 'screen'
? 'map-screen'
: item.kind === 'organizer_desk'
? 'map-organizer-desk'
: 'map-team',
{
'map-team-needs-applications':
item.kind === 'team' && teamsNeedingApplications.has(item.itemID),
},
]" :style="{ left: `${item.x || 0}px`, top: `${item.y || 0}px` }"
@pointerdown.stop="item.kind !== 'team' && startMapDrag(item, $event)">
<div v-if="item.kind === 'team'" class="map-team-handle"
@pointerdown.stop="startMapDrag(item, $event)" />
<span>{{ item.label }}</span>
<n-space v-if="item.kind === 'team' && teamsNeedingApplications.has(item.itemID)"
class="map-team-applications" vertical>
<n-button v-for="application in teamsNeedingApplications.get(item.itemID)?.applications"
:key="application.name" size="small"
@click="giveApplication(teamsNeedingApplications.get(item.itemID)!, application)">
Выдать: {{ application.name }}
</n-button>
</n-space>
<small v-if="item.kind === 'screen'">{{ item.status }}{{ item.soundEnabled ? ' · звук' : '' }}</small>
<button class="map-item-hide" type="button" @pointerdown.stop @click.stop="toggleMapItem(item)">
×
</button>
</div>
</div>
</div>
<n-space v-if="mapItems.some((item) => item.hidden)" class="map-hidden-items">
<n-button v-for="item in mapItems.filter((item) => item.hidden)" :key="`hidden-${item.kind}-${item.itemID}`"
size="small" @click="toggleMapItem(item)">
Добавить {{ item.label }}
</n-button>
</n-space>
</div>
<n-space v-if="mapItems.some((item) => item.hidden)" class="map-hidden-items">
<n-button
v-for="item in mapItems.filter((item) => item.hidden)"
:key="`hidden-${item.kind}-${item.itemID}`"
size="small"
@click="toggleMapItem(item)"
>
Добавить {{ item.label }}
</n-button>
</n-space>
</div>
<aside class="screen-preview-panel">
<p class="screens-title">Превью экрана</p>
<ScreenPreview
v-if="previewScreen?.status === 'connected'"
:state="previewScreen"
:questions="previewQuestions"
:teams="previewTeams"
/>
<ScreenPreview v-if="previewScreen?.status === 'connected'" :state="previewScreen"
:questions="previewQuestions" :teams="previewTeams" />
<div v-else class="screen-preview-placeholder">Экран не подключён</div>
</aside>
</div>
@@ -1224,8 +1136,7 @@ useSimplePolling(() => {
{{ team.name }}
<small v-if="team.captainEmail"> — {{ team.captainEmail }}</small>
<small v-if="team.participantsCount">
· Участников: {{ team.participantsCount }}</small
>
· Участников: {{ team.participantsCount }}</small>
</span>
<div v-if="team.captainEmail || team.participationStatus !== 'pending'">
{{
@@ -1244,28 +1155,20 @@ useSimplePolling(() => {
</div>
<n-flex justify="end">
<n-button
v-if="team.participationStatus !== 'cancelled'"
@click="openQR(getTeamStoryURL(team.ID!, team.password!), 'Подсмотреть игру')"
>
<n-button v-if="team.participationStatus !== 'cancelled'"
@click="openQR(getTeamStoryURL(team.ID!, team.password!), 'Подсмотреть игру')">
QR
</n-button>
<n-button
v-if="team.participationStatus !== 'cancelled'"
@click="toTeamStory(team.ID!, team.password!, true)"
>
<n-button v-if="team.participationStatus !== 'cancelled'"
@click="toTeamStory(team.ID!, team.password!, true)">
Подсмотреть игру
</n-button>
<n-button type="error" ghost @click="deleteTeam(team)"> Удалить </n-button>
</n-flex>
</n-flex>
<n-button
v-for="application in team.applications"
:key="application.name"
class="link-button"
@click="giveApplication(team, application)"
>
<n-button v-for="application in team.applications" :key="application.name" class="link-button"
@click="giveApplication(team, application)">
Выдать: {{ application.name }}
</n-button>
</div>
@@ -1274,14 +1177,8 @@ useSimplePolling(() => {
</div>
<n-modal v-model:show="showScreenScanner" @after-leave="stopScreenScanner">
<n-card
style="width: min(600px, 94vw)"
title="Привязать экран"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<n-card style="width: min(600px, 94vw)" title="Привязать экран" :bordered="false" size="huge" role="dialog"
aria-modal="true">
<template #header-extra>
<n-button @click="closeScreenScanner"> x </n-button>
</template>
@@ -1299,48 +1196,25 @@ useSimplePolling(() => {
<!-- Окно настроек -->
<n-modal v-model:show="showSettingsModal">
<n-card
style="width: 600px"
title="Настройки игры"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<n-card style="width: 600px" title="Настройки игры" :bordered="false" size="huge" role="dialog" aria-modal="true">
<template #header-extra>
<n-button @click="closeSettings()"> x </n-button>
</template>
<n-space vertical>
<p>Название</p>
<n-input
v-model:value="settingsDraft.name"
type="text"
placeholder='Дело №0 "Следствие ведут овечки"'
/>
<n-input v-model:value="settingsDraft.name" type="text" placeholder='Дело №0 "Следствие ведут овечки"' />
<p class="settings-header">Формат игры</p>
<n-select
v-model:value="settingsDraft.format"
:options="[
{ label: 'Online', value: 'online' },
{ label: 'Offline', value: 'offline' },
]"
aria-label="Формат игры"
/>
<n-select v-model:value="settingsDraft.format" :options="[
{ label: 'Online', value: 'online' },
{ label: 'Offline', value: 'offline' },
]" aria-label="Формат игры" />
<p class="settings-header">Описание</p>
<n-input
v-model:value="settingsDraft.description"
type="textarea"
placeholder="Убийство пастуха..."
/>
<n-input v-model:value="settingsDraft.description" type="textarea" placeholder="Убийство пастуха..." />
<p class="settings-header">Место проведения</p>
<n-input
v-model:value="settingsDraft.location"
type="text"
placeholder="Оставьте пустым для онлайн-игры"
/>
<n-input v-model:value="settingsDraft.location" type="text" placeholder="Оставьте пустым для онлайн-игры" />
</n-space>
<template #footer>
<n-flex justify="end">
@@ -1352,14 +1226,7 @@ useSimplePolling(() => {
<!-- Окно выдачи улик -->
<n-modal v-model:show="showGiveApplicationModal">
<n-card
style="width: 600px"
title="Настройки игры"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<n-card style="width: 600px" title="Настройки игры" :bordered="false" size="huge" role="dialog" aria-modal="true">
<template #header-extra>
<n-button @click="showGiveApplicationModal = false"> x </n-button>
</template>
@@ -1370,38 +1237,22 @@ useSimplePolling(() => {
<p>Улика</p>
<h3>{{ giveApplicationApplication.name }}</h3>
<div
v-if="
applicationFileType(giveApplicationApplication) == 'image' &&
giveApplicationApplication.image
"
class="give-application-image-block"
>
<img
:src="applicationFileURL(giveApplicationApplication.image)"
class="give-application-image"
/>
<div v-if="
applicationFileType(giveApplicationApplication) == 'image' &&
giveApplicationApplication.image
" class="give-application-image-block">
<img :src="applicationFileURL(giveApplicationApplication.image)" class="give-application-image" />
</div>
<audio
v-else-if="
applicationFileType(giveApplicationApplication) == 'audio' &&
giveApplicationApplication.image
"
:src="applicationFileURL(giveApplicationApplication.image)"
controls
preload="none"
class="give-application-audio"
></audio>
<a
v-else-if="
applicationFileType(giveApplicationApplication) == 'pdf' &&
giveApplicationApplication.image
"
:href="applicationFileURL(giveApplicationApplication.image)"
target="_blank"
rel="noopener"
class="give-application-pdf-link"
>
<audio v-else-if="
applicationFileType(giveApplicationApplication) == 'audio' &&
giveApplicationApplication.image
" :src="applicationFileURL(giveApplicationApplication.image)" controls preload="none"
class="give-application-audio"></audio>
<a v-else-if="
applicationFileType(giveApplicationApplication) == 'pdf' &&
giveApplicationApplication.image
" :href="applicationFileURL(giveApplicationApplication.image)" target="_blank" rel="noopener"
class="give-application-pdf-link">
PDF
</a>
</n-space>
@@ -1415,33 +1266,16 @@ useSimplePolling(() => {
<!-- Окно создания команды -->
<n-modal v-model:show="showAddTeamModal">
<n-card
style="width: 600px"
title="Создание команды"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<n-card style="width: 600px" title="Создание команды" :bordered="false" size="huge" role="dialog" aria-modal="true">
<template #header-extra>
<n-button @click="showAddTeamModal = false"> x </n-button>
</template>
<n-space vertical>
<n-input v-model:value="newTeamName" type="text" placeholder="Три дебила" />
<n-input
v-model:value="newTeamEmail"
:input-props="{ type: 'email', autocomplete: 'email' }"
placeholder="Почта капитана (необязательно)"
aria-label="Почта капитана (необязательно)"
/>
<n-input-number
v-model:value="newTeamParticipants"
:min="1"
:max="8"
:precision="0"
placeholder="Количество игроков"
aria-label="Количество игроков"
/>
<n-input v-model:value="newTeamEmail" :input-props="{ type: 'email', autocomplete: 'email' }"
placeholder="Почта капитана (необязательно)" aria-label="Почта капитана (необязательно)" />
<n-input-number v-model:value="newTeamParticipants" :min="1" :max="8" :precision="0"
placeholder="Количество игроков" aria-label="Количество игроков" />
</n-space>
<template #footer>
<n-flex justify="end">
@@ -1453,14 +1287,7 @@ useSimplePolling(() => {
<!-- Окно создания команды -->
<n-modal v-model:show="showDeleteTeamModal">
<n-card
style="width: 600px"
title="Удаление команды"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<n-card style="width: 600px" title="Удаление команды" :bordered="false" size="huge" role="dialog" aria-modal="true">
<template #header-extra>
<n-button @click="showDeleteTeamModal = false"> x </n-button>
</template>
@@ -1484,14 +1311,7 @@ useSimplePolling(() => {
<!-- Окно удаления игры -->
<n-modal v-model:show="showDeleteGameModal">
<n-card
style="width: 600px"
title="Удаление игры"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<n-card style="width: 600px" title="Удаление игры" :bordered="false" size="huge" role="dialog" aria-modal="true">
<template #header-extra>
<n-button @click="showDeleteGameModal = false"> x </n-button>
</template>
@@ -1500,11 +1320,7 @@ useSimplePolling(() => {
Введите название игры
<n-text class="name-text" strong>{{ game.name }}</n-text> чтобы удалить её
</p>
<n-input
v-model:value="deletedGameName"
type="text"
placeholder='Дело №0 "Следствие ведут овечки"'
/>
<n-input v-model:value="deletedGameName" type="text" placeholder='Дело №0 "Следствие ведут овечки"' />
</n-space>
<template #footer>
@@ -1519,25 +1335,14 @@ useSimplePolling(() => {
<!-- Окно qr -->
<n-modal v-model:show="showIssueTicketsModal">
<n-card
style="width: min(94vw, 700px)"
title="Выпуск билетов"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<n-card style="width: min(94vw, 700px)" title="Выпуск билетов" :bordered="false" size="huge" role="dialog"
aria-modal="true">
<template #header-extra>
<n-button @click="showIssueTicketsModal = false"> x </n-button>
</template>
<n-space vertical>
<n-input-number v-model:value="ticketCount" :min="1" :max="100" />
<n-button
type="primary"
:loading="isIssuingTickets"
:disabled="isIssuingTickets"
@click="issueTickets()"
>
<n-button type="primary" :loading="isIssuingTickets" :disabled="isIssuingTickets" @click="issueTickets()">
Выпустить
</n-button>
<div v-for="ticket in issuedTickets" :key="ticket.ID" class="issued-ticket">
@@ -1552,14 +1357,7 @@ useSimplePolling(() => {
</n-modal>
<n-modal v-model:show="showQR">
<n-card
style="width: 600px"
:title="titleQR"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<n-card style="width: 600px" :title="titleQR" :bordered="false" size="huge" role="dialog" aria-modal="true">
<template #header-extra>
<n-button @click="showQR = false"> x </n-button>
</template>
@@ -1575,15 +1373,8 @@ useSimplePolling(() => {
<!-- Окно ответов команд и оценки -->
<n-modal v-model:show="showAnswersModal">
<n-card
style="width: min(94vw, 900px)"
title="Ответы команд"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
:content-style="{ maxHeight: '70vh', overflow: 'auto' }"
>
<n-card style="width: min(94vw, 900px)" title="Ответы команд" :bordered="false" size="huge" role="dialog"
aria-modal="true" :content-style="{ maxHeight: '70vh', overflow: 'auto' }">
<template #header-extra>
<n-button @click="showAnswersModal = false"> x </n-button>
</template>
@@ -1602,12 +1393,9 @@ useSimplePolling(() => {
<div class="answers-team-answer">
{{ teamAnswer(team, question.code) || '—' }}
</div>
<n-input-number
:value="scores[scoreKey(team.team?.ID, question.code)]"
@update:value="onScoreUpdate(scoreKey(team.team?.ID, question.code), $event)"
placeholder="Балл"
class="answers-score-input"
/>
<n-input-number :value="scores[scoreKey(team.team?.ID, question.code)]"
@update:value="onScoreUpdate(scoreKey(team.team?.ID, question.code), $event)" placeholder="Балл"
class="answers-score-input" />
</div>
</div>
<template #footer>
@@ -1621,15 +1409,8 @@ useSimplePolling(() => {
</n-modal>
<n-modal v-model:show="showReviewsModal">
<n-card
style="width: min(94vw, 800px)"
title="Отзывы игры"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
:content-style="{ maxHeight: '70vh', overflow: 'auto' }"
>
<n-card style="width: min(94vw, 800px)" title="Отзывы игры" :bordered="false" size="huge" role="dialog"
aria-modal="true" :content-style="{ maxHeight: '70vh', overflow: 'auto' }">
<template #header-extra>
<n-button @click="showReviewsModal = false"> x </n-button>
</template>