从0开始,手把手教你用Vue.js开发一个答题App(下)

简介: 从0开始,手把手教你用Vue.js开发一个答题App(下)

1.4 运行项目

yarn run serve


image.png


2. 答题页面开发

image.png


2.1 修改路由


修改router/index.js:

import Vue from 'vue'
import VueRouter from 'vue-router'
import MainMenu from '../views/MainMenu.vue'
import GameController from '../views/GameController.vue'
Vue.use(VueRouter)
const routes = [
  {
    name: 'home',
    path: '/',
    component: MainMenu
  }, {
    name: 'quiz',
    path: '/quiz',
    component: GameController,
    props: (route) => ({ 
      number: route.query.number, 
      difficulty: route.query.difficulty, 
      category: route.query.category,
      type: route.query.type
    })
  }
]
const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})
export default router


2.2 答题页面


新增views/GameController.vue

本页面是本项目最重要的模块,展示问题,和处理用户提交的答案,简单解析一下:

1.fetchQuestions函数通过请求远程接口获得问题列表。

2.setQuestions保存远程回应的问题列表到本地数组。

3.onAnswerSubmit处理用户提交的选项,调用nextQuestion函数返回下一问题。

<template>
  <div class="h-100">
    <LoadingIcon v-if="loading"></LoadingIcon>
    <Question :question="currentQuestion" @answer-submitted="onAnswerSubmit" v-else></Question>
  </div>
</template>
<script>
import EventBus from '../eventBus'
import ShuffleMixin from '../mixins/shuffleMixin'
import Question from '../components/Question'
import LoadingIcon from '../components/LoadingIcon'
import axios from 'axios'
export default {
  name: 'GameController',
  mixins: [ShuffleMixin],
  props: {
    /** Number of questions */
    number: {
      default: '10',
      type: String,
      required: true
    },
    /** Id of category. Empty string if not included in query */
    category: String,
    /** Difficulty of questions. Empty string if not included in query */
    difficulty: String,
    /** Type of questions. Empty string if not included in query */
    type: String
  },
  components: {
    Question,
    LoadingIcon
  },
  data() {
    return {
      // Array of custom question objects. See setQuestions() for format
      questions: [],
      currentQuestion: {},
      // Used for displaying ajax loading animation OR form
      loading: true
    }
  },
  created() {
    this.fetchQuestions()
  },
  methods: {
    /** Invoked on created()
     * Builds API URL from query string (props).
     * Fetches questions from API.
     * "Validates" return from API and either routes to MainMenu view, or invokes setQuestions(resp).
     * @public
     */
    fetchQuestions() {
      let url = `https://opentdb.com/api.php?amount=${this.number}`
      if (this.category)   url += `&category=${this.category}`
      if (this.difficulty) url += `&difficulty=${this.difficulty}`
      if (this.type)       url += `&type=${this.type}`
      axios.get(url)
        .then(resp => resp.data)
        .then(resp => {
          if (resp.response_code === 0) {
            this.setQuestions(resp)
          } else {
            EventBus.$emit('alert-error', 'Bad game settings. Try another combination.')
            this.$router.replace({ name: 'home' })
          }
        })
    },
    /** Takes return data from API call and transforms to required object setup. 
     * Stores return in $root.$data.state.
     * @public
     */
    setQuestions(resp) {
      resp.results.forEach(qst => {
        const answers = this.shuffleArray([qst.correct_answer, ...qst.incorrect_answers])
        const question = {
          questionData: qst,
          answers: answers,
          userAnswer: null,
          correct: null
        }
        this.questions.push(question)
      })
      this.$root.$data.state = this.questions
      this.currentQuestion = this.questions[0]
      this.loading = false
    },
    /** Called on submit.
     * Checks if answer is correct and sets the user answer.
     * Invokes nextQuestion().
     * @public
     */
    onAnswerSubmit(answer) {
      if (this.currentQuestion.questionData.correct_answer === answer) {
        this.currentQuestion.correct = true
      } else {
        this.currentQuestion.correct = false
      }
      this.currentQuestion.userAnswer = answer
      this.nextQuestion()
    },
    /** Filters all unanswered questions, 
     * checks if any questions are left unanswered, 
     * updates currentQuestion if so, 
     * or routes to "result" if not.
     * @public
     */
    nextQuestion() {
      const unansweredQuestions = this.questions.filter(q => !q.userAnswer)
      if (unansweredQuestions.length > 0) {
        this.currentQuestion = unansweredQuestions[0]
      } else {
        this.$router.replace({ name: 'result' })
      }
    }
  }
}
</script>

