This commit is contained in:
2026-10-04 23:46:57 +07:00
parent 68fd0760f6
commit 196dbb3b7b
6 changed files with 412 additions and 359 deletions
+203 -159
View File
@@ -15,6 +15,7 @@ import type {
TeamResult,
} from '@/api/generated/crabs/evening_detective_server'
import ScreenPreview from '@/components/ScreenPreview.vue'
import UiBlock from '@/components/UiBlock.vue'
const props = defineProps<{
mapItems: MapItem[]
@@ -154,8 +155,8 @@ onBeforeUnmount(() => {
<template>
<div class="map-preview-layout">
<div class="map-block">
<n-flex align="center" justify="space-between">
<UiBlock>
<n-flex align="center" justify="space-between" class="screen-header">
<p class="screens-title">Карта помещения</p>
<n-space>
<n-button size="small" @click="changeMapScale(-0.1)">−</n-button>
@@ -234,8 +235,8 @@ onBeforeUnmount(() => {
Добавить {{ item.label }}
</n-button>
</n-space>
</div>
<aside class="screen-preview-panel">
</UiBlock>
<UiBlock tag="aside" class="screen-preview-panel">
<p class="screens-title">Превью экрана</p>
<ScreenPreview
v-if="previewScreen?.status === 'connected'"
@@ -245,7 +246,7 @@ onBeforeUnmount(() => {
/>
<div v-else class="screen-preview-placeholder">Экран не подключён</div>
<div class="screen-plan">
<n-flex align="center" justify="space-between">
<n-flex align="center" justify="space-between" class="screen-header">
<p class="screens-title">План экранов</p>
<n-space>
<n-button
@@ -282,66 +283,77 @@ onBeforeUnmount(() => {
План экранов ещё не создан.
</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-space
v-for="(step, index) in screenPlanDraft"
:key="index"
class="screen-plan-step"
align="center"
justify="space-between"
wrap
>
<n-space 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-model:value="step.timerLabel"
v-if="step.mode === 'SCREEN_MODE_TEXT'"
v-model:value="step.text"
:disabled="screenPlanSaving"
size="small"
placeholder="Надпись таймера"
placeholder="Текст экрана"
style="min-width: 240px"
@update:value="emit('dirty')"
/>
<n-input-number
v-model:value="step.timerDurationSeconds"
:min="1"
:max="86400"
:disabled="screenPlanSaving"
<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-space>
<n-space class="screen-plan-actions">
<n-button
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>
: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-space>
<n-button size="small" :disabled="screenPlanSaving" @click="emit('addStep')">
@@ -358,119 +370,138 @@ onBeforeUnmount(() => {
</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-space
v-for="(step, index) in screenPlan.steps || []"
:key="index"
class="screen-plan-step"
align="center"
justify="space-between"
wrap
>
<n-space align="center">
<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-space>
<n-space class="screen-plan-actions">
<n-button
size="small"
:type="screenPlan.currentStep === index ? 'primary' : 'default'"
:disabled="screenPlan.currentStep === index"
@click="emit('setStep', index)"
>
<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>
Показать
</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>
</template>
</n-space>
</n-space>
</template>
</div>
</aside>
</UiBlock>
</div>
</template>
<style scoped>
.map-preview-layout {
display: grid;
gap: 12px;
gap: 8px;
grid-template-columns: minmax(0, 1fr) minmax(280px, 420px);
}
.map-block,
.screen-preview-panel {
background-color: #222;
border-radius: 10px;
margin: 12px 0;
padding: 16px;
}
.screens-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 12px;
margin: 0;
}
.screen-header {
margin-bottom: 8px;
}
.screen-preview-placeholder {
@@ -483,6 +514,19 @@ onBeforeUnmount(() => {
text-align: center;
}
.screen-preview-panel > .screens-title {
margin-bottom: 8px;
}
.screen-plan {
margin-top: 16px;
}
.screen-plan-step {
border-top: 1px solid rgb(255 255 255 / 10%);
padding: 8px 0;
}
.map-field {
aspect-ratio: 1;
background-color: #151515;
@@ -515,7 +559,7 @@ onBeforeUnmount(() => {
border-radius: 6px;
cursor: grab;
min-width: 90px;
padding: 8px 24px 8px 10px;
padding: 8px 24px 8px 8px;
position: absolute;
user-select: none;
}
@@ -545,8 +589,8 @@ onBeforeUnmount(() => {
background-color: #48633a;
border-radius: 6px 6px 0 0;
cursor: grab;
margin: -8px -24px 8px -10px;
padding: 8px 24px 8px 10px;
margin: -8px -24px 8px -8px;
padding: 8px 24px 8px 8px;
}
.map-team-handle:active {
@@ -584,7 +628,7 @@ onBeforeUnmount(() => {
}
.map-hidden-items {
margin-top: 10px;
margin-top: 16px;
}
@media (width < 1024px) {