Это вторая из трех частей руководства о том, как создать простую (но увлекательную !!) викторину для веб-приложений с использованием Vue.js

Добро пожаловать назад. В первой части нашего проекта мы инициализировали нашу игру, объявили наш первый маршрут, определили хранилище (Vuex) и получили вопросы из API.

В этой части мы позаботимся о логике нашей игры. Итак, начнем с этого.

Исходное репо:

Https://github.com/omar1893/vue-quiz.git

Создание наших компонентов.

Итак, сначала нам нужно создать и импортировать наши компоненты для нашей игры и статистику игры. Итак, давайте создадим в нашей папке «components» следующие файлы vue:

  • Game.vue
  • Answers.vue
  • Question.vue

Каждый из них имеет базовую структуру файла .vue:

<template>
  <div>
   This is x Component
  </div>
</template>
<script>
   export default{}
</script>
<style scoped>
</style>

Теперь давайте создадим экземпляр нашего нового маршрута в нашем «router / index.js». Сначала нам нужно импортировать его:

import Game from '@/components/Game'

А затем добавим объект в массив «routes»:

{
  path: '/game',
  name: 'game',
  component: Game
},

Вы, наверное, задаетесь вопросом: «Если я только что создал три компонента, почему я создаю только путь к« игровому »компоненту?». Это потому, что я собираюсь показать вам, как работать с вложенными дочерними компонентами. Взгляните на следующий рисунок (он же шедевр дизайна 😎):

Теперь это изображение означает, что компоненты «вопрос» и «ответ» (дочерние) будут внутри «игры» (родительский элемент). Обязанность «игры» будет заключаться в том, чтобы извлечь массив вопросов из нашего магазина и просто передать его своим потомкам. Компонент «вопрос» получит вопрос и просто отобразит его. Компонент «ответы» покажет варианты и, кроме того, он также обработает всю логику игры.

Еще много чего нужно сделать. Итак, приступим:

Родительский компонент

Сначала поработаем над нашим файлом «components / game.vue». Во-первых, мы должны импортировать дочерний компонент, чтобы использовать их. Итак, внутри нашего тега скрипта напишем следующее:

import Question from './Question.vue'
import Answers from './Answers.vue'

Теперь, когда мы только что импортировали эти два компонента, мы почти готовы использовать дочерние элементы внутри родительского компонента. Давайте поместим в наш экспортный объект следующее:

components:{
question: Question,
answers: Answers
},

При этом мы просто указали названия тегов для каждого из необходимых нам компонентов. Итак, внутри html нашего компонента введите следующее:

<question></question>
<answers></answers>

Теперь мы должны увидеть два «Это компонент x». Давайте добавим немного стиля нашему компоненту. Помните, что это стили, которые я использовал. Вы можете поставить свои:

<template>
   <div class=”game”>
    <div class=”card w-75 text-center py-4 mx-auto”>
     <question></question>
     <answers></answers>
    </div> 
  </div>
</template>

После этого создадим данные нашего «игрового» компонента.

data: function(){
 return{
   questions:[],
   question:'',
   object: {},
   result:{
     corrects:0,
     incorrects:0
   },
 }
},

Итак, давайте быстро объясним атрибуты нашего компонента. Первые «вопросы», это будет набор вопросов. Теперь «вопрос» будет содержать строку вопроса, которая активна в нашей игре. Тогда «объект» будет тем, что будет нести активный объект вопроса. «Результат» - это парень, который заботится о количестве неудач и успехов игрока.

Теперь, когда мы это понимаем, давайте серьезно займемся игрой. На данный момент у нас должны быть вопросы в нашем магазине. Но для того, чтобы использовать его, нам нужно получить к ним доступ из нашего компонента «game», поэтому давайте сделаем следующее с нашим атрибутом «questions»:

questions: this.$store.state.questions,

Если мы хотим проверить, работает ли это, мы можем использовать ловушку жизненного цикла «created», поэтому введите следующее:

created(){
   console.log(this.questions)
}

Теперь каждый раз, когда этот компонент создается, он должен печатать в нашей консоли массив, который находится внутри «questions».

Помните: вам нужно перейти от «начального» компонента, чтобы выполнить запрос к API.

Теперь нам нужно получить первый объект вопроса нашего массива. Для этого нам нужно написать наш первый метод. Итак, давайте сделаем это:

methods:{
   getQuestion: function(answer){
   this.object = this.questions.shift();
   this.question = this.object.question;
  }
}

