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

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

时间2023-07-09 07:16:01发布访客分类VUE浏览1263
导读:项目演示项目演示项目源码项目源码配套讲解视频配套讲解视频第一节配套讲解视频第二节教程说明本教程适合对Vue基础知识有一点了解,但不懂得综合运用,还未曾使用Vue从头开发过一个小型App的读者。本教程不对所有的Vue知识点进行讲解,而是手把手...

项目演示


项目演示


项目源码


项目源码


配套讲解视频


配套讲解视频第一节

配套讲解视频第二节


教程说明


本教程适合对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等待动画文件,请参考项目源码。

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!


若转载请注明出处: 从0开始,手把手教你用Vue.js开发一个答题App(上)
本文地址: https://pptw.com/jishu/297693.html
VS Code配置snippets代码片段快速生成html模板,提高前端编写效率 从0开始,手把手教你用Vue.js开发一个答题App(下)

游客 回复需填写必要信息