Files
evening_detective_frontend/src/components/TeamStoryPage.vue
T
2026-08-03 01:18:17 +07:00

182 lines
4.4 KiB
Vue

<script setup lang="ts">
import { getAuthClient } from '@/api/auth_client';
import type { Game, Place, Story } from '@/api/generated/crabs/evening_detective_server';
import HeaderMenu from '@/components/HeaderMenu.vue'
import { NCard, NFlex, NInput, NModal, NSpace, NText, NUpload, NUploadDragger, NAlert, NTag, NButton } from 'naive-ui'
import { useMessage } from 'naive-ui';
import { nextTick, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import PlaceBlock from '@/components/PlaceBlock.vue'
import GameInputForm from '@/components/GameInputForm.vue'
import { useSimplePolling } from '@/composables/useSimplePolling';
import { useAuthStore } from '@/stores/auth';
const authStore = useAuthStore()
const client = getAuthClient()
const message = useMessage()
const route = useRoute()
const teamId = route.params.id
const password = route.query.password
const newPlacesCount = ref(0)
const scrollContainer = ref<HTMLDivElement | null>();
const story = ref<AdvancedStory>({
places: []
})
const game = ref<Game | undefined>({
id: undefined,
name: undefined,
description: undefined,
startAt: undefined,
status: undefined,
scenario: undefined,
teams: undefined
})
const code = ref('')
const isAtBottom = () => {
const container = scrollContainer.value;
if (!container) return false;
return container.scrollHeight - container.scrollTop - container.clientHeight < 1;
};
async function getStory() {
const oldStory = story.value
const res = await client.GetTeamStory({
id: Number(teamId),
password: password as string
})
if (res.error != '') {
message.error(res.error!)
return
}
game.value = res.game
const wasAtBottom = isAtBottom();
if (wasAtBottom) {
newPlacesCount.value = 0
}
const oldCount = story.value.places.length
story.value = {
places: res.story!.places?.map((item, i) => {
console.log(oldStory?.places[i]?.mode)
return {
place: item,
mode: oldStory?.places[i]?.mode || 'show'
}
}) || []
}
const newCount = story.value.places.length
if (newCount > oldCount && oldCount != 0) {
newPlacesCount.value += newCount - oldCount
}
await nextTick();
if (wasAtBottom && oldCount != 0) {
const container = scrollContainer.value;
if (!container) return false;
container.scrollTop = container.scrollHeight;
}
}
getStory()
async function addAction(newCode: string) {
const res = await client.AddTeamAction({
id: Number(teamId),
password: password as string,
code: newCode
})
if (res.error != '') {
message.error(res.error!)
return
}
await getStory()
code.value = ''
await nextTick();
newPlacesCount.value = 0
}
type AdvancedStory = {
places: AdvancedPlace[];
}
type AdvancedPlace = {
place: Place;
mode: string;
}
async function deleteLast() {
const res = await client.DeleteLastTeamAction({
id: Number(teamId),
})
if (res.error != '') {
message.error(res.error!)
return
}
await getStory()
code.value = ''
}
useSimplePolling(() => {
getStory()
}, 2000);
</script>
<template>
<div class="center-block-custom bottom-margin smooth-scroll" ref="scrollContainer">
<div class="width700">
<GameInputForm :addAction="addAction" :allPlacesCount="story.places.length"
:newPlacesCount="newPlacesCount"></GameInputForm>
<PlaceBlock :place="advancedPlace.place" v-model:mode="advancedPlace.mode"
v-for="advancedPlace in story.places" v-bind:key="advancedPlace.place.code" :addAction="addAction">
</PlaceBlock>
<div v-if="authStore.hasRole('author')" class="edit-panel">
<n-flex justify="end">
<n-button @click="deleteLast">Удалить последний ход</n-button>
</n-flex>
</div>
</div>
</div>
<HeaderMenu active="games" />
</template>
<style scoped>
.bottom-margin {
height: calc(100dvh - 70px - 76px);
}
.smooth-scroll {
scroll-behavior: smooth;
}
.edit-panel {
margin: 30px 0;
padding: 20px;
background-color: #222;
border-radius: 10px;
}
@media (width >=1024px) {
.bottom-margin {
height: calc(100dvh - 70px - 76px);
}
}
</style>