С помощью этого метода мы извлекаем первый вопрос из нашего массива и сохраняем его в атрибуте «object», затем берем строку вопроса внутри «объекта» и сохраняем ее в атрибуте «question», теперь нам нужно вызвать этот метод. Для этого мы можем использовать крючок жизненного цикла, который мы только что создали минуту назад. Итак, давайте добавим в наш метод created () следующее:

created(){
  this.getQuestion()
  console.log(this.questions)
}

Теперь нам нужно начать передавать props дочерним компонентам. Начнем с самого простого, компонента «вопрос». Нам нужно отправить атрибут «вопрос» соответствующему компоненту. Для этого мы должны добавить к его тегу html следующее:

<question v-bind:question='question'></question>

Этим мы, по сути, говорим, что отправляем «пакет» под названием question (v-bind: question), а внутри находится наш атрибут «question» (= «question»).

Теперь нам нужно отобразить его на нашем экране. Для этого нам нужно добавить несколько строк в наш файл «components / question.vue». Так что давайте сделаем это быстро. Мы еще не закончили с этим компонентом ... пока.

Компонент вопроса.

Это будет короткое. Чтобы отобразить вопрос, который активен в родительском элементе, нам нужно добавить следующее внутри объекта экспорта:

props:[‘question’],

Решив это, мы должны заменить предыдущий текст и добавить следующую строку в html этого компонента:

<p>{{question}}</p>

Теперь мы должны увидеть первый вопрос, отображаемый в нашем компоненте. Довольно круто, но мы должны продолжать двигаться вперед. Итак, вернемся к файлу «components / game.vue».

Компонент "Ответы".

Теперь вернемся к компоненту «игра», нам нужно добавить следующее в компонент ответов в html:

<answers v-bind:answers='object'></answers>

Теперь мы передаем весь объект вопроса в компонент игры.

Теперь перейдем к компоненту ответов, мы еще не закончили, поэтому вернемся позже. Теперь перейдем к файлу «components / answers.vue».

Сначала давайте создадим экземпляр свойства, которое этот компонент получит от родительского компонента:

props:['answers'],

Затем введите данные этого компонента:

data () {
  return{
     options: [],
     correct: '',
     gameActive: true,
     selected: '',
     status: false
  }
}

Сделав это, давайте объясним это, потому что с этих строк мы просто запустили основную логику игры. Итак, во-первых, у нас есть массив «options», в нем будут храниться параметры, которые мы собираемся отображать. Атрибут «правильный» будет содержать правильный вариант для активного вопроса. Затем у нас есть gameActive, это логическое значение уведомит нас, что пользователь уже сделал выбор, и мы должны деактивировать кнопки. Атрибут «selected» будет иметь вариант, который пользователь выбрал, когда нажал одну из кнопок. И, наконец, «статус», он изменится, если пользователь верен или неверен, и позволит нам увеличить счет в игровом компоненте.

Теперь нам нужно назначить реквизиты данным. Для этого напишем первый метод этого компонента.

methods: {
   assign: function(){
   this.options = this.answers.incorrect_answers,
   this.correct = this.answers.correct_answer,
   this.gameActive = true,
   this.selected = ''
   }
},

Тогда давайте воспользуемся методом жизненного цикла created:

created(){
   this.assign()
}

Теперь каждый раз, когда создается этот компонент, мы инициализируем наши данные с активным вопросом.

Теперь давайте добавим HTML в этот компонент:

<template>
<div class='answers'>
  <div class='container'>
   <div class='row px-3'>
    <div class='col-md-6" v-for=”option in options'>
     <div class='multiple-choice'>
      <button v-on:click='pickOption(option)' class='action-button           animate w-100 mb-3'>
       {{option}}
      </button>
     </div>
    </div>
     <button v-if='!gameActive' v-on:click='nextQuestion()' class='action-button animate w-100 blue my-3 bits'>
       Next >
     </button>
   </div>
  </div>
</div>
</template>

Это базовый HTML-код, который я напечатал для этой игры, помните, я использовал bootstrap 4 для этой игры, но вы можете использовать все, что захотите.

Давайте объясним важные части этого HTML. Сначала у нас есть опция v-for = ’в options’, при этом Vue должен отображать кнопки всех опций, которые мы получили в массиве «options» в наших данных. V-on: click = ’pickOption (option)’ - это метод (который мы скоро напишем), который будет использовать выбранный пользователем вариант и обрабатывать его, чтобы определить, правильный или неправильный ответ. С помощью v-on: click = ’nextQuestion ()’ мы перейдем к следующему вопросу. А с v-if = ’! GameActive’ мы будем показывать эту кнопку, только если игра неактивна.

Теперь напишем метод pickOption:

