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

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

项目演示


项目演示


项目源码


项目源码


配套讲解视频


配套讲解视频第一节

配套讲解视频第二节


教程说明


本教程适合对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.png


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


1.2 初始目录结构


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


image.png


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样式略,请参考源码。

<template>
  <div id="app" class="bg-light">
    <Navbar></Navbar>
    <b-alert :show="dismissCountdown" dismissible variant="danger" @dismissed="dismissCountdown = 0">
      {{ errorMessage }}
    </b-alert>
    <div class="d-flex justify-content-center">
      <b-card no-body id="main-card" class="col-sm-12 col-lg-4 px-0">
        <router-view></router-view>
      </b-card>
    </div>
  </div>
</template>
<script>
import EventBus from './eventBus'
import Navbar from './components/Navbar'
export default {
  name: 'app',
  components: {
    Navbar
  },
  data() {
    return {
      errorMessage: '',
      dismissSecs: 5,
      dismissCountdown: 0
    }
  },
  methods: {
    showAlert(error) {
      this.errorMessage = error
      this.dismissCountdown = this.dismissSecs
    }
  },
  mounted() {
    EventBus.$on('alert-error', (error) => {
      this.showAlert(error)
    })
  },
  beforeDestroy() {
    EventBus.$off('alert-error')
  }
}
</script>


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


image.png


<template>
    <b-navbar id="navbar" class="custom-info" type="dark" sticky>
      <b-navbar-brand id="nav-logo" :to="{ name: 'home' }">Vue-Quiz</b-navbar-brand>
      <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>
</template>
<script>
export default {
  name: 'Navbar'
}
</script>
<style scoped>
</style>

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组件。

<template>
<div>
  <b-card-header class="custom-info text-white font-weight-bold">New Game</b-card-header>
  <b-card-body class="h-100">
    <GameForm @form-submitted="handleFormSubmitted"></GameForm>
  </b-card-body>
</div>
</template>
<script>
import GameForm from '../components/GameForm'
export default {
  name: 'MainMenu',
  components: {
    GameForm
  },
  methods: {
    /** Triggered by custom 'form-submitted' event from GameForm child component. 
     * Parses formData, and route pushes to 'quiz' with formData as query
     * @public
     */
    handleFormSubmitted(formData) {
      const query = formData
      query.difficulty = query.difficulty.toLowerCase()
      this.$router.push({ name: 'quiz', query: query })
    }
  }
}
</script>

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


image.png

<template>
  <div>
    <LoadingIcon v-if="loading"></LoadingIcon>
    <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>
  </div>
</template>
<script>
import LoadingIcon from './LoadingIcon'
import axios from 'axios'
export default {
  components: {
    LoadingIcon
  },
  data() {
    return {
      // Form data, tied to respective inputs
      form: {
        number: '',
        category: '',
        difficulty: '',
        type: ''
      },
      // Used for form dropdowns and number input
      categories: [{ text: 'Category', value: '' }],
      difficulties: [{ text: 'Difficulty', value: '' }, 'Easy', 'Medium', 'Hard'],
      types: [
        { text: 'Type', value: '' }, 
        { text: 'Multiple Choice', value: 'multiple' }, 
        { text: 'True or False', value: 'boolean'}
      ],
      minQuestions: 10,
      maxQuestions: 20,
      // Used for displaying ajax loading animation OR form
      loading: true
    }
  },
  created() {
    this.fetchCategories()
  },
  methods: {
    fetchCategories() {
      axios.get('https://opentdb.com/api_category.php')
      .then(resp => resp.data)
      .then(resp => {
        resp.trivia_categories.forEach(category => {
          this.categories.push({text: category.name, value: `${category.id}`})
        });
        this.loading = false;
      })
    },
    onSubmit(evt) {
      evt.preventDefault()
       /** Triggered on form submit. Passes form data
        * @event form-submitted
        * @type {number|string}
        * @property {object}
        */
      this.$emit('form-submitted', this.form)
    }
  }
}
</script>

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

