【问题标题】:How do I keep the selected answers in this Vue quiz application?如何在这个 Vue 测验应用程序中保留选定的答案?
【发布时间】:2022-01-09 07:47:12
【问题描述】:

我正在使用 Vue 3Bootstrap 4 构建一个测验应用程序。测验可以双向导航(有nextQuestion 方法和prevQuestion 方法)。

理想情况下,我会在 Trivia API 的帮助下将它变成一个全栈应用程序,我无法修改它,所以我将拥有在 Vue 中做尽可能多的逻辑。

const quizApp = {
  data() {
    return {
      isScoreVisible: false,
      questionCount: 0,
      selectedAnswer: "",
      correctAnswers: [],
      results: [{
          question: 'The book "The Little Prince" was written by...',
          correct_answer: "Antoine de Saint-Exupéry",
          selectedAnswer: "",
          incorrect_answers: [
            "Miguel de Cervantes Saavedra",
            "Jane Austen",
            "F. Scott Fitzgerald"
          ]
        },
        {
          question: "Which novel by John Grisham was conceived on a road trip to Florida while thinking about stolen books with his wife?",
          selectedAnswer: "",
          correct_answer: "Camino Island",
          incorrect_answers: ["Rogue Lawyer", "Gray Mountain", "The Litigators"]
        },
        {
          question: 'In Terry Pratchett\'s Discworld novel "Wyrd Sisters", which of these are not one of the three main witches?',
          selectedAnswer: "",
          correct_answer: "Winny Hathersham",
          incorrect_answers: [
            "Granny Weatherwax",
            "Nanny Ogg",
            "Magrat Garlick"
          ]
        }
      ]
    };
  },
  methods: {
    nextQuestion() {
      if (this.questionCount < this.results.length - 1) {
        this.questionCount++;
      }
      // Clear selected answer as you advance through the questions
      this.selectedAnswer = "";
    },
    prevQuestion() {
      if (this.questionCount >= 1) {
        this.questionCount--;
      }
    },
    checkAnswer(answer) {
      let answersList = document.querySelectorAll("ul.answers li");
      answersList.forEach(function(item) {
        item.removeAttribute("class");
      });

      // check if the clicked anwser is equal to the correct answer
      this.selectedAnswer = answer;
      if (answer == this.results[this.questionCount].correct_answer) {
        // Update the correct answers arary (make sure thare are no duplicates)
        if (this.correctAnswers.indexOf(answer) === -1) {
          this.correctAnswers.push(answer);
        }
        // Add correct answer class
        event.target.classList.add("text-white", "bg-success");
      } else {
        // Add incorrect answer
        event.target.classList.add("text-white", "bg-danger");
      }
    },
    showScore() {
      this.isScoreVisible = true;
    },
    resetQuiz() {
      this.questionCount = 0;
      this.correctAnswers = [];
      this.selectedAnswer = "";
      this.isScoreVisible = false;
    },
    removeElementFromArray(arr, elm) {
      return arr.filter((el) => el !== elm);
    },
    shuffle(arr) {
      var len = arr.length;
      var d = len;
      var array = [];
      var k, i;
      for (i = 0; i < d; i++) {
        k = Math.floor(Math.random() * len);
        array.push(arr[k]);
        arr.splice(k, 1);
        len = arr.length;
      }
      for (i = 0; i < d; i++) {
        arr[i] = array[i];
      }
      return arr;
    }
  },
  computed: {
    answers() {
      let incorrectAnswers = this.results[this.questionCount].incorrect_answers;
      let correctAnswer = this.results[this.questionCount].correct_answer;
      // return all answers, shuffled
      return this.shuffle(incorrectAnswers.concat(correctAnswer));
    },
    showScoreBtn() {
      return (
        this.questionCount + 1 == this.results.length &&
        this.selectedAnswer != ""
      );
    }
  }
};

Vue.createApp(quizApp).mount("#quiz_app");
#quiz_app {
  height: 100vh;
}

.container {
  flex: 1;
}

.logo {
  width: 30px;
}

.nav-item {
  width: 100%;
}

.category-name {
  font-size: 2rem !important;
  font-weight: 400;
  text-align: center;
}

.questions .card {
  width: 100%;
  min-height: 330px;
}

