add online games

This commit is contained in:
2026-09-13 01:02:52 +07:00
parent bc0d9ce35f
commit 5462373235
54 changed files with 652 additions and 624 deletions
+12 -2
View File
@@ -2,7 +2,7 @@
import { Flag, Pause, Play, Reset, Settings, Trophy } from '@vicons/carbon'
import { Icon } from '@vicons/utils'
import { NButton, NTag } from 'naive-ui'
import { NCard, NFlex, NInput, NInputNumber, NModal, NQrCode, NSpace, NText } from 'naive-ui'
import { NCard, NFlex, NInput, NInputNumber, NModal, NQrCode, NSelect, NSpace, NText } from 'naive-ui'
import { useMessage } from 'naive-ui'
import { computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
@@ -36,7 +36,7 @@ const gameId = route.params.id
const message = useMessage()
const showSettingsModal = ref(false)
const settingsDraft = ref({ name: '', description: '' })
const settingsDraft = ref({ name: '', description: '', format: 'online' })
const showGiveApplicationModal = ref(false)
const showAddTeamModal = ref(false)
const showDeleteTeamModal = ref(false)
@@ -53,6 +53,7 @@ const game = ref<Game>({
description: undefined,
startAt: undefined,
status: undefined,
format: undefined,
scenario: undefined,
teams: undefined,
})
@@ -272,6 +273,7 @@ function openSettings() {
settingsDraft.value = {
name: game.value.name || '',
description: game.value.description || '',
format: game.value.format || 'online',
}
showSettingsModal.value = true
}
@@ -285,6 +287,7 @@ async function updateGame() {
id: game.value.id,
name: settingsDraft.value.name,
description: settingsDraft.value.description,
format: settingsDraft.value.format,
startAt: game.value.startAt,
scenarioId: game.value.scenario?.id,
})
@@ -738,6 +741,13 @@ useSimplePolling(() => {
<p>Название</p>
<n-input v-model:value="settingsDraft.name" type="text" placeholder='Дело №0 "Следствие ведут овечки"' />
<p class="settings-header">Формат игры</p>
<n-select
v-model:value="settingsDraft.format"
:options="[{ label: 'Online', value: 'online' }, { label: 'Offline', value: 'offline' }]"
aria-label="Формат игры"
/>
<p class="settings-header">Описание</p>
<n-input v-model:value="settingsDraft.description" type="textarea" placeholder="Убийство пастуха..." />
</n-space>
+11 -1
View File
@@ -46,6 +46,7 @@ function toResults(id: number) {
// ===== Создание игры =====
const showCreateGameModal = ref(false)
const newGameName = ref('')
const newGameFormat = ref('online')
const newGameDescription = ref('')
const newGameScenarioId = ref<number | null>(null)
const newGameStartAt = ref<number | null>(null)
@@ -82,6 +83,7 @@ async function getCatalogScenarios() {
function openCreateGame() {
newGameName.value = ''
newGameFormat.value = 'online'
newGameDescription.value = ''
newGameScenarioId.value = null
newGameStartAt.value = null
@@ -101,6 +103,7 @@ async function createGame() {
const res = await client.AddGame({
name: name,
description: newGameDescription.value,
format: newGameFormat.value,
// Сервер парсит startAt как google.protobuf.Timestamp — нужен RFC 3339 с таймзоной
startAt: new Date(startAtMs).toISOString(),
scenarioId: scenarioId,
@@ -132,7 +135,7 @@ async function createGame() {
<div v-for="game in games" :key="game.id" class="game-block" @click="toGame(game.id!)">
<div class="game-title">
{{ game.name }}
{{ game.name }} — {{ game.format === 'offline' ? 'Offline' : 'Online' }}
</div>
<div class="game-description">
{{ game.description }}
@@ -168,6 +171,13 @@ async function createGame() {
placeholder='Дело №0 "Следствие ведут овечки"'
/>
<p>Формат игры</p>
<n-select
v-model:value="newGameFormat"
:options="[{ label: 'Online', value: 'online' }, { label: 'Offline', value: 'offline' }]"
aria-label="Формат игры"
/>
<p>Описание</p>
<n-input
v-model:value="newGameDescription"
+1
View File
@@ -192,6 +192,7 @@ async function addGame(name: string, id: number) {
const resAddGame = await client.AddGame({
name: ('Тестовая игра: ' + name).trim(),
description: 'Тестовая игра',
format: 'online',
startAt: now,
scenarioId: id,
})
+5 -3
View File
@@ -45,6 +45,7 @@ const game = ref<Game | undefined>({
description: undefined,
startAt: undefined,
status: undefined,
format: undefined,
scenario: undefined,
teams: undefined,
})
@@ -181,11 +182,11 @@ async function submitAnswers() {
// Отвечать можно только пока игра идёт (run/pause); после завершения
// ответы фиксируются для оценки организатором.
const canAnswer = computed(() => {
return game.value?.status == 'run' || game.value?.status == 'pause'
return game.value?.format !== 'offline' && (game.value?.status == 'run' || game.value?.status == 'pause')
})
const hasQuestions = computed(() => {
return (story.value.questions?.length || 0) > 0
return game.value?.format !== 'offline' && (story.value.questions?.length || 0) > 0
})
// Поля ввода и кнопка на светлом бумажном листе: тёмная тема naive-ui
@@ -247,6 +248,7 @@ useSimplePolling(() => {
<IntroBlock
v-if="
game?.format !== 'offline' &&
game?.status &&
game.status != 'draft' &&
story.introduction &&
@@ -289,7 +291,7 @@ useSimplePolling(() => {
:place="advancedPlace.place"
v-model:mode="advancedPlace.mode"
v-for="(advancedPlace, index) in story.places"
v-bind:key="advancedPlace.place.code"
v-bind:key="`${game?.format}:${advancedPlace.place.code}`"
:mcp="authStore.hasRole('author') && actions[index]?.mcp"
:addAction="addAction"
>