Это вторая из трех частей руководства о том, как создать простую (но увлекательную !!) викторину для веб-приложений с использованием 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; },
В этой функции мы получаем объект результатов и устанавливаем «результаты» нашего состояния.
После этого мы готовы к базовой логике нашей игры. Единственное, что осталось в нашей игре:
- Отображение счета в таблице.
- Загрузка партитуры.
- И получить весь список оценок.
Мы рассмотрим эти моменты в следующей главе.
Тогда увидимся.