.questions .card-header {
  padding-top: 1.25rem;
  padding-bottom: 1.25rem;
}

.questions .card-footer {
  padding-top: 0.7rem;
  padding-bottom: 0.7rem;
}

.answers li {
  cursor: pointer;
  display: block;
  padding: 7px 15px;
  margin-bottom: 5px;
  border-radius: 6px;
  border: 1px solid rgba(0, 0, 0, 0.1);
  background: #fff;
}

.answers li:last-child {
  margin-bottom: 0;
}

.answers li:hover {
  background: #fafafa;
}

.pager {
  list-style-type: none;
  margin: 0;
  padding: 0;
  display: flex;
  justify-content: space-between;
}

.pager li > a {
  display: inline-block;
  padding: 5px 10px;
  text-align: center;
  width: 100px;
  background-color: #fff;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 999px;
  text-decoration: none !important;
  color: #fff;
}

.pager li > a.disabled {
  pointer-events: none;
  background-color: #9d9d9d !important;
}

.results a,
.results a:hover {
  color: #fff;
  font-weight: 500;
  border-radius: 999px;
}

.results a .fas {
  font-size: 85%;
  margin-right: 3px;
}

@media (min-width: 768px) {
  .nav-item {
    width: auto;
  }
  .questions .card {
    width: 67%;
  }
}

