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