新增\src\mixins\shuffleMixin.js

打乱问题答案,因为远程返回的答案有规律。mixins是混入的意思,可以混入到我们的某个页面或组件中,补充页面或组件功能,便于复用。

const ShuffleMixin = {
    methods: {
      shuffleArray: (arr) => arr
        .map(a => [Math.random(), a])
        .sort((a, b) => a[0] - b[0])
        .map(a => a[1])
    }
  }
  export default ShuffleMixin

新增src/components/Question.vue

<template>
  <div>
    <QuestionBody :questionData="question.questionData"></QuestionBody>
    <b-card-body class="pt-0">
      <hr>
      <b-form @submit="onSubmit">
        <b-form-group
          label="Select an answer:"
          class="text-left"
        >
          <b-form-radio 
            v-for="(ans, index) of question.answers" 
            :key="index" 
            v-model="answer" 
            :value="ans"
          >
            <div v-html="ans"></div>
          </b-form-radio>
        </b-form-group>
        <b-button type="submit" class="custom-success">Submit</b-button>
      </b-form>
    </b-card-body>
  </div>
</template>
<script>
import QuestionBody from './QuestionBody'
export default {
  name: 'Question',
  props: {
    /** Question object containing questionData, possible answers, and user answer information. */
    question: {
      required: true,
      type: Object
    }
  },
  components: {
    QuestionBody
  },
  data() {
    return {
      answer: null
    }
  },
  methods: {
    onSubmit(evt) {
      evt.preventDefault()
      if (this.answer) {
        /** Triggered on form submit. Passes user answer.
        * @event answer-submitted
        * @type {number|string}
        * @property {string}
        */
        this.$emit('answer-submitted', this.answer)
        this.answer = null
      }
    } 
  }
}
</script>

新增src/components/QuestionBody.vue


image.png


运行:

yarn run serve

启动成功:


image.png

如果能看到该页面,恭喜你,项目到此成功了。


2.3 至此项目目录结构


如果你走丢,请下载源码进行对比:


image.png

image.png

3 实现最终结果展示页面


image.png

image.png


再次修改router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import MainMenu from '../views/MainMenu.vue'
import GameController from '../views/GameController.vue'
import GameOver from '../views/GameOver'
Vue.use(VueRouter)
const routes = [
  ...
  {
    name: 'result',
    path: '/result',
    component: GameOver
  }
]
...

新增src/views/GameOver.vue:

<template>
  <div class="h-100">
      <b-card-header class="custom-info text-white font-weight-bold">Your Score: {{ score }} / {{ maxScore }}</b-card-header>
    <Answer v-for="(question, index) of questions" :key="index" :question="question"></Answer>
  </div>
</template>
<script>
import Answer from '../components/Answer'
export default {
  name: 'GameOver',
  components: {
    Answer
  },
  data() {
    return {
      questions: [],
      score: 0,
      maxScore: 0
    }
  },
  methods: {
    /** Invoked on created().
     * Grabs data from $root.$data.state.
     * Empties $root.$data.state => This is done to ensure data is cleared when starting a new game.
     * Invokes setScore().
     * @public
     */
    setQuestions() {
      this.questions = this.$root.$data.state || []
      this.$root.$data.state = []
      this.setScore()
    },
    /** Computes maximum possible score (amount of questions * 10)
     * Computes achieved score (amount of correct answers * 10)
     * @public
     */
    setScore() {
      this.maxScore = this.questions.length * 10
      this.score = this.questions.filter(q => q.correct).length * 10
    }
  },
  created() {
    this.setQuestions();
  }
}
</script>

新增src\components\Answer.vue