@media (min-width: 992px) {
  .questions .card {
    width: 50%;
  }
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://kit.fontawesome.com/3f9baea05e.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/vue@next"></script>

<div id="quiz_app" class="container d-flex flex-column justify-content-center my-3">
    <h2 class="display-4 category-name">Books</h2>
    <div class="questions d-flex flex-column align-items-center">
      <div v-if="results.length" class="card shadow-sm" :class="{'bg-light': isScoreVisible}">
        <div v-if="isScoreVisible" class="results my-auto text-center">
          <h5 class="mb-3">Your result: {{correctAnswers.length}} / {{results.length}}</h5>
          <a href="#" class="btn btn-md bg-dark" @click="resetQuiz">
            <i class="fas fa-sync-alt"></i> Play again
          </a>
        </div>
        <template v-else>
          <div class="card-header bg-light h6">
            {{results[questionCount].question}}
          </div>
          <div class="card-body">
            <ul class="answers list-unstyled m-0">
              <li v-for="answer in answers" :key="answer" @click="checkAnswer(answer)">
                {{answer}}
              </li>
            </ul>
          </div>
          <div class="card-footer bg-white">
            <ul class="pager">
              <li><a href="#" @click="prevQuestion" class="bg-dark" :class="{'disabled' : questionCount == 0}">Previous</a></li>
              <li class="d-flex align-items-center text-secondary font-weight-bold small">
                Question {{questionCount + 1}} of {{results.length}}
              </li>
              <li v-if="showScoreBtn">
                <a href="#" class="bg-dark" @click="showScore">Score</a>
              </li>
              <li v-else>
                <a href="#" class="bg-dark" :class="{'disabled' : selectedAnswer == ''}" @click="nextQuestion">Next</a>
              </li>
            </ul>
          </div>
        </template>
      </div>
    </div>
  </div>

问题

如果我在测验问题中来回导航,我已经选择的答案不会保留

问题

  1. 是否有可靠的方法来仅使用 JavaScript 来保留给定的答案,或者我应该使用将它们保存在数据库中的 API?
  2. 如果我需要 API,Trivia API 会完成这项工作(还是只允许获取请求)?

【问题讨论】:

  • 我要做的是创建一个 API。但是如果你仍然希望它被永久保存,你可以使用 LocalStorage 或 Cookies 来拥有一个持久的数据。如果只需要时间状态,可以使用 Vuex。
  • “时间状态”是什么意思?
  • 当您刷新页面 (F5) 时,数据将返回初始状态并且不保存。我的意思是暂时的,因为它们只有在你不重新加载 vue APP 时才会存在

标签: javascript vue.js vuejs3


【解决方案1】:

您可以将应用程序状态中的所有选定答案存储为数组。修改您的字段selectedAnswers,使其成为selectedAnswers = []。然后,当一个答案被选中时,将该答案存储在数组中,索引对应于问题索引。

下面我修改了你的代码来完成这个。我不太了解 vue 来弄清楚如何有条件地将样式应用于正确答案,所以我只是添加了一个列表项,上面写着 Your answer: ...。请注意,当您返回之前的问题时,您之前的答案会被保存。

修改此代码应该很简单,因此它不会渲染每个答案,而是使用不同的 css 类渲染已经选择的答案,以实现您正在寻找的内容。

const quizApp = {
  data() {
    return {
      isScoreVisible: false,
      questionCount: 0,
      selectedAnswers: [],
      correctAnswers: [],
      results: [{
          question: 'The book "The Little Prince" was written by...',
          correct_answer: "Antoine de Saint-Exupéry",
          selectedAnswer: "",
          incorrect_answers: [
            "Miguel de Cervantes Saavedra",
            "Jane Austen",
            "F. Scott Fitzgerald"
          ]
        },
        {
          question: "Which novel by John Grisham was conceived on a road trip to Florida while thinking about stolen books with his wife?",
          selectedAnswer: "",
          correct_answer: "Camino Island",
          incorrect_answers: ["Rogue Lawyer", "Gray Mountain", "The Litigators"]
        },
        {
          question: 'In Terry Pratchett\'s Discworld novel "Wyrd Sisters", which of these are not one of the three main witches?',
          selectedAnswer: "",
          correct_answer: "Winny Hathersham",
          incorrect_answers: [
            "Granny Weatherwax",
            "Nanny Ogg",
            "Magrat Garlick"
          ]
        }
      ]
    };
  },
  methods: {
    nextQuestion() {
      if (this.questionCount < this.results.length - 1) {
        this.questionCount++;
      }
    },
    prevQuestion() {
      if (this.questionCount >= 1) {
        this.questionCount--;
      }
    },
    checkAnswer(answer) {
      let answersList = document.querySelectorAll("ul.answers li");
      answersList.forEach(function(item) {
        item.removeAttribute("class");
      });

      // check if the clicked anwser is equal to the correct answer
      this.selectedAnswers[this.questionCount] = answer;
      if (answer == this.results[this.questionCount].correct_answer) {
        // Update the correct answers arary (make sure thare are no duplicates)
        if (this.correctAnswers.indexOf(answer) === -1) {
          this.correctAnswers.push(answer);
        }
        // Add correct answer class
        event.target.classList.add("text-white", "bg-success");
      } else {
        // Add incorrect answer
        event.target.classList.add("text-white", "bg-danger");
      }
    },
    showScore() {
      this.isScoreVisible = true;
    },
    resetQuiz() {
      this.questionCount = 0;
      this.correctAnswers = [];
      this.selectedAnswers = [];
      this.isScoreVisible = false;
    },
    removeElementFromArray(arr, elm) {
      return arr.filter((el) => el !== elm);
    },
    shuffle(arr) {
      var len = arr.length;
      var d = len;
      var array = [];
      var k, i;
      for (i = 0; i < d; i++) {
        k = Math.floor(Math.random() * len);
        array.push(arr[k]);
        arr.splice(k, 1);
        len = arr.length;
      }
      for (i = 0; i < d; i++) {
        arr[i] = array[i];
      }
      return arr;
    }
  },
  computed: {
    answers() {
      let incorrectAnswers = this.results[this.questionCount].incorrect_answers;
      let correctAnswer = this.results[this.questionCount].correct_answer;
      // return all answers, shuffled
      return this.shuffle(incorrectAnswers.concat(correctAnswer));
    },
    showScoreBtn() {
      return (
        this.questionCount + 1 == this.results.length &&
        this.selectedAnswers.length > 0
      );
    }
  }
};

Vue.createApp(quizApp).mount("#quiz_app");
#quiz_app {
  height: 100vh;
}

.container {
  flex: 1;
}

.logo {
  width: 30px;
}

.nav-item {
  width: 100%;
}

.category-name {
  font-size: 2rem;
  font-weight: 400;
  text-align: center;
}

.questions .card {
  width: 100%;
  min-height: 330px;
}

.questions .card-header {
  padding-top: 1.25rem;
  padding-bottom: 1.25rem;
}

.questions .card-footer {
  padding-top: 0.7rem;
  padding-bottom: 0.7rem;
}

