欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

从0开始,手把手教你用Vue开发一个答题App

程序员文章站 2024-02-18 13:48:52
...

项目演示
项目演示

项目源码
项目源码

教程说明
本教程适合对Vue基础知识有一点了解,但不懂得综合运用,还未曾使用Vue从头开发过一个小型App的读者。本教程不对所有的Vue知识点进行讲解,而是手把手一步步从0到1,做出一个完整的小项目。目前网上的教程不是只有零散的知识点讲解;就是抛出一个开源的大项目,初级读者下载下来后,运行起来都很费劲,更谈不上理解这个项目是如何一步步开发出来的了。本教程试图弥补这个空白。

  1. 项目初始化
    1.1使用 Vue CLI 创建项目
    如果你还没有安装 VueCLI,请执行下面的命令安装或是升级:

npm install --global @vue/cli
在命令行中输入以下命令创建 Vue 项目:

vue create vue-quiz
Vue CLI v4.3.1
? Please pick a preset:

default (babel, eslint)
Manually select features
default:默认勾选 babel、eslint,回车之后直接进入装包

manually:自定义勾选特性配置,选择完毕之后,才会进入装包

选择第 1 种 default.

安装结束,命令提示你项目创建成功,按照命令行的提示在终端中分别输入:

进入你的项目目录

cd vue-quiz

启动开发服务

npm run serve
启动成功,命令行中输出项目的 http 访问地址。 打开浏览器,输入其中任何一个地址进行访问

image-20200707121732592

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

1.2 初始目录结构
项目创建好以后,下面我们来了解一下初始目录结构:

image-20200707122944401

1.3 调整初始目录结构,实现游戏设置页面
默认生成的目录结构不满足我们的开发需求,所以需要做一些自定义改动。

这里主要处理下面的内容:

删除初始化的默认文件
新增调整我们需要的目录结构
删除默认示例文件:

src/components/HelloWorld.vue
src/assets/logo.png
修改package.json,添加项目依赖:

“dependencies”: {
“axios”: “^0.19.2”,
“bootstrap”: “^4.4.1”,
“bootstrap-vue”: “^2.5.0”,
“core-js”: “^3.6.5”,
“vue”: “^2.6.11”,
“vue-router”: “^3.1.5”
},
“devDependencies”: {
“@vue/cli-plugin-babel”: “~4.4.0”,
“@vue/cli-plugin-eslint”: “~4.4.0”,
“@vue/cli-plugin-router”: “~4.4.0”,
“@vue/cli-service”: “~4.4.0”,
“babel-eslint”: “^10.1.0”,
“eslint”: “^6.7.2”,
“eslint-plugin-vue”: “^6.2.2”,
“vue-template-compiler”: “^2.6.11”
},
然后运行yarn install,安装依赖。

修改项目入口文件main.js,引入bootstrap-vue。

import Vue from ‘vue’
import App from ‘./App.vue’
import router from ‘./router’
import BootstrapVue from ‘bootstrap-vue’
import ‘bootstrap/dist/css/bootstrap.css’
import ‘bootstrap-vue/dist/bootstrap-vue.css’

Vue.config.productionTip = false

Vue.use(BootstrapVue)

const state = { questions: [] }

new Vue({
router,
data: state,
render: h => h(App)
}).$mount(’#app’)

定义一个state对象来共享答题数据(答题页面和结果页面共享)

const state = { questions: [] }
src目录下新增eventBus.js消息总线,用来在组件间传递消息,代码如下:

import Vue from ‘vue’
const EventBus = new Vue()
export default EventBus
修改App.vue,css样式略,请参考源码。

新增components/Navbar.vue,定义导航部分。

image-20200707125506858

Vue-Quiz
  <b-navbar-nav class="ml-auto">
    <b-nav-item :to="{ name: 'home' }">New Game </b-nav-item>
    <b-nav-item href="#" target="_blank">About</b-nav-item>
  </b-navbar-nav>
</b-navbar>

src目录下新增router/index.js,定义首页路由。

import Vue from ‘vue’
import VueRouter from ‘vue-router’
import MainMenu from ‘…/views/MainMenu.vue’

Vue.use(VueRouter)

const routes = [
{
name: ‘home’,
path: ‘/’,
component: MainMenu
}
]

const router = new VueRouter({
mode: ‘history’,
base: process.env.BASE_URL,
routes
})

export default router

src下新增views/MainMenu.vue,MainMenu主要包含GameForm组件。

New Game

新增src/components/GameForm.vue,实现游戏初始设置。

image-20200707125814786

<div v-else>
  <b-form @submit="onSubmit">
    <b-form-group 
      id="input-group-number-of-questions"
      label="Select a number"
      label-for="input-number-of-questions"
      class="text-left"
    >
      <b-form-input
        id="input-number-of-questions"
        v-model="form.number"
        type="number"
        :min="minQuestions"
        :max="maxQuestions"
        required 
        :placeholder="`Between ${minQuestions} and ${maxQuestions}`"
      ></b-form-input>
    </b-form-group>

    <b-form-group id="input-group-category">
      <b-form-select
        id="input-category"
        v-model="form.category"
        :options="categories"
      ></b-form-select>
    </b-form-group>

    <b-form-group id="input-group-difficulty">
      <b-form-select
        id="input-difficulty"
        v-model="form.difficulty"
        :options="difficulties"
      ></b-form-select>
    </b-form-group>

    <b-form-group id="input-group-type">
      <b-form-select
        id="input-type"
        v-model="form.type"
        :options="types"
      ></b-form-select>
    </b-form-group>

    <b-button type="submit" class="custom-success">Submit</b-button>
  </b-form>
</div>

GameForm组件,主要通过axios发起获取全部题目分类请求:

axios.get(‘https://opentdb.com/api_category.php’)
新增src/components/LoadingIcon.vue,在异步请求数据未返回时,渲染等待图标。

从0开始,手把手教你用Vue开发一个答题App

新增src/assets/ajax-loader.gif等待动画文件,请参考项目源码。

1.4 运行项目
yarn run serve
image-20200707130702456

  1. 答题页面开发
    image-20200708083506391

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函数返回下一问题。

新增\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
https://www.thefreedictionary.com/%e5%8d%8e%e7%ba%b3%e5%bc%80%e6%88%b7%e5%be%ae%e4%bf%a1-18183615678

<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>

https://www.thefreedictionary.com/%e5%8d%8e%e7%ba%b3%e6%80%bb%e5%85%ac%e5%8f%b8%e5%ae%a2%e6%9c%8d%e5%be%ae%e4%bf%a1-18183615678
新增src/components/QuestionBody.vue

image-20200708083544511

{{ questionData.category }}
{{ questionData.difficulty }}
Simple component displaying question category, difficulty and question text. Used on both Question component and Answer component. 运行:

yarn run serve
启动成功:

image-20200708083506391

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

2.3 至此项目目录结构
如果你走丢,请下载源码进行对比:

image-20200708084009828

3 实现最终结果展示页面
image-20200708084853745

再次修改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:

Your Score: {{ score }} / {{ maxScore }}

新增src\components\Answer.vue


3.1 运行项目
yarn run serve

3.2 项目结构
image-20200708085222248

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