axios.get('https://opentdb.com/api_category.php')

新增src/components/LoadingIcon.vue,在异步请求数据未返回时,渲染等待图标。

<template>
  <div id="loading-icon" class="h-100 d-flex justify-content-center align-items-center">
    <img src="@/assets/ajax-loader.gif" alt="Loading Icon">
  </div>
</template>
<script>
export default {
  name: 'LoadingIcon'
}
</script>

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

目录
相关文章
|
4天前
|
数据采集 JavaScript Android开发
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
27 7
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
2月前
|
Web App开发 JavaScript 前端开发
Node.js开发
Node.js开发
78 13
|
2月前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
本文将带领读者从零基础开始,一步步深入到Node.js后端开发的精髓。我们将通过通俗易懂的语言和实际代码示例,探索Node.js的强大功能及其在现代Web开发中的应用。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的见解和技巧,让你的后端开发技能更上一层楼。
|
3月前
|
Web App开发 开发框架 JavaScript
深入浅出Node.js后端开发
本文将带你领略Node.js的魅力,从基础概念到实践应用,一步步深入理解并掌握Node.js在后端开发中的运用。我们将通过实例学习如何搭建一个基本的Web服务,探讨Node.js的事件驱动和非阻塞I/O模型,以及如何利用其强大的生态系统进行高效的后端开发。无论你是前端开发者还是后端新手,这篇文章都会为你打开一扇通往全栈开发的大门。
|
3月前
|
JavaScript 前端开发 API
深入理解Node.js事件循环及其在后端开发中的应用
本文旨在揭示Node.js的核心特性之一——事件循环,并探讨其对后端开发实践的深远影响。通过剖析事件循环的工作原理和关键组件,我们不仅能够更好地理解Node.js的非阻塞I/O模型,还能学会如何优化我们的后端应用以提高性能和响应能力。文章将结合实例分析事件循环在处理大量并发请求时的优势,以及如何避免常见的编程陷阱,从而为读者提供从理论到实践的全面指导。
|
3月前
|
存储 JavaScript 前端开发
深入浅出Node.js后端开发
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将以Node.js为例,深入探讨其背后的哲学思想、核心特性以及在实际项目中的应用,旨在为读者揭示Node.js如何优雅地处理高并发请求,并通过实践案例加深理解。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和思考。
|
3月前
|
Web App开发 开发框架 JavaScript
深入浅出Node.js后端开发
在这篇文章中,我们将一起探索Node.js的奇妙世界。无论你是刚接触后端开发的新手,还是希望深化理解的老手,这篇文章都适合你。我们将从基础概念开始,逐步深入到实际应用,最后通过一个代码示例来巩固所学知识。让我们一起开启这段旅程吧!
|
3月前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
本文将带你走进Node.js的世界,从基础到进阶,逐步解析Node.js在后端开发中的应用。我们将通过实例来理解Node.js的异步特性、事件驱动模型以及如何利用它处理高并发请求。此外,文章还会介绍如何搭建一个基本的Node.js服务器,并探讨如何利用现代前端框架与Node.js进行交互,实现全栈式开发。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和深入的理解。
90 4
|
3月前
|
前端开发 JavaScript 关系型数据库
基于 Vue2.0 + Nest.js 全栈开发的后台应用
Vue2 Admin 是一个基于 Vue2 和 Ant Design Pro 开发的前端项目,配合 Nest.js 构建的后端,提供了一个完整的全栈后台应用解决方案。该项目支持动态国际化、用户权限管理、操作日志记录等功能,适合全栈开发者学习参考。线上预览地址:https://vue2.baiwumm.com/,用户名:Admin,密码:abc123456。
|
3月前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
66 2

热门文章

最新文章

  • 1
    当面试官再问我JS闭包时,我能答出来的都在这里了。
    43
  • 2
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    27
  • 3
    Node.js 中实现多任务下载的并发控制策略
    33
  • 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技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
    55