.answers li {
  cursor: pointer;
  display: block;
  padding: 7px 15px;
  margin-bottom: 5px;
  border-radius: 6px;
  border: 1px solid rgba(0, 0, 0, 0.1);
  background: #fff;
}

.answers li:last-child {
  margin-bottom: 0;
}

.answers li:hover {
  background: #fafafa;
}

.pager {
  list-style-type: none;
  margin: 0;
  padding: 0;
  display: flex;
  justify-content: space-between;
}

.pager li > a {
  display: inline-block;
  padding: 5px 10px;
  text-align: center;
  width: 100px;
  background-color: #fff;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 999px;
  text-decoration: none !important;
  color: #fff;
}

.pager li > a.disabled {
  pointer-events: none;
  background-color: #9d9d9d !important;
}

.results a,
.results a:hover {
  color: #fff;
  font-weight: 500;
  border-radius: 999px;
}

.results a .fas {
  font-size: 85%;
  margin-right: 3px;
}

@media (min-width: 768px) {
  .nav-item {
    width: auto;
  }
  .questions .card {
    width: 67%;
  }
}

@media (min-width: 992px) {
  .questions .card {
    width: 50%;
  }
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://kit.fontawesome.com/3f9baea05e.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/vue@next"></script>

<div id="quiz_app" class="container d-flex flex-column justify-content-center my-3">
  <h2 class="display-4 category-name">Books</h2>
  <div class="questions d-flex flex-column align-items-center">
    <div v-if="results.length" class="card shadow-sm" :class="{'bg-light': isScoreVisible}">
      <div v-if="isScoreVisible" class="results my-auto text-center">
        <h5 class="mb-3">Your result: {{correctAnswers.length}} / {{results.length}}</h5>
        <a href="#" class="btn btn-md bg-dark" @click="resetQuiz">
          <i class="fas fa-sync-alt"></i> Play again
        </a>
      </div>
      <template v-else>
          <div class="card-header bg-light h6">
            {{results[questionCount].question}}
          </div>
          <div class="card-body">
            <ul class="answers list-unstyled m-0">
              <li v-for="answer in answers" :key="answer" @click="checkAnswer(answer)">
                {{answer}}
              </li>
              <li>
                Your answer: {{selectedAnswers[this.questionCount]}}
              </li>
            </ul>
          </div>
          <div class="card-footer bg-white">
            <ul class="pager">
              <li><a href="#" @click="prevQuestion" class="bg-dark" :class="{'disabled' : questionCount == 0}">Previous</a></li>
              <li class="d-flex align-items-center text-secondary font-weight-bold small">
                Question {{questionCount + 1}} of {{results.length}}
              </li>
              <li v-if="showScoreBtn">
                <a href="#" class="bg-dark" @click="showScore">Score</a>
              </li>
              <li v-else>
                <a href="#" class="bg-dark" :class="{'disabled' : typeof selectedAnswers[this.questionCount] == undefined}" @click="nextQuestion">Next</a>
              </li>
            </ul>
          </div>
        </template>
    </div>
  </div>
</div>

【讨论】:

  • 是的,这是一个不错的解决方案,但我想保留所选答案的视觉指示(着色)。
  • 就像我说的,一旦你在状态中选择了答案,就很容易根据它来渲染东西。我从未使用过 vue,但您需要比较答案是否与所选答案匹配,如果为真则使用不同的类渲染元素,而不是仅渲染 {{answer}}。也许你可以使用像clsx 这样的工具来有条件地渲染类。
  • 我试过了,还是不行。
【解决方案2】:

我查看了您的代码和测验行为,看起来您没有在此对象中设置 selectedAnswer 的值。当您调用 checkAnswer 时,还要在该对象中设置 selectAnswer 该答案。

{
  question: 'In Terry Pratchett\'s Discworld novel "Wyrd Sisters", which of these are not one of the three main witches?',
  selectedAnswer: "",
  correct_answer: "Winny Hathersham",
  incorrect_answers: [
        "Granny Weatherwax",
        "Nanny Ogg",
        "Magrat Garlick"
  ]
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-06-22
    • 1970-01-01
    • 2018-01-20
    • 1970-01-01
    • 1970-01-01
    • 2021-10-13
    • 2019-05-03
    相关资源
    最近更新 更多