从0开始,手把手教你用Vue开发一个答题App01之项目创建及答题设置页面开发
项目演示
项目源码
教程说明
本教程适合对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 访问地址。 打开浏览器,输入其中任何一个地址进行访问

如果能看到该页面,恭喜你,项目创建成功了。
1.2 初始目录结构
项目创建好以后,下面我们来了解一下初始目录结构:

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,定义导航部分。

<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,实现游戏初始设置。

<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等待动画文件,请参考项目源码。
1.4 运行项目
yarn run serve

从0开始,手把手教你用Vue开发一个答题App01之项目创建及答题设置页面开发的更多相关文章
- 手把手教你从零写一个简单的 VUE
本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...
- 手把手教你用vue-cli构建一个简单的路由应用
上一章说道:十分钟上手-搭建vue开发环境(新手教程)https://www.jianshu.com/p/0c6678671635 开发环境搭建好之后,那么开始新添加一些页面,构建最基本的vue项目, ...
- 手把手教你从零写一个简单的 VUE--模板篇
教程目录1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 Hello,我又回来了,上一次的文章教会了大家如何书写一个简单 VUE,里面实现了VUE 的数据驱动视图 ...
- 手把手教你用redis实现一个简单的mq消息队列(java)
众所周知,消息队列是应用系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构.目前使用较多的消息队列有 ActiveMQ,RabbitMQ,Zero ...
- 手把手教你使用 js 实现一个 Canvas 编辑器
手把手教你使用 js 实现一个 Canvas 编辑器 拖拽 缩放,等比缩放 导出 image 模版 撤销,重做 OOP,封装,继承,多态 发布库 CI/CD (gitlab/github) ... h ...
- 【Django笔记0】-Django项目创建,settings设置,运行
Django项目创建,settings设置,运行 1,项目创建 通过pip下载Django以后,在cmd中cd到想要创建项目的路径,之后输入: django-admin startproject ...
- 从0开始,手把手教你用Vue开发一个答题App
项目演示 项目演示 项目源码 项目源码 教程说明 本教程适合对Vue基础知识有一点了解,但不懂得综合运用,还未曾使用Vue从头开发过一个小型App的读者.本教程不对所有的Vue知识点进行讲解,而是手把 ...
- 手把手教你用Vue造轮子(3):开发可排序的表格组件
前言 最近闰土大叔跟Vue干上了,没办法,公司业务驱动,不用Vue没招啊,leader尝到了前后端分离带来的好处,除非你离职,哈哈哈,当然,那是不可能的,对于我这种要攒钱买房子的人来说.那还说什么呢, ...
- 手把手教你使用VUE+SpringMVC+Spring+Mybatis+Maven构建属于你自己的电商系统之vue后台前端框架搭建——猿实战01
猿实战是一个原创系列文章,通过实战的方式,采用前后端分离的技术结合SpringMVC Spring Mybatis,手把手教你撸一个完整的电商系统,跟着教程走下来,变身猿人找到工作不是 ...
随机推荐
- python—异常处理
一:什么是异常? (异常就是程序运行时发生错误的信号) 错误分两种: 1.语法错误 2.逻辑错误 二:异常的种类? (在python中不同的异常可以用不同的类型(python中统一了类与类型,类型即类 ...
- mysql基础-数据库表简单查询-记录(五)
0x01 MySQL的查询操作 单表查询:简单查询 多表查询:连续查询 联合查询 选择和投影 投影:挑选要符合的字段 select ...
- Yangcs从简书搬回来了
追求更加畅快淋漓的书写体验: 简书地址: http://www.jianshu.com/users/9913981cb400/latest_articles. Yangcs在简书[2016] 简书已经 ...
- 学习nginx从入门到实践(四) 基础知识之nginx基本配置语法
nginx基本配置语法 1.http相关 展示每次请求的请求头: curl -v http://www.baidu.com 2.nginx日志类型 error.log. access.log log_ ...
- 50道Java集合经典面试题(收藏版)
前言 来了来了,50道Java集合面试题也来啦~ 已经上传github: https://github.com/whx123/JavaHome 1. Arraylist与LinkedList区别 可以 ...
- 【 哈希和哈希表】Three Friends【进制哈希】
Three Friends 传送门:链接 (UPC)或 链接(大视野) 题目描述 Three friends like to play the following game. The first f ...
- 【01JMeter基础】线程组
线程组 我们存在接口请求的地方,在JMeter中我们使用最多的模块,分为 setUp线程组.线程组.tearDown线程组 setUp线程组:不论如何排序,都会在所有的线程组中被最早执行,如果有多个s ...
- 《Java并发编程的艺术》 第9章 Java中的线程池
第9章 Java中的线程池 在开发过程中,合理地使用线程池能带来3个好处: 降低资源消耗.通过重复利用已创建的线程 降低线程创建和销毁造成的消耗. 提高响应速度.当任务到达时,任务可以不需要等到线程创 ...
- 在群晖NAS上运行URLOS之后竟然能安装Discuz! Q!!
如果我们手头上有1台群晖NAS时,有没有考虑过把群晖NAS当成服务器来使用,这样会不会很有意思呢? 现在,我们终于可以尝试一番了,把群晖NAS变成一台实实在在的服务器,在上面跑各种运行环境!其实很简单 ...
- CODING DevOps 系列第四课:DevOps 中的质量内建实践
什么是质量内建 随着时间的推移,我们项目的开发效率会逐渐降低,直到几年之后整个项目可能就无法维护,只能推倒重来.具体的表现首先就是随着时间推移,我们会发现整个需求列表里面能做的需求越来越少,因为每当我 ...