generated from VLADIMIR/template_frontend
up
This commit is contained in:
Vendored
+1
-1
@@ -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-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/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">
|
||||||
|
|||||||
+48
-176
@@ -112,21 +112,13 @@ const screenPlan = ref<ScreenPlan>()
|
|||||||
const screenPlanDraft = ref<ScreenPlanStep[]>([])
|
const screenPlanDraft = ref<ScreenPlanStep[]>([])
|
||||||
const screenPlanDirty = ref(false)
|
const screenPlanDirty = ref(false)
|
||||||
const screenPlanSaving = ref(false)
|
const screenPlanSaving = ref(false)
|
||||||
|
const screenPlanMode = ref<'use' | 'edit'>('use')
|
||||||
const previewQuestions = ref<ResultsQuestion[]>([])
|
const previewQuestions = ref<ResultsQuestion[]>([])
|
||||||
const previewTeams = ref<TeamResult[]>([])
|
const previewTeams = ref<TeamResult[]>([])
|
||||||
const mapItems = ref<MapItem[]>([])
|
const mapItems = ref<MapItem[]>([])
|
||||||
const mapSaving = ref(false)
|
const mapSaving = ref(false)
|
||||||
const screenVolume = ref(100)
|
const screenVolume = ref(100)
|
||||||
const screenPosition = ref(0)
|
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 previewScreen = computed(() => screens.value.find((screen) => screen.status === 'connected') || screens.value[0])
|
||||||
const hasDisconnectedScreen = computed(() => screens.value.some((screen) => screen.status === 'disconnected'))
|
const hasDisconnectedScreen = computed(() => screens.value.some((screen) => screen.status === 'disconnected'))
|
||||||
const screenPlanModeOptions = [
|
const screenPlanModeOptions = [
|
||||||
@@ -139,22 +131,6 @@ const screenPlanModeOptions = [
|
|||||||
{ label: 'Таймер', value: 'SCREEN_MODE_TIMER' },
|
{ label: 'Таймер', value: 'SCREEN_MODE_TIMER' },
|
||||||
{ label: 'Текст', value: 'SCREEN_MODE_TEXT' },
|
{ 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 mapScale = ref(1)
|
||||||
const mapOffset = ref({ x: 0, y: 0 })
|
const mapOffset = ref({ x: 0, y: 0 })
|
||||||
let dragState:
|
let dragState:
|
||||||
@@ -196,11 +172,6 @@ getGame(Number(gameId))
|
|||||||
async function getGameScreens() {
|
async function getGameScreens() {
|
||||||
const res = await client.GetGameScreens({ gameID: Number(gameId) })
|
const res = await client.GetGameScreens({ gameID: Number(gameId) })
|
||||||
screens.value = res.screens || []
|
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') {
|
if (previewScreen.value?.mode === 'SCREEN_MODE_RESULTS') {
|
||||||
const result = await client.GetGameResults({ gameID: Number(gameId) })
|
const result = await client.GetGameResults({ gameID: Number(gameId) })
|
||||||
previewQuestions.value = result.questions || []
|
previewQuestions.value = result.questions || []
|
||||||
@@ -229,6 +200,20 @@ function addScreenPlanStep() {
|
|||||||
timerDurationSeconds: 600,
|
timerDurationSeconds: 600,
|
||||||
})
|
})
|
||||||
screenPlanDirty.value = true
|
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) {
|
function removeScreenPlanStep(index: number) {
|
||||||
@@ -271,7 +256,7 @@ async function saveScreenPlan() {
|
|||||||
screenPlan.value = res.plan
|
screenPlan.value = res.plan
|
||||||
screenPlanDraft.value = res.plan?.steps?.map((step) => ({ ...step })) || []
|
screenPlanDraft.value = res.plan?.steps?.map((step) => ({ ...step })) || []
|
||||||
screenPlanDirty.value = false
|
screenPlanDirty.value = false
|
||||||
await getGameScreens()
|
screenPlanMode.value = 'use'
|
||||||
message.success(res.plan ? 'План экранов сохранён' : 'План экранов удалён')
|
message.success(res.plan ? 'План экранов сохранён' : 'План экранов удалён')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
message.error(err instanceof Error ? err.message : 'Не удалось сохранить план экранов')
|
message.error(err instanceof Error ? err.message : 'Не удалось сохранить план экранов')
|
||||||
@@ -502,37 +487,6 @@ async function unbindScreen(screen: ScreenState) {
|
|||||||
await getGameScreens()
|
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) {
|
async function toTeamStory(id: number, password: string, newTab: boolean = false) {
|
||||||
const uri = '/team-story/' + id + '?password=' + password
|
const uri = '/team-story/' + id + '?password=' + password
|
||||||
if (newTab) {
|
if (newTab) {
|
||||||
@@ -980,115 +934,26 @@ useSimplePolling(() => {
|
|||||||
</n-button>
|
</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</div>
|
</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">
|
<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 && !screenPlanDirty" size="small" @click="addScreenPlanStep">
|
<n-button v-if="!screenPlan && screenPlanMode === 'use'" size="small" @click="addScreenPlanStep">
|
||||||
Создать план
|
Создать план
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button
|
<template v-if="screenPlan && screenPlanMode === 'use'">
|
||||||
v-if="screenPlan || screenPlanDirty"
|
<n-button size="small" @click="editScreenPlan">Редактировать</n-button>
|
||||||
size="small"
|
</template>
|
||||||
type="primary"
|
<template v-if="screenPlanMode === 'edit'">
|
||||||
:loading="screenPlanSaving"
|
<n-button size="small" @click="cancelScreenPlanEdit">Отменить</n-button>
|
||||||
@click="saveScreenPlan"
|
<n-button size="small" type="primary" :loading="screenPlanSaving" @click="saveScreenPlan">
|
||||||
>
|
Сохранить план
|
||||||
Сохранить план
|
</n-button>
|
||||||
</n-button>
|
</template>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-flex>
|
</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-space v-for="(step, index) in screenPlanDraft" :key="index" align="center" wrap>
|
||||||
<n-text>{{ index + 1 }}</n-text>
|
<n-text>{{ index + 1 }}</n-text>
|
||||||
<n-select
|
<n-select
|
||||||
@@ -1133,15 +998,32 @@ useSimplePolling(() => {
|
|||||||
<n-button size="small" type="error" ghost @click="removeScreenPlanStep(index)">
|
<n-button size="small" type="error" ghost @click="removeScreenPlanStep(index)">
|
||||||
Удалить
|
Удалить
|
||||||
</n-button>
|
</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
|
<n-button
|
||||||
size="small"
|
size="small"
|
||||||
:type="screenPlan?.currentStep === index ? 'primary' : 'default'"
|
:type="screenPlan.currentStep === index ? 'primary' : 'default'"
|
||||||
:disabled="screenPlanDirty || screenPlan?.currentStep === index"
|
:disabled="screenPlan.currentStep === index"
|
||||||
@click="setScreenPlanStep(index)"
|
@click="setScreenPlanStep(index)"
|
||||||
>
|
>
|
||||||
Показать
|
Показать
|
||||||
</n-button>
|
</n-button>
|
||||||
<template v-if="screenPlan?.currentStep === index && !screenPlanDirty">
|
<template v-if="screenPlan.currentStep === index">
|
||||||
<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-button size="small" @click="sendScreenPlanCommand(step.mode, 'SCREEN_COMMAND_TIMER_START')">
|
||||||
Старт
|
Старт
|
||||||
@@ -1183,12 +1065,6 @@ useSimplePolling(() => {
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</n-space>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1926,10 +1802,6 @@ useSimplePolling(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.screen-mode-controls {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-preview-layout {
|
.map-preview-layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
Reference in New Issue
Block a user