mirror of
https://github.com/anatolykopyl/vk-bingo.git
synced 2026-03-26 21:04:26 +00:00
Added sse and a new screen
This commit is contained in:
18
frontend/src/views/Game/EndGame.vue
Normal file
18
frontend/src/views/Game/EndGame.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<button @click="endGame">
|
||||
Закончить игру
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import axios from 'axios'
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter()
|
||||
|
||||
function endGame() {
|
||||
axios.post(process.env.VUE_APP_BACKEND + '/end')
|
||||
router.push('/login')
|
||||
}
|
||||
</script>
|
||||
153
frontend/src/views/Game/Index.vue
Normal file
153
frontend/src/views/Game/Index.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="card"
|
||||
v-if="card"
|
||||
>
|
||||
<img
|
||||
class="meme"
|
||||
:src="card.image"
|
||||
>
|
||||
<h2>Кто скинул этот мем?</h2>
|
||||
<div class="interactive">
|
||||
<transition name="fade-answers">
|
||||
<List
|
||||
v-if="!selectedAnswer"
|
||||
:options="options"
|
||||
@selectedAnswer="selectAnswer"
|
||||
/>
|
||||
</transition>
|
||||
<transition name="spin-result">
|
||||
<Result
|
||||
v-if="showResult"
|
||||
:name="card.name"
|
||||
:selectedName="selectedAnswer"
|
||||
:date="card.date"
|
||||
:correct="correctAnswer"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
<Score
|
||||
v-if="card"
|
||||
:score="score"
|
||||
/>
|
||||
|
||||
<EndGame />
|
||||
|
||||
<square-loader
|
||||
v-if="!card"
|
||||
:color="'#f3f3f3'"
|
||||
class="loader"
|
||||
/>
|
||||
<Stats />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import axios from 'axios'
|
||||
import List from './List.vue'
|
||||
import Result from './Result.vue'
|
||||
import Score from './Score.vue'
|
||||
import Stats from './Stats.vue'
|
||||
import EndGame from './EndGame.vue'
|
||||
|
||||
import SquareLoader from 'vue-spinner/src/SquareLoader.vue'
|
||||
|
||||
const options = ref()
|
||||
const card = ref()
|
||||
const correctAnswer = ref()
|
||||
const selectedAnswer = ref()
|
||||
const showResult = ref()
|
||||
const score = reactive({
|
||||
"right": 0,
|
||||
"wrong": 0
|
||||
})
|
||||
|
||||
async function getCard() {
|
||||
correctAnswer.value = null
|
||||
selectedAnswer.value = null
|
||||
showResult.value = false
|
||||
const response = await axios.get(process.env.VUE_APP_BACKEND + '/card')
|
||||
card.value = response.data
|
||||
}
|
||||
|
||||
async function selectAnswer(selection) {
|
||||
selectedAnswer.value = selection
|
||||
// setTimeout(function() {
|
||||
// showResult.value = true
|
||||
// if (correctAnswer.value) {
|
||||
// score.right++
|
||||
// } else {
|
||||
// score.wrong++
|
||||
// }
|
||||
// }, 805)
|
||||
|
||||
await axios.post(process.env.VUE_APP_BACKEND + '/answer', {
|
||||
'data': {
|
||||
'id': card.value._id,
|
||||
'name': selectedAnswer.value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
getCard()
|
||||
const response = await axios.get(process.env.VUE_APP_BACKEND + '/options')
|
||||
options.value = response.data
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
width: 450px;
|
||||
padding: 18px;
|
||||
border-radius: 17px;
|
||||
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.6);
|
||||
background-color: #121212;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.meme {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.interactive {
|
||||
position: relative;
|
||||
-webkit-perspective: 900000px;
|
||||
perspective: 900000px;
|
||||
}
|
||||
|
||||
.fade-answers-leave-active {
|
||||
transition: all 0.8s ease;
|
||||
}
|
||||
.fade-answers-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.3);
|
||||
}
|
||||
|
||||
.spin-result-enter-active {
|
||||
transition: all 2s ease;
|
||||
}
|
||||
.spin-result-enter-from {
|
||||
transform: scale(0.2);
|
||||
transform: rotateY(120deg);
|
||||
}
|
||||
|
||||
.loader {
|
||||
margin-top: 100px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 520px) {
|
||||
.card {
|
||||
width: 94%;
|
||||
padding: 3%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
41
frontend/src/views/Game/List.vue
Normal file
41
frontend/src/views/Game/List.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="answers">
|
||||
<span class="option" v-for="name in options" :key="name" v-on:click="selectAnswer(name)">
|
||||
{{ name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'List',
|
||||
props: {
|
||||
options: Array
|
||||
},
|
||||
methods: {
|
||||
selectAnswer: function(selection) {
|
||||
this.$emit('selectedAnswer', selection)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.answers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.option {
|
||||
background-color: #5a5a5a;
|
||||
border-radius: 6px;
|
||||
margin: 3px;
|
||||
padding: 5px 9px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.option:hover {
|
||||
transform: scale(1.06);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
37
frontend/src/views/Game/Result.vue
Normal file
37
frontend/src/views/Game/Result.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div>
|
||||
<span v-show="!correct" class="wrong">Нет, это был не {{ selectedName }} 😢</span>
|
||||
<div class="result" v-bind:class="{correct: correct}">
|
||||
{{ name }} {{ date }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Result",
|
||||
props: {
|
||||
name: String,
|
||||
selectedName: String,
|
||||
date: String,
|
||||
correct: Boolean
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.result {
|
||||
padding: 30px 40px;
|
||||
border-radius: 8px;
|
||||
background-color: #5a5a5a;
|
||||
}
|
||||
|
||||
.correct {
|
||||
color: #121212;
|
||||
background-color: rgb(124, 230, 124);
|
||||
}
|
||||
|
||||
.wrong {
|
||||
color: rgb(255, 71, 71);
|
||||
}
|
||||
</style>
|
||||
51
frontend/src/views/Game/Score.vue
Normal file
51
frontend/src/views/Game/Score.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div>
|
||||
<span class="score-right">{{ score.right }}</span>
|
||||
<span class="score-wrong">{{ score.wrong }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Score",
|
||||
props: {
|
||||
score: Object
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
width: 350px;
|
||||
border-radius: 0 0 17px 17px;
|
||||
background-color: #f3f3f3;
|
||||
margin: auto;
|
||||
padding: 0.4em 18px;
|
||||
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.score-right {
|
||||
color: rgb(124, 230, 124);
|
||||
}
|
||||
|
||||
.score-wrong {
|
||||
color: rgb(255, 71, 71);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 520px) {
|
||||
div {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
padding: 0.4em 0;
|
||||
bottom: 0;
|
||||
border-radius: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
80
frontend/src/views/Game/Stats.vue
Normal file
80
frontend/src/views/Game/Stats.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div>
|
||||
<span>🎭 <small>Самый непредсказуемый:</small> <b>{{ mostOriginal }}</b> 🎭</span><br>
|
||||
<span>🪂 <small>Самый стабильный:</small> <b>{{ mostPredictable }}</b> 🪂</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'Stats',
|
||||
data() {
|
||||
return {
|
||||
stats: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mostOriginal: function() {
|
||||
let value = 1
|
||||
let returnName
|
||||
|
||||
for (const name in this.stats) {
|
||||
if (this.stats[name] < value) {
|
||||
value = this.stats[name]
|
||||
returnName = name
|
||||
}
|
||||
}
|
||||
|
||||
return returnName
|
||||
},
|
||||
mostPredictable: function() {
|
||||
let value = 0
|
||||
let returnName
|
||||
|
||||
for (const name in this.stats) {
|
||||
if (this.stats[name] > value) {
|
||||
value = this.stats[name]
|
||||
returnName = name
|
||||
}
|
||||
}
|
||||
|
||||
return returnName
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
axios
|
||||
.get(process.env.VUE_APP_BACKEND + '/stats')
|
||||
.then(response => {
|
||||
response.data.forEach(element => {
|
||||
this.stats[element._id] = element.correct / element.wrong
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
width: auto;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 2em;
|
||||
padding: 1em;
|
||||
background-color: #121212;
|
||||
border-radius: 7px;
|
||||
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 520px) {
|
||||
div {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding: 1em 0;
|
||||
margin: 1em 0 0 0;
|
||||
border-radius: 17px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user