generated from VLADIMIR/template_frontend
up
This commit is contained in:
Vendored
+1
-1
@@ -5,7 +5,7 @@
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Вечерний детектив</title>
|
||||
<script type="module" crossorigin src="/assets/index-VgViCwpY.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-Bj8g3wkx.js"></script>
|
||||
<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-4JJV7N1X.js">
|
||||
|
||||
+48
-176
@@ -112,21 +112,13 @@ const screenPlan = ref<ScreenPlan>()
|
||||
const screenPlanDraft = ref<ScreenPlanStep[]>([])
|
||||
const screenPlanDirty = ref(false)
|
||||
const screenPlanSaving = ref(false)
|
||||
const screenPlanMode = ref<'use' | 'edit'>('use')
|
||||
const previewQuestions = ref<ResultsQuestion[]>([])
|
||||
const previewTeams = ref<TeamResult[]>([])
|
||||
const mapItems = ref<MapItem[]>([])
|
||||
const mapSaving = ref(false)
|
||||
const screenVolume = ref(100)
|
||||
const screenPosition = ref(0)
|
||||
const timerDurationMinutes = ref<number | null>(10)
|
||||
const timerLabel = ref('Перерыв')
|
||||
const timerFormInitialized = ref(false)
|
||||
const currentScreenMode = computed<ScreenMode | undefined>(() => {
|
||||
const modes = screens.value.map((screen) => screen.mode).filter(Boolean)
|
||||
if (!modes.length || modes.some((mode) => mode !== modes[0])) return undefined
|
||||
return modes[0] as ScreenMode
|
||||
})
|
||||
const timerScreen = computed(() => screens.value[0])
|
||||
const previewScreen = computed(() => screens.value.find((screen) => screen.status === 'connected') || screens.value[0])
|
||||
const hasDisconnectedScreen = computed(() => screens.value.some((screen) => screen.status === 'disconnected'))
|
||||
const screenPlanModeOptions = [
|
||||
@@ -139,22 +131,6 @@ const screenPlanModeOptions = [
|
||||
{ label: 'Таймер', value: 'SCREEN_MODE_TIMER' },
|
||||
{ label: 'Текст', value: 'SCREEN_MODE_TEXT' },
|
||||
]
|
||||
const timerStateLabel = computed(() => {
|
||||
switch (timerScreen.value?.timerState) {
|
||||
case 'SCREEN_TIMER_STATE_RUNNING':
|
||||
return 'Идёт'
|
||||
case 'SCREEN_TIMER_STATE_PAUSED':
|
||||
return 'Пауза'
|
||||
case 'SCREEN_TIMER_STATE_FINISHED':
|
||||
return 'Завершён'
|
||||
default:
|
||||
return 'Остановлен'
|
||||
}
|
||||
})
|
||||
const timerRemainingLabel = computed(() => {
|
||||
const seconds = Math.max(0, timerScreen.value?.timerRemainingSeconds || 0)
|
||||
return `${String(Math.floor(seconds / 60)).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`
|
||||
})
|
||||
const mapScale = ref(1)
|
||||
const mapOffset = ref({ x: 0, y: 0 })
|
||||
let dragState:
|
||||
@@ -196,11 +172,6 @@ getGame(Number(gameId))
|
||||
async function getGameScreens() {
|
||||
const res = await client.GetGameScreens({ gameID: Number(gameId) })
|
||||
screens.value = res.screens || []
|
||||
if (!timerFormInitialized.value && screens.value[0]) {
|
||||
timerDurationMinutes.value = Math.max(1, (screens.value[0].timerDurationSeconds || 600) / 60)
|
||||
timerLabel.value = screens.value[0].timerLabel || 'Перерыв'
|
||||
timerFormInitialized.value = true
|
||||
}
|
||||
if (previewScreen.value?.mode === 'SCREEN_MODE_RESULTS') {
|
||||
const result = await client.GetGameResults({ gameID: Number(gameId) })
|
||||
previewQuestions.value = result.questions || []
|
||||
@@ -229,6 +200,20 @@ function addScreenPlanStep() {
|
||||
timerDurationSeconds: 600,
|
||||
})
|
||||
screenPlanDirty.value = true
|
||||
screenPlanMode.value = 'edit'
|
||||
}
|
||||
|
||||
function editScreenPlan() {
|
||||
if (!screenPlan.value) return
|
||||
screenPlanDraft.value = screenPlan.value.steps?.map((step) => ({ ...step })) || []
|
||||
screenPlanMode.value = 'edit'
|
||||
}
|
||||
|
||||
function cancelScreenPlanEdit() {
|
||||
if (screenPlanDirty.value && !window.confirm('Отменить несохранённые изменения плана?')) return
|
||||
screenPlanDraft.value = screenPlan.value?.steps?.map((step) => ({ ...step })) || []
|
||||
screenPlanDirty.value = false
|
||||
screenPlanMode.value = 'use'
|
||||
}
|
||||
|
||||
function removeScreenPlanStep(index: number) {
|
||||
@@ -271,7 +256,7 @@ async function saveScreenPlan() {
|
||||
screenPlan.value = res.plan
|
||||
screenPlanDraft.value = res.plan?.steps?.map((step) => ({ ...step })) || []
|
||||
screenPlanDirty.value = false
|
||||
await getGameScreens()
|
||||
screenPlanMode.value = 'use'
|
||||
message.success(res.plan ? 'План экранов сохранён' : 'План экранов удалён')
|
||||
} catch (err) {
|
||||
message.error(err instanceof Error ? err.message : 'Не удалось сохранить план экранов')
|
||||
@@ -502,37 +487,6 @@ async function unbindScreen(screen: ScreenState) {
|
||||
await getGameScreens()
|
||||
}
|
||||
|
||||
async function setScreenMode(mode: ScreenMode) {
|
||||
await client.SetScreenMode({ gameID: Number(gameId), mode })
|
||||
}
|
||||
|
||||
async function configureScreenTimer() {
|
||||
const durationMinutes = timerDurationMinutes.value
|
||||
const label = timerLabel.value.trim()
|
||||
if (!durationMinutes || durationMinutes <= 0 || !label) {
|
||||
message.error('Укажите длительность и надпись таймера')
|
||||
return
|
||||
}
|
||||
await client.SetScreenTimer({
|
||||
gameID: Number(gameId),
|
||||
durationSeconds: Math.round(durationMinutes * 60),
|
||||
label,
|
||||
})
|
||||
await getGameScreens()
|
||||
}
|
||||
|
||||
async function sendScreenCommand(command: ScreenCommand) {
|
||||
if (command === 'SCREEN_COMMAND_PLAY' || command === 'SCREEN_COMMAND_REPLAY') {
|
||||
if (!window.confirm('Запустить медиа на всех экранах?')) return
|
||||
}
|
||||
await client.SendScreenCommand({
|
||||
gameID: Number(gameId),
|
||||
command,
|
||||
positionMs: Math.max(0, Math.round(screenPosition.value * 1000)),
|
||||
volume: Math.max(0, Math.min(100, Math.round(screenVolume.value))),
|
||||
})
|
||||
}
|
||||
|
||||
async function toTeamStory(id: number, password: string, newTab: boolean = false) {
|
||||
const uri = '/team-story/' + id + '?password=' + password
|
||||
if (newTab) {
|
||||
@@ -980,115 +934,26 @@ useSimplePolling(() => {
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</div>
|
||||
<template v-if="!screenPlan && !screenPlanDirty">
|
||||
<n-space class="screen-mode-controls">
|
||||
<n-button
|
||||
size="small"
|
||||
:type="currentScreenMode === 'SCREEN_MODE_COVER' ? 'primary' : 'default'"
|
||||
@click="setScreenMode('SCREEN_MODE_COVER')"
|
||||
>Обложка</n-button
|
||||
>
|
||||
<n-button
|
||||
size="small"
|
||||
:type="currentScreenMode === 'SCREEN_MODE_INTRO' ? 'primary' : 'default'"
|
||||
@click="setScreenMode('SCREEN_MODE_INTRO')"
|
||||
>Введение</n-button
|
||||
>
|
||||
<n-button
|
||||
size="small"
|
||||
:type="currentScreenMode === 'SCREEN_MODE_INTRO_DONE' ? 'primary' : 'default'"
|
||||
@click="setScreenMode('SCREEN_MODE_INTRO_DONE')"
|
||||
>Успехов</n-button
|
||||
>
|
||||
<n-button
|
||||
size="small"
|
||||
:type="currentScreenMode === 'SCREEN_MODE_CONCLUSION' ? 'primary' : 'default'"
|
||||
@click="setScreenMode('SCREEN_MODE_CONCLUSION')"
|
||||
>Закрывашка</n-button
|
||||
>
|
||||
<n-button
|
||||
size="small"
|
||||
:type="currentScreenMode === 'SCREEN_MODE_CONCLUSION_DONE' ? 'primary' : 'default'"
|
||||
@click="setScreenMode('SCREEN_MODE_CONCLUSION_DONE')"
|
||||
>Спасибо за игру!</n-button
|
||||
>
|
||||
<n-button
|
||||
v-if="game.status === 'finish'"
|
||||
size="small"
|
||||
:type="currentScreenMode === 'SCREEN_MODE_RESULTS' ? 'primary' : 'default'"
|
||||
@click="setScreenMode('SCREEN_MODE_RESULTS')"
|
||||
>Результаты</n-button
|
||||
>
|
||||
<n-button
|
||||
:type="currentScreenMode === 'SCREEN_MODE_TIMER' ? 'primary' : 'default'"
|
||||
@click="setScreenMode('SCREEN_MODE_TIMER')"
|
||||
>Таймер</n-button
|
||||
>
|
||||
</n-space>
|
||||
<n-space class="screen-mode-controls" align="center">
|
||||
<n-input-number
|
||||
v-model:value="timerDurationMinutes"
|
||||
:min="1"
|
||||
:max="1440"
|
||||
:step="1"
|
||||
size="small"
|
||||
placeholder="Минуты"
|
||||
/>
|
||||
<n-input v-model:value="timerLabel" size="small" placeholder="Надпись таймера" />
|
||||
<n-button size="small" @click="configureScreenTimer">Настроить таймер</n-button>
|
||||
<n-tag type="info">{{ timerStateLabel }} · {{ timerRemainingLabel }}</n-tag>
|
||||
</n-space>
|
||||
<n-space class="screen-mode-controls">
|
||||
<n-button size="small" @click="sendScreenCommand('SCREEN_COMMAND_TIMER_START')">Старт таймера</n-button>
|
||||
<n-button size="small" @click="sendScreenCommand('SCREEN_COMMAND_TIMER_PAUSE')">Пауза таймера</n-button>
|
||||
<n-button size="small" @click="sendScreenCommand('SCREEN_COMMAND_TIMER_RESUME')"
|
||||
>Продолжить таймер</n-button
|
||||
>
|
||||
<n-button size="small" @click="sendScreenCommand('SCREEN_COMMAND_TIMER_RESET')">Сбросить таймер</n-button>
|
||||
</n-space>
|
||||
<n-space class="screen-mode-controls">
|
||||
<n-button size="small" @click="sendScreenCommand('SCREEN_COMMAND_PLAY')">Запуск</n-button>
|
||||
<n-button size="small" @click="sendScreenCommand('SCREEN_COMMAND_PAUSE')">Пауза</n-button>
|
||||
<n-button size="small" @click="sendScreenCommand('SCREEN_COMMAND_RESUME')"
|
||||
>Продолжить</n-button
|
||||
>
|
||||
<n-button size="small" @click="sendScreenCommand('SCREEN_COMMAND_STOP')">Стоп</n-button>
|
||||
<n-button size="small" @click="sendScreenCommand('SCREEN_COMMAND_REPLAY')"
|
||||
>Повтор</n-button
|
||||
>
|
||||
</n-space>
|
||||
<n-space class="screen-mode-controls" align="center">
|
||||
<n-text>Громкость</n-text>
|
||||
<n-input-number v-model:value="screenVolume" :min="0" :max="100" :step="5" size="small" />
|
||||
<n-button size="small" @click="sendScreenCommand('SCREEN_COMMAND_VOLUME')"
|
||||
>Применить</n-button
|
||||
>
|
||||
<n-text>Позиция, сек.</n-text>
|
||||
<n-input-number v-model:value="screenPosition" :min="0" :step="5" size="small" />
|
||||
<n-button size="small" @click="sendScreenCommand('SCREEN_COMMAND_SEEK')"
|
||||
>Перемотать</n-button
|
||||
>
|
||||
</n-space>
|
||||
</template>
|
||||
<div class="screen-plan">
|
||||
<n-flex align="center" justify="space-between">
|
||||
<p class="screens-title">План экранов</p>
|
||||
<n-space>
|
||||
<n-button v-if="!screenPlan && !screenPlanDirty" size="small" @click="addScreenPlanStep">
|
||||
<n-button v-if="!screenPlan && screenPlanMode === 'use'" size="small" @click="addScreenPlanStep">
|
||||
Создать план
|
||||
</n-button>
|
||||
<n-button
|
||||
v-if="screenPlan || screenPlanDirty"
|
||||
size="small"
|
||||
type="primary"
|
||||
:loading="screenPlanSaving"
|
||||
@click="saveScreenPlan"
|
||||
>
|
||||
Сохранить план
|
||||
</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" @click="cancelScreenPlanEdit">Отменить</n-button>
|
||||
<n-button size="small" type="primary" :loading="screenPlanSaving" @click="saveScreenPlan">
|
||||
Сохранить план
|
||||
</n-button>
|
||||
</template>
|
||||
</n-space>
|
||||
</n-flex>
|
||||
<template v-if="screenPlan || screenPlanDirty">
|
||||
<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
|
||||
@@ -1133,15 +998,32 @@ useSimplePolling(() => {
|
||||
<n-button size="small" type="error" ghost @click="removeScreenPlanStep(index)">
|
||||
Удалить
|
||||
</n-button>
|
||||
</n-space>
|
||||
<n-space>
|
||||
<n-button size="small" @click="addScreenPlanStep">Добавить шаг</n-button>
|
||||
<n-button v-if="screenPlanDraft.length" size="small" @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="screenPlanDirty || screenPlan?.currentStep === index"
|
||||
:type="screenPlan.currentStep === index ? 'primary' : 'default'"
|
||||
:disabled="screenPlan.currentStep === index"
|
||||
@click="setScreenPlanStep(index)"
|
||||
>
|
||||
Показать
|
||||
</n-button>
|
||||
<template v-if="screenPlan?.currentStep === index && !screenPlanDirty">
|
||||
<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')">
|
||||
Старт
|
||||
@@ -1183,12 +1065,6 @@ useSimplePolling(() => {
|
||||
</template>
|
||||
</template>
|
||||
</n-space>
|
||||
<n-space>
|
||||
<n-button size="small" @click="addScreenPlanStep">Добавить шаг</n-button>
|
||||
<n-button v-if="screenPlanDraft.length" size="small" @click="screenPlanDraft = []; markScreenPlanDirty()">
|
||||
Удалить план
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1926,10 +1802,6 @@ useSimplePolling(() => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.screen-mode-controls {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.map-preview-layout {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
|
||||
Reference in New Issue
Block a user