<template>
  <div>
    <b-card no-body class="answer-card rounded-0">
      <QuestionBody :questionData="question.questionData"></QuestionBody>
      <b-card-body class="pt-0 text-left">
        <hr class="mt-0">
        <b-card-text 
          class="px-2" 
          v-html="question.questionData.correct_answer"
        >
        </b-card-text>
        <b-card-text 
          class="px-2" 
          :class="{ 'custom-success': question.correct, 'custom-danger': !question.correct }"
          v-html="question.userAnswer"
        >
        </b-card-text>
      </b-card-body>
    </b-card>
  </div>
</template>
<script>
import QuestionBody from './QuestionBody'
export default {
  name: 'Answer',
  props: {
    /** Question object containing questionData, possible answers, and user answer information. */
    question: {
      required: true,
      type: Object
    }
  },
  components: {
    QuestionBody
  }
}
</script>
<style scoped>
.answer-card >>> .card-header {
  border-radius: 0;
}
</style>


3.1 运行项目

yarn run serve


image.png


3.2 项目结构

image.png

image.png


项目总结


很感谢您和豆约翰走到了这里,至此我们一个小型的Vue项目,全部开发完毕,下一期,豆约翰会带大家见识一个中型的项目,咱们循序渐进,一起加油。

本系列文章首发于作者的微信公众号[豆约翰],想尝鲜的朋友,请微信搜索关注。

有什么问题也可以加我微信[tiantiancode]一起讨论。


最后


为了将来还能找到我


image.png

目录
相关文章
|
11天前
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
141 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
4天前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
23 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
4天前
|
数据采集 JavaScript Android开发
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
29 7
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
11天前
|
机器学习/深度学习 存储 人工智能
MNN-LLM App:在手机上离线运行大模型,阿里巴巴开源基于 MNN-LLM 框架开发的手机 AI 助手应用
MNN-LLM App 是阿里巴巴基于 MNN-LLM 框架开发的 Android 应用,支持多模态交互、多种主流模型选择、离线运行及性能优化。
884 14
MNN-LLM App:在手机上离线运行大模型,阿里巴巴开源基于 MNN-LLM 框架开发的手机 AI 助手应用
|
8天前
|
安全 JavaScript 前端开发
小游戏源码开发之可跨app软件对接是如何设计和开发的
小游戏开发团队常需应对跨平台需求,为此设计了成熟的解决方案。流程涵盖游戏设计、技术选型、接口设计等。首先明确游戏功能与特性,选择合适的技术架构和引擎(如Unity或Cocos2d-x)。接着设计通用接口,确保与不同App的无缝对接,并制定接口规范。开发过程中实现游戏逻辑和界面,完成登录、分享及数据对接功能。最后进行测试优化,确保兼容性和性能,发布后持续维护更新。
|
10天前
|
前端开发 Java 测试技术
语音app系统软件源码开发搭建新手启蒙篇
在移动互联网时代,语音App已成为生活和工作的重要工具。本文为新手开发者提供语音App系统软件源码开发的启蒙指南,涵盖需求分析、技术选型、界面设计、编码实现、测试部署等关键环节。通过明确需求、选择合适的技术框架、优化用户体验、严格测试及持续维护更新,帮助开发者掌握开发流程,快速搭建功能完善的语音App。
|
27天前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
116 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
16天前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
121 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
24天前
|
Dart 前端开发 容器
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
75 18
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
14天前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
37 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex

热门文章

最新文章

  • 1
    当面试官再问我JS闭包时,我能答出来的都在这里了。
    49
  • 2
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    29
  • 3
    Node.js 中实现多任务下载的并发控制策略
    34
  • 4
    【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
    26
  • 5
    【JavaScript】深入理解 let、var 和 const
    49
  • 6
    【04】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架二次开发准备工作-以及建立初步后端目录菜单列-优雅草卓伊凡商业项目实战
    47
  • 7
    【03】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架搭建-服务端-后台管理-整体搭建-优雅草卓伊凡商业项目实战
    57
  • 8
    【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
    57
  • 9
    如何通过pm2以cluster模式多进程部署next.js(包括docker下的部署)
    72
  • 10
    【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
    57