generated from VLADIMIR/template_frontend
up
This commit is contained in:
Vendored
+4
-4
@@ -5,10 +5,10 @@
|
|||||||
<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-D9DHo4Ux.js"></script>
|
<script type="module" crossorigin src="/assets/index-DVruj17R.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/client-Dr2LhGo3.js">
|
<link rel="modulepreload" crossorigin href="/assets/client-CmW03vy2.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/light-DXa4IKh6.js">
|
<link rel="modulepreload" crossorigin href="/assets/light-CU7ioAUB.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/light-4JJV7N1X.js">
|
<link rel="modulepreload" crossorigin href="/assets/light-CBsH9Klj.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CF62QB4P.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CF62QB4P.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NButton, NFlex, NSpace, NText } from 'naive-ui'
|
import { NButton, NFlex, NInput, NInputNumber, NSelect, NSpace, NText } from 'naive-ui'
|
||||||
import { computed, onBeforeUnmount, ref } from 'vue'
|
import { computed, onBeforeUnmount, ref } from 'vue'
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Application,
|
Application,
|
||||||
MapItem,
|
MapItem,
|
||||||
ResultsQuestion,
|
ResultsQuestion,
|
||||||
|
ScreenCommand,
|
||||||
|
ScreenMode,
|
||||||
|
ScreenPlan,
|
||||||
|
ScreenPlanStep,
|
||||||
ScreenState,
|
ScreenState,
|
||||||
Team,
|
Team,
|
||||||
TeamResult,
|
TeamResult,
|
||||||
@@ -18,11 +22,30 @@ const props = defineProps<{
|
|||||||
screens: ScreenState[]
|
screens: ScreenState[]
|
||||||
previewQuestions: ResultsQuestion[]
|
previewQuestions: ResultsQuestion[]
|
||||||
previewTeams: TeamResult[]
|
previewTeams: TeamResult[]
|
||||||
|
screenPlan?: ScreenPlan
|
||||||
|
screenPlanDraft: ScreenPlanStep[]
|
||||||
|
screenPlanDirty: boolean
|
||||||
|
screenPlanSaving: boolean
|
||||||
|
screenPlanMode: 'use' | 'edit'
|
||||||
|
screenVolume: number
|
||||||
|
screenPosition: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
saveItem: [item: MapItem, hidden?: boolean]
|
saveItem: [item: MapItem, hidden?: boolean]
|
||||||
giveApplication: [team: Team, application: Application]
|
giveApplication: [team: Team, application: Application]
|
||||||
|
addStep: []
|
||||||
|
edit: []
|
||||||
|
cancel: []
|
||||||
|
removeStep: [index: number]
|
||||||
|
moveStep: [index: number, offset: number]
|
||||||
|
dirty: []
|
||||||
|
clearPlan: []
|
||||||
|
save: []
|
||||||
|
setStep: [index: number]
|
||||||
|
command: [mode: ScreenMode | undefined, command: ScreenCommand]
|
||||||
|
'update:screenVolume': [value: number | null]
|
||||||
|
'update:screenPosition': [value: number | null]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const mapScale = ref(1)
|
const mapScale = ref(1)
|
||||||
@@ -34,6 +57,16 @@ const teamsNeedingApplications = computed(
|
|||||||
() =>
|
() =>
|
||||||
new Map(props.teams.filter((team) => team.applications?.length).map((team) => [team.ID, team])),
|
new Map(props.teams.filter((team) => team.applications?.length).map((team) => [team.ID, team])),
|
||||||
)
|
)
|
||||||
|
const screenPlanModeOptions = [
|
||||||
|
{ label: 'Обложка', value: 'SCREEN_MODE_COVER' },
|
||||||
|
{ label: 'Введение', value: 'SCREEN_MODE_INTRO' },
|
||||||
|
{ label: 'Успехов', value: 'SCREEN_MODE_INTRO_DONE' },
|
||||||
|
{ label: 'Закрывашка', value: 'SCREEN_MODE_CONCLUSION' },
|
||||||
|
{ label: 'Спасибо за игру', value: 'SCREEN_MODE_CONCLUSION_DONE' },
|
||||||
|
{ label: 'Результаты', value: 'SCREEN_MODE_RESULTS' },
|
||||||
|
{ label: 'Таймер', value: 'SCREEN_MODE_TIMER' },
|
||||||
|
{ label: 'Текст', value: 'SCREEN_MODE_TEXT' },
|
||||||
|
]
|
||||||
let dragState:
|
let dragState:
|
||||||
{ item: MapItem; startX: number; startY: number; itemX: number; itemY: number } | undefined
|
{ item: MapItem; startX: number; startY: number; itemX: number; itemY: number } | undefined
|
||||||
let panState: { startX: number; startY: number; offsetX: number; offsetY: number } | undefined
|
let panState: { startX: number; startY: number; offsetX: number; offsetY: number } | undefined
|
||||||
@@ -211,6 +244,210 @@ onBeforeUnmount(() => {
|
|||||||
:teams="previewTeams"
|
:teams="previewTeams"
|
||||||
/>
|
/>
|
||||||
<div v-else class="screen-preview-placeholder">Экран не подключён</div>
|
<div v-else class="screen-preview-placeholder">Экран не подключён</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="emit('addStep')"
|
||||||
|
>
|
||||||
|
Создать план
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
v-if="screenPlan && screenPlanMode === 'use'"
|
||||||
|
size="small"
|
||||||
|
@click="emit('edit')"
|
||||||
|
>
|
||||||
|
Редактировать
|
||||||
|
</n-button>
|
||||||
|
<template v-if="screenPlanMode === 'edit'">
|
||||||
|
<n-button size="small" :disabled="screenPlanSaving" @click="emit('cancel')">
|
||||||
|
Отменить
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
:loading="screenPlanSaving"
|
||||||
|
:disabled="screenPlanSaving"
|
||||||
|
@click="emit('save')"
|
||||||
|
>
|
||||||
|
Сохранить план
|
||||||
|
</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="index" 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="emit('dirty')"
|
||||||
|
/>
|
||||||
|
<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="emit('dirty')"
|
||||||
|
/>
|
||||||
|
<template v-if="step.mode === 'SCREEN_MODE_TIMER'">
|
||||||
|
<n-input
|
||||||
|
v-model:value="step.timerLabel"
|
||||||
|
:disabled="screenPlanSaving"
|
||||||
|
size="small"
|
||||||
|
placeholder="Надпись таймера"
|
||||||
|
@update:value="emit('dirty')"
|
||||||
|
/>
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="step.timerDurationSeconds"
|
||||||
|
:min="1"
|
||||||
|
:max="86400"
|
||||||
|
:disabled="screenPlanSaving"
|
||||||
|
size="small"
|
||||||
|
placeholder="Секунды"
|
||||||
|
@update:value="emit('dirty')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
:disabled="screenPlanSaving || index === 0"
|
||||||
|
@click="emit('moveStep', index, -1)"
|
||||||
|
>
|
||||||
|
↑
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
:disabled="screenPlanSaving || index === screenPlanDraft.length - 1"
|
||||||
|
@click="emit('moveStep', index, 1)"
|
||||||
|
>
|
||||||
|
↓
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
type="error"
|
||||||
|
ghost
|
||||||
|
:disabled="screenPlanSaving"
|
||||||
|
@click="emit('removeStep', index)"
|
||||||
|
>
|
||||||
|
Удалить
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
<n-space>
|
||||||
|
<n-button size="small" :disabled="screenPlanSaving" @click="emit('addStep')">
|
||||||
|
Добавить шаг
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
v-if="screenPlanDraft.length"
|
||||||
|
size="small"
|
||||||
|
:disabled="screenPlanSaving"
|
||||||
|
@click="emit('clearPlan')"
|
||||||
|
>
|
||||||
|
Удалить план
|
||||||
|
</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="emit('setStep', index)"
|
||||||
|
>
|
||||||
|
Показать
|
||||||
|
</n-button>
|
||||||
|
<template v-if="screenPlan.currentStep === index">
|
||||||
|
<template v-if="step.mode === 'SCREEN_MODE_TIMER'">
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_START')"
|
||||||
|
>
|
||||||
|
Старт
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_PAUSE')"
|
||||||
|
>
|
||||||
|
Пауза
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_RESUME')"
|
||||||
|
>
|
||||||
|
Продолжить
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
@click="emit('command', 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="emit('command', step.mode, 'SCREEN_COMMAND_PLAY')">
|
||||||
|
Запуск
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_PAUSE')">
|
||||||
|
Пауза
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_RESUME')">
|
||||||
|
Продолжить
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_STOP')">
|
||||||
|
Стоп
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_REPLAY')">
|
||||||
|
Повтор
|
||||||
|
</n-button>
|
||||||
|
<n-input-number
|
||||||
|
:value="screenVolume"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:step="5"
|
||||||
|
size="small"
|
||||||
|
@update:value="emit('update:screenVolume', $event)"
|
||||||
|
/>
|
||||||
|
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_VOLUME')">
|
||||||
|
Громкость
|
||||||
|
</n-button>
|
||||||
|
<n-input-number
|
||||||
|
:value="screenPosition"
|
||||||
|
:min="0"
|
||||||
|
:step="5"
|
||||||
|
size="small"
|
||||||
|
@update:value="emit('update:screenPosition', $event)"
|
||||||
|
/>
|
||||||
|
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_SEEK')">
|
||||||
|
Перемотать
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -247,10 +484,11 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.map-field {
|
.map-field {
|
||||||
|
aspect-ratio: 1;
|
||||||
background-color: #151515;
|
background-color: #151515;
|
||||||
border: 1px dashed rgb(255 255 255 / 20%);
|
border: 1px dashed rgb(255 255 255 / 20%);
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
height: 420px;
|
height: auto;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
+145
-119
@@ -12,6 +12,8 @@ import {
|
|||||||
NRate,
|
NRate,
|
||||||
NSelect,
|
NSelect,
|
||||||
NSpace,
|
NSpace,
|
||||||
|
NTabPane,
|
||||||
|
NTabs,
|
||||||
NText,
|
NText,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
@@ -52,6 +54,20 @@ const isDownloadingQR = ref(false)
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const gameId = route.params.id
|
const gameId = route.params.id
|
||||||
|
type GameTab = 'game' | 'teams' | 'screens-map'
|
||||||
|
|
||||||
|
function isGameTab(value: string): value is GameTab {
|
||||||
|
return ['game', 'teams', 'screens-map'].includes(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryTab = typeof route.query.tab === 'string' ? route.query.tab : ''
|
||||||
|
const activeTab = ref<GameTab>(isGameTab(queryTab) ? queryTab : 'game')
|
||||||
|
|
||||||
|
async function setActiveTab(value: string) {
|
||||||
|
if (!isGameTab(value)) return
|
||||||
|
activeTab.value = value
|
||||||
|
await router.replace({ query: { ...route.query, tab: value } })
|
||||||
|
}
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
@@ -680,127 +696,137 @@ useSimplePolling(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="center-block-custom">
|
<div class="center-block-custom">
|
||||||
<div class="width1200">
|
<div class="width1200">
|
||||||
<div class="settings-block">
|
<n-tabs :value="activeTab" type="line" animated @update:value="setActiveTab">
|
||||||
<n-flex justify="space-between">
|
<n-tab-pane name="game" tab="Игра">
|
||||||
<n-space>
|
<div class="settings-block">
|
||||||
<n-tag :bordered="false" type="info"> Участвуют команд: {{ activeTeamCount }} </n-tag>
|
<n-flex justify="space-between">
|
||||||
<n-tag type="info">
|
<n-space>
|
||||||
{{ timeString }}
|
<n-tag :bordered="false" type="info">
|
||||||
</n-tag>
|
Участвуют команд: {{ activeTeamCount }}
|
||||||
<n-tag v-if="formatGameDateTime(game.startAt)" type="info">
|
</n-tag>
|
||||||
Начало: {{ formatGameDateTime(game.startAt) }}
|
<n-tag type="info">
|
||||||
</n-tag>
|
{{ timeString }}
|
||||||
</n-space>
|
</n-tag>
|
||||||
|
<n-tag v-if="formatGameDateTime(game.startAt)" type="info">
|
||||||
|
Начало: {{ formatGameDateTime(game.startAt) }}
|
||||||
|
</n-tag>
|
||||||
|
</n-space>
|
||||||
|
|
||||||
<n-flex justify="end">
|
<n-flex justify="end">
|
||||||
<n-button quaternary @click="openQR(url, 'Админка')"> QR </n-button>
|
<n-button quaternary @click="openQR(url, 'Админка')"> QR </n-button>
|
||||||
<n-button quaternary @click="openIssueTickets()"> Выпустить билеты </n-button>
|
<n-button quaternary @click="openIssueTickets()"> Выпустить билеты </n-button>
|
||||||
<n-button quaternary @click="openAnswersModal()"> Ответы команд </n-button>
|
<n-button quaternary @click="openAnswersModal()"> Ответы команд </n-button>
|
||||||
<n-button v-if="game.status == 'finish'" quaternary @click="toResults()">
|
<n-button v-if="game.status == 'finish'" quaternary @click="toResults()">
|
||||||
<Icon class="settings-icon">
|
<Icon class="settings-icon">
|
||||||
<Trophy />
|
<Trophy />
|
||||||
</Icon>
|
</Icon>
|
||||||
Итоги
|
Итоги
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button
|
<n-button
|
||||||
v-if="activeTeamCount"
|
v-if="activeTeamCount"
|
||||||
quaternary
|
quaternary
|
||||||
:loading="isDownloadingQR"
|
:loading="isDownloadingQR"
|
||||||
:disabled="isDownloadingQR"
|
:disabled="isDownloadingQR"
|
||||||
@click="downloadTeamQR()"
|
@click="downloadTeamQR()"
|
||||||
>
|
>
|
||||||
QR команд
|
QR команд
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button quaternary @click="openSettings()">
|
<n-button quaternary @click="openSettings()">
|
||||||
<Icon class="settings-icon">
|
<Icon class="settings-icon">
|
||||||
<Settings />
|
<Settings />
|
||||||
</Icon>
|
</Icon>
|
||||||
Настройки
|
Настройки
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button
|
<n-button
|
||||||
v-if="(game.teams?.length || 0) == 0"
|
v-if="(game.teams?.length || 0) == 0"
|
||||||
type="error"
|
type="error"
|
||||||
quaternary
|
quaternary
|
||||||
@click="showDeleteGameModal = true"
|
@click="showDeleteGameModal = true"
|
||||||
>
|
>
|
||||||
Удалить игру
|
Удалить игру
|
||||||
</n-button>
|
</n-button>
|
||||||
|
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-button type="info" v-if="game.status != 'draft'" dashed @click="resetGame()">
|
<n-button type="info" v-if="game.status != 'draft'" dashed @click="resetGame()">
|
||||||
<Icon>
|
<Icon>
|
||||||
<Reset />
|
<Reset />
|
||||||
</Icon>
|
</Icon>
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button type="info" v-if="game.status == 'draft'" dashed @click="startGame()">
|
<n-button type="info" v-if="game.status == 'draft'" dashed @click="startGame()">
|
||||||
<Icon>
|
<Icon>
|
||||||
<Play />
|
<Play />
|
||||||
</Icon>
|
</Icon>
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button type="info" v-if="game.status == 'pause'" dashed @click="playGame()">
|
<n-button type="info" v-if="game.status == 'pause'" dashed @click="playGame()">
|
||||||
<Icon>
|
<Icon>
|
||||||
<Play />
|
<Play />
|
||||||
</Icon>
|
</Icon>
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button type="info" v-if="game.status == 'run'" dashed @click="pauseGame()">
|
<n-button type="info" v-if="game.status == 'run'" dashed @click="pauseGame()">
|
||||||
<Icon>
|
<Icon>
|
||||||
<Pause />
|
<Pause />
|
||||||
</Icon>
|
</Icon>
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button type="info" v-if="game.status != 'finish'" dashed @click="finishGame()">
|
<n-button type="info" v-if="game.status != 'finish'" dashed @click="finishGame()">
|
||||||
<Icon>
|
<Icon>
|
||||||
<Flag />
|
<Flag />
|
||||||
</Icon>
|
</Icon>
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</div>
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
<GameScreensPanel
|
<n-tab-pane name="teams" tab="Команды">
|
||||||
:screens="screens"
|
<n-tag :bordered="false" type="info"> Участвуют команд: {{ activeTeamCount }} </n-tag>
|
||||||
:screen-plan="screenPlan"
|
<GameTeamsPanel
|
||||||
:screen-plan-draft="screenPlanDraft"
|
:teams="game.teams || []"
|
||||||
:screen-plan-dirty="screenPlanDirty"
|
:participation-options="participationOptions"
|
||||||
:screen-plan-saving="screenPlanSaving"
|
@add="showAddTeamModal = true"
|
||||||
:screen-plan-mode="screenPlanMode"
|
@open-qr="openTeamQR"
|
||||||
:screen-volume="screenVolume"
|
@preview="(id, password) => toTeamStory(id, password, true)"
|
||||||
:screen-position="screenPosition"
|
@delete="deleteTeam"
|
||||||
:screen-binding="screenBinding"
|
@give-application="giveApplication"
|
||||||
@bind="bindScannedScreen"
|
/>
|
||||||
@set-sound="setSoundScreen"
|
</n-tab-pane>
|
||||||
@unbind="unbindScreen"
|
<n-tab-pane name="screens-map" tab="Экраны и карта">
|
||||||
@add-step="addScreenPlanStep"
|
<GameScreensPanel
|
||||||
@edit="editScreenPlan"
|
:screens="screens"
|
||||||
@cancel="cancelScreenPlanEdit"
|
:screen-binding="screenBinding"
|
||||||
@remove-step="removeScreenPlanStep"
|
@bind="bindScannedScreen"
|
||||||
@move-step="moveScreenPlanStep"
|
@set-sound="setSoundScreen"
|
||||||
@dirty="markScreenPlanDirty"
|
@unbind="unbindScreen"
|
||||||
@clear-plan="clearScreenPlan"
|
/>
|
||||||
@save="saveScreenPlan"
|
<GameMapPanel
|
||||||
@set-step="setScreenPlanStep"
|
:map-items="mapItems"
|
||||||
@command="sendScreenPlanCommand"
|
:teams="game.teams || []"
|
||||||
@update:screen-volume="screenVolume = $event ?? 0"
|
:screens="screens"
|
||||||
@update:screen-position="screenPosition = $event ?? 0"
|
:preview-questions="previewQuestions"
|
||||||
/>
|
:preview-teams="previewTeams"
|
||||||
<GameMapPanel
|
:screen-plan="screenPlan"
|
||||||
:map-items="mapItems"
|
:screen-plan-draft="screenPlanDraft"
|
||||||
:teams="game.teams || []"
|
:screen-plan-dirty="screenPlanDirty"
|
||||||
:screens="screens"
|
:screen-plan-saving="screenPlanSaving"
|
||||||
:preview-questions="previewQuestions"
|
:screen-plan-mode="screenPlanMode"
|
||||||
:preview-teams="previewTeams"
|
:screen-volume="screenVolume"
|
||||||
@save-item="saveMapItemAndReload"
|
:screen-position="screenPosition"
|
||||||
@give-application="giveApplication"
|
@save-item="saveMapItemAndReload"
|
||||||
/>
|
@give-application="giveApplication"
|
||||||
<GameTeamsPanel
|
@add-step="addScreenPlanStep"
|
||||||
:teams="game.teams || []"
|
@edit="editScreenPlan"
|
||||||
:participation-options="participationOptions"
|
@cancel="cancelScreenPlanEdit"
|
||||||
@add="showAddTeamModal = true"
|
@remove-step="removeScreenPlanStep"
|
||||||
@open-qr="openTeamQR"
|
@move-step="moveScreenPlanStep"
|
||||||
@preview="(id, password) => toTeamStory(id, password, true)"
|
@dirty="markScreenPlanDirty"
|
||||||
@delete="deleteTeam"
|
@clear-plan="clearScreenPlan"
|
||||||
@give-application="giveApplication"
|
@save="saveScreenPlan"
|
||||||
/>
|
@set-step="setScreenPlanStep"
|
||||||
|
@command="sendScreenPlanCommand"
|
||||||
|
@update:screen-volume="screenVolume = $event ?? 0"
|
||||||
|
@update:screen-position="screenPosition = $event ?? 0"
|
||||||
|
/>
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import { NAlert, NButton, NFlex, NTag } from 'naive-ui'
|
||||||
NAlert,
|
|
||||||
NButton,
|
|
||||||
NFlex,
|
|
||||||
NInput,
|
|
||||||
NInputNumber,
|
|
||||||
NSelect,
|
|
||||||
NSpace,
|
|
||||||
NTag,
|
|
||||||
NText,
|
|
||||||
} from 'naive-ui'
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import type {
|
import type { ScreenState } from '@/api/generated/crabs/evening_detective_server'
|
||||||
ScreenCommand,
|
|
||||||
ScreenMode,
|
|
||||||
ScreenPlan,
|
|
||||||
ScreenPlanStep,
|
|
||||||
ScreenState,
|
|
||||||
} from '@/api/generated/crabs/evening_detective_server'
|
|
||||||
import ScreenBindingModal from '@/components/ScreenBindingModal.vue'
|
import ScreenBindingModal from '@/components/ScreenBindingModal.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
screens: ScreenState[]
|
screens: ScreenState[]
|
||||||
screenPlan?: ScreenPlan
|
|
||||||
screenPlanDraft: ScreenPlanStep[]
|
|
||||||
screenPlanDirty: boolean
|
|
||||||
screenPlanSaving: boolean
|
|
||||||
screenPlanMode: 'use' | 'edit'
|
|
||||||
screenVolume: number
|
|
||||||
screenPosition: number
|
|
||||||
screenBinding: boolean
|
screenBinding: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -37,32 +14,9 @@ const emit = defineEmits<{
|
|||||||
bind: [screenID: string, accessToken: string]
|
bind: [screenID: string, accessToken: string]
|
||||||
setSound: [screen: ScreenState]
|
setSound: [screen: ScreenState]
|
||||||
unbind: [screen: ScreenState]
|
unbind: [screen: ScreenState]
|
||||||
addStep: []
|
|
||||||
edit: []
|
|
||||||
cancel: []
|
|
||||||
removeStep: [index: number]
|
|
||||||
moveStep: [index: number, offset: number]
|
|
||||||
dirty: []
|
|
||||||
clearPlan: []
|
|
||||||
save: []
|
|
||||||
setStep: [index: number]
|
|
||||||
command: [mode: ScreenMode | undefined, command: ScreenCommand]
|
|
||||||
'update:screenVolume': [value: number | null]
|
|
||||||
'update:screenPosition': [value: number | null]
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const showBindingModal = ref(false)
|
const showBindingModal = ref(false)
|
||||||
|
|
||||||
const screenPlanModeOptions = [
|
|
||||||
{ label: 'Обложка', value: 'SCREEN_MODE_COVER' },
|
|
||||||
{ label: 'Введение', value: 'SCREEN_MODE_INTRO' },
|
|
||||||
{ label: 'Успехов', value: 'SCREEN_MODE_INTRO_DONE' },
|
|
||||||
{ label: 'Закрывашка', value: 'SCREEN_MODE_CONCLUSION' },
|
|
||||||
{ label: 'Спасибо за игру', value: 'SCREEN_MODE_CONCLUSION_DONE' },
|
|
||||||
{ label: 'Результаты', value: 'SCREEN_MODE_RESULTS' },
|
|
||||||
{ label: 'Таймер', value: 'SCREEN_MODE_TIMER' },
|
|
||||||
{ label: 'Текст', value: 'SCREEN_MODE_TEXT' },
|
|
||||||
]
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -93,206 +47,11 @@ const screenPlanModeOptions = [
|
|||||||
<n-flex justify="end">
|
<n-flex justify="end">
|
||||||
<n-tag v-if="screen.soundEnabled" type="success">Со звуком</n-tag>
|
<n-tag v-if="screen.soundEnabled" type="success">Со звуком</n-tag>
|
||||||
<n-button v-else size="small" @click="emit('setSound', screen)">Назначить звук</n-button>
|
<n-button v-else size="small" @click="emit('setSound', screen)">Назначить звук</n-button>
|
||||||
<n-button size="small" type="error" ghost @click="emit('unbind', screen)"
|
<n-button size="small" type="error" ghost @click="emit('unbind', screen)">
|
||||||
>Отвязать</n-button
|
Отвязать
|
||||||
>
|
</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</div>
|
</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="emit('addStep')"
|
|
||||||
>
|
|
||||||
Создать план
|
|
||||||
</n-button>
|
|
||||||
<n-button
|
|
||||||
v-if="screenPlan && screenPlanMode === 'use'"
|
|
||||||
size="small"
|
|
||||||
@click="emit('edit')"
|
|
||||||
>
|
|
||||||
Редактировать
|
|
||||||
</n-button>
|
|
||||||
<template v-if="screenPlanMode === 'edit'">
|
|
||||||
<n-button size="small" :disabled="screenPlanSaving" @click="emit('cancel')"
|
|
||||||
>Отменить</n-button
|
|
||||||
>
|
|
||||||
<n-button
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
:loading="screenPlanSaving"
|
|
||||||
:disabled="screenPlanSaving"
|
|
||||||
@click="emit('save')"
|
|
||||||
>
|
|
||||||
Сохранить план
|
|
||||||
</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="index" 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="emit('dirty')"
|
|
||||||
/>
|
|
||||||
<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="emit('dirty')"
|
|
||||||
/>
|
|
||||||
<template v-if="step.mode === 'SCREEN_MODE_TIMER'">
|
|
||||||
<n-input
|
|
||||||
v-model:value="step.timerLabel"
|
|
||||||
:disabled="screenPlanSaving"
|
|
||||||
size="small"
|
|
||||||
placeholder="Надпись таймера"
|
|
||||||
@update:value="emit('dirty')"
|
|
||||||
/>
|
|
||||||
<n-input-number
|
|
||||||
v-model:value="step.timerDurationSeconds"
|
|
||||||
:min="1"
|
|
||||||
:max="86400"
|
|
||||||
:disabled="screenPlanSaving"
|
|
||||||
size="small"
|
|
||||||
placeholder="Секунды"
|
|
||||||
@update:value="emit('dirty')"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<n-button
|
|
||||||
size="small"
|
|
||||||
:disabled="screenPlanSaving || index === 0"
|
|
||||||
@click="emit('moveStep', index, -1)"
|
|
||||||
>↑</n-button
|
|
||||||
>
|
|
||||||
<n-button
|
|
||||||
size="small"
|
|
||||||
:disabled="screenPlanSaving || index === screenPlanDraft.length - 1"
|
|
||||||
@click="emit('moveStep', index, 1)"
|
|
||||||
>↓</n-button
|
|
||||||
>
|
|
||||||
<n-button
|
|
||||||
size="small"
|
|
||||||
type="error"
|
|
||||||
ghost
|
|
||||||
:disabled="screenPlanSaving"
|
|
||||||
@click="emit('removeStep', index)"
|
|
||||||
>Удалить</n-button
|
|
||||||
>
|
|
||||||
</n-space>
|
|
||||||
<n-space>
|
|
||||||
<n-button size="small" :disabled="screenPlanSaving" @click="emit('addStep')"
|
|
||||||
>Добавить шаг</n-button
|
|
||||||
>
|
|
||||||
<n-button
|
|
||||||
v-if="screenPlanDraft.length"
|
|
||||||
size="small"
|
|
||||||
:disabled="screenPlanSaving"
|
|
||||||
@click="emit('clearPlan')"
|
|
||||||
>Удалить план</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="emit('setStep', index)"
|
|
||||||
>Показать</n-button
|
|
||||||
>
|
|
||||||
<template v-if="screenPlan.currentStep === index">
|
|
||||||
<template v-if="step.mode === 'SCREEN_MODE_TIMER'">
|
|
||||||
<n-button
|
|
||||||
size="small"
|
|
||||||
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_START')"
|
|
||||||
>Старт</n-button
|
|
||||||
>
|
|
||||||
<n-button
|
|
||||||
size="small"
|
|
||||||
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_PAUSE')"
|
|
||||||
>Пауза</n-button
|
|
||||||
>
|
|
||||||
<n-button
|
|
||||||
size="small"
|
|
||||||
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_RESUME')"
|
|
||||||
>Продолжить</n-button
|
|
||||||
>
|
|
||||||
<n-button
|
|
||||||
size="small"
|
|
||||||
@click="emit('command', 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="emit('command', step.mode, 'SCREEN_COMMAND_PLAY')"
|
|
||||||
>Запуск</n-button
|
|
||||||
>
|
|
||||||
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_PAUSE')"
|
|
||||||
>Пауза</n-button
|
|
||||||
>
|
|
||||||
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_RESUME')"
|
|
||||||
>Продолжить</n-button
|
|
||||||
>
|
|
||||||
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_STOP')"
|
|
||||||
>Стоп</n-button
|
|
||||||
>
|
|
||||||
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_REPLAY')"
|
|
||||||
>Повтор</n-button
|
|
||||||
>
|
|
||||||
<n-input-number
|
|
||||||
:value="screenVolume"
|
|
||||||
:min="0"
|
|
||||||
:max="100"
|
|
||||||
:step="5"
|
|
||||||
size="small"
|
|
||||||
@update:value="emit('update:screenVolume', $event)"
|
|
||||||
/>
|
|
||||||
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_VOLUME')"
|
|
||||||
>Громкость</n-button
|
|
||||||
>
|
|
||||||
<n-input-number
|
|
||||||
:value="screenPosition"
|
|
||||||
:min="0"
|
|
||||||
:step="5"
|
|
||||||
size="small"
|
|
||||||
@update:value="emit('update:screenPosition', $event)"
|
|
||||||
/>
|
|
||||||
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_SEEK')"
|
|
||||||
>Перемотать</n-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<ScreenBindingModal
|
<ScreenBindingModal
|
||||||
|
|||||||
Reference in New Issue
Block a user