pickOption: function(a){
   this.gameActive = false;
   this.selected = a;
   if(this.correct == this.selected) this.status=true
   else this.status=false
},

С помощью этого метода сначала мы деактивируем игру (по крайней мере, в наших данных, на данный момент…), затем мы сохраняем выбранный вариант и сравниваем его с правильным ответом и проверяем, правильный ли ответ (this.status = true) или неверно (this.status = false).

Теперь нам нужно показать пользователю, правильно он выбрал или нет. Для этого нам нужно поэкспериментировать с классами кнопки. Но сначала давайте посмотрим на логику классов и на то, когда мы собираемся отображать каждый из них.

Если вы используете стили кнопок, о которых я говорил в первой части руководства, у вас будет 4 класса, представляющих 4 цвета (.blue,. Red, .yellow, .blue), и, кроме того, у меня .disabled ( указатель-события: нет;), который просто отключает кнопки.

Мы собираемся отображать эти классы в следующих ситуациях:

  • синий: когда игра активна.
  • отключено: когда игра неактивна.
  • желтый: если вариант, выбранный пользователем, является вариантом этой кнопки, И выбранный вариант неверен.
  • зеленый: если игра неактивна, И выбранный вариант является вариантом этой кнопки И выбранный вариант является правильным, ИЛИ, если игра неактивна, И выбранный вариант является вариантом этой кнопки. И выбранный вариант неверен.
  • красный: если игра неактивна.

Итак, применив эту ситуацию, мы должны ввести следующее в кнопки параметров:

<button v-bind:class='{
blue: gameActive, 
disabled: !gameActive, 
red: !gameActive ,
yellow:selected == option && selected != correct, 
green: !gameActive && selected == option && selected == correct || !gameActive && selected != correct && option == correct}' 
v-on:click='pickOption(option)' 
class='action-button animate w-100 mb-3'>

Теперь, когда это применимо, у нас есть основная логика нашей игры. Так что прямо сейчас мы должны иметь возможность играть… только первый вопрос 😭. Нам нужно создать метод, чтобы родительский компонент обновлялся до следующего вопроса. Выглядит это так:

nextQuestion: function(){
   this.$emit('nextQuestion', this.status);
}

Этот метод просто генерирует событие, которое родитель должен прослушать и выполнить метод getQuestion. Кроме того, мы передаем статус для обновления счетчика компонента «игра». Но прежде чем мы перейдем к компоненту «игра», нам понадобится наблюдатель, чтобы применять метод «assign» каждый раз при обновлении свойств, поэтому давайте воспользуемся опцией «смотреть» в этом компоненте:

watch: {
   '$props':{
     handler: function (val, oldVal) {
      this.assign()
     },
    deep: true
   }
},

Теперь мы готовы снова работать над «игровой» составляющей.

Обновление вопроса.

Итак, сейчас единственное, что осталось, - это обрабатывать события, генерируемые компонентом «ответы». Для этого добавим в тег «ответы» следующее.

<answers v-bind:answers='object' v-on:nextQuestion='getQuestion($event)'>
</answers>

После этого мы указали событие, которое будет прослушивать «игровой» компонент, и метод, который будет выполняться.

Затем нам нужно обновить метод getQuestion:

getQuestion: function(answer){
  if(answer){
   this.result.corrects++
  }
  else if(answer == false){
   this.result.incorrects++
  }
  else if(answer == null){}
  if(this.questions[0]){
   this.gameActive = true;
   this.object = this.questions.shift();
   this.question = this.object.question;
  }
  else{
   this.$store.commit('setResults', this.result);
   this.$router.push({name: 'results'})
  }
}

Мы сделали это, чтобы обновить счет данными, полученными из «answers.vue». Затем, если в массиве «questions» есть хотя бы один объект, он обновит реквизиты. Если его нет, он установит счетчики игры в наш магазин и перейдет к следующему просмотру (мы увидим это в следующей главе).

Наконец, нам нужно создать эту мутацию в нашем магазине. Итак, давайте поработаем над store / store.js.

Установка результатов игры.

Теперь давайте создадим мутацию под названием «setResults», чтобы установить счет игрока:

setResults(state, answers) {
   state.results.correct_answers = answers.corrects;
   state.results.incorrect_answers = answers.incorrects;
},

В этой функции мы получаем объект результатов и устанавливаем «результаты» нашего состояния.

После этого мы готовы к базовой логике нашей игры. Единственное, что осталось в нашей игре:

  • Отображение счета в таблице.
  • Загрузка партитуры.
  • И получить весь список оценок.

Мы рассмотрим эти моменты в следующей главе.

Тогда увидимся.