#16. Quiz App(Vue版)

本日任务说明

继昨天完成的Quiz App(原生JS版)後,这次要来看的是Vue版:Demo Link | CodePen(原生JS版)

Vue版和原生JS版本不同的地方

  1. CSS全部倚赖tailwind css来完成,并且加上答题的进度条,增加一点Hover效果
  2. 选单部分原本是要点击 tag,然後submit送出。在Vue版,点击就会直接跳到下一题。

Vue档案部分有三个

  1. src/views/QuizApp.vue
  2. src/components/quiz-app/Questions.vue
  3. src/components/quiz-app/Result.vue
    原则上2, 3是作为子元件,放在QuizApp.vue当中。

程序码说明

src/views/QuizApp.vue

<template>
	<home-btn />
	<main
		class="
			bg-gradient-to-tl
			from-blue-200
			via-yellow-100
			to-gray-300
			w-[100vw]
			flex
			justify-center
			items-center
			overflow-hidden
			mr-0
		"
	>
		<div
			class="bg-white rounded-lg w-[600px] drop-shadow-lg"
			id="quiz-container"
		>
			<div id="quiz-header" class="p-12 flex flex-col justify-around">
				<questions
                    // 答题数小於题库长度时才显示子元件
					v-if="quizAnswered < quizData.length"
                    // 将题库quizData送进子元件
					:quizData="quizData"
                    // 将答题数送进子元件
					:quizAnswered="quizAnswered"
                    // 将属性check-answer送进子元件,当子元件emit上来时,便会触发函式checkAnswer
					@check-answer="checkAnswer"
				/>
                // 子元件questions不显示时便显示result
				<result v-else :score="score" />
				<button
					id="Reset"
					class="
						bg-purple-600
						hover:bg-purple-800
						text-white
						tracking-wide
						font-normal
						w-[15%]
						cursor-pointer
                        mt-3
						rounded-3xl
					"
                    // 当答题数等於题库阵列长度时,才显示reset按钮
					v-if="quizAnswered === quizData.length"
                    // 点击按钮,触发reset,回到第一题。
					@click.prevent="reset"
				>
					Reset
				</button>
			</div>
		</div>
	</main>
</template>

<script>
import homeBtn from '../components/HomeBtn.vue'
import Questions from '../components/quiz-app/Questions.vue'
import Result from '../components/quiz-app/Result.vue'
import { ref } from 'vue'

export default {
	name: '#15. Quiz App',
	components: {
		homeBtn,
		Questions,
		Result,
	},
	setup() {
        // 建立题库
		const quizData = ref([
			{
				q: 'Which language runs in a web browser?',
				answers: [
					{
						text: 'Java',
						is_correct: false,
					},
					{
						text: 'C',
						is_correct: false,
					},
					{
						text: 'Python',
						is_correct: false,
					},
					{
						text: 'Javascript',
						is_correct: true,
					},
				],
			},
			// ...略(共四题)
		])

		const quizAnswered = ref(0)
		const score = ref(0)
        
        // 若选中的answer.is_correct为true,则score + 1
		const checkAnswer = (is_correct) => {
			if (is_correct) {
				score.value++
			}
            // 判断是否要给分後,进到下一题
			quizAnswered.value++
		}
        // 重设分数与答题数
		const reset = () => {
			score.value = 0
			quizAnswered.value = 0
		}

		return {
			quizData,
			quizAnswered,
			score,
			checkAnswer,
			reset,
		}
	},
}
</script>

src/components/quiz-app/Questions.vue

<template>
		<div
			class="p-0 flex flex-col"
			id="single-question"
            // 这里使用v-for遍历题库,将资料渲染进模板中
			v-for="(question, index) in quizData"
            // 只有目前要作答的题目才会显示
			v-show="quizAnswered === index"
            :key="question.q"
		>
			<h2 class="text-center mb-3 text-3xl font-semibold">
                // 对照题库资料结构,显示题目
				{{ question.q }}
			</h2>
			<div
				class="
					text-xl
					font-thin
					text-center
					my-2
					hover:bg-gray-100 hover:font-normal
					rounded-sm
				"
                // 按照资料结构,每个题目中还有一个阵列answers,在此也用v-for渲染出来
				v-for="answer in question.answers"
				:key="answer.text"
                // 被选中的答案,会把answer里的is_correct带进函式selectAnswer
				@click.prevent="selectAnswer(answer.is_correct)"
			>
				<div class="cursor-pointer py-3 border-2 rounded-lg">
                // 对照题库资料结构,显示答案选项
					{{ answer.text }}
				</div>
			</div>
		</div>
	<div class="progress mt-2 h-[40px] relative bg-pink-50">
		<div
			class="bar bg-pink-300 h-[40px] transition-all duration-300"
            // 这里运用答题数与题库阵列长度的运算,即时改变style所绑定的width
            // 达成进度条显示效果
			:style="{ width: `${(quizAnswered / quizData.length) * 100}%` }"
		></div>
		<div class="status absolute top-[10px] w-full left-0 text-center">
			{{ quizAnswered }} out of {{ quizData.length }} quiz answered
		</div>
	</div>
</template>

<script>
export default {
	name: 'Questions',
    // 用emits引入父元件的check-answer
	emits: ['check-answer'],
    // 从父元件引入props资料quizData和quizAnswered
	props: {
		quizData: {
			type: Object,
			required: true,
		},
		quizAnswered: {
			type: Number,
			required: true,
		},
	},
	setup(props, { emit }) {
    // 建立selectAnswer函式,并引入参数is_correct,然後透过emit中的check-answer,将is_correct送到父元件,来执行父元件的checkAnswer函式
    const selectAnswer = (is_correct) => {
      emit('check-answer', is_correct)
    }
	return {
      selectAnswer
    }
  },
}
</script>

src/components/quiz-app/Result.vue

Result.vue部分还有一些地方可以优化,之後会再进一步调整。

<template>
	<div class="result">
		<div class="title">Glad you finish this quiz! </div>
        // 这里单纯从父元件取得score,然後渲染进模板中
		<div class="desc">Your score is: {{ score }} / 4</div>
	</div>
</template>

<script>

export default {
	name: 'results',
	props: {
		score: {
			type: Number,
			required: true,
		}
	},
	setup() {
	},
}
</script>

写文章写一半发现忘了时间,PO完这篇已经过了6秒,今年铁人赛没挑战成功...Orz
没关系继续努力!


<<:  【Day 02】- 网路爬虫环境设定(Python、pipenv、Vscode)

>>:  Day 2 - Home Lab 事前准备 - 安装篇

Day 01:Hello Computer Science!

前言 初次参加铁人赛,开赛第一天,先放轻松暖个身,把前辈的文章看过一遍吧! 相关文章 Mike Fa...

【系统程序】2.1基本组译器功能

2.1基本组译器功能 2.1.1简易SIC组译器 组译器的两阶段处理 1.扫描原始程序中的标记,并计...

再谈中断与异常

想知道我们在使用滑鼠操作电脑时作业系统在背後做了什麽事情吗? 又或者为什麽我们在写 C 语言时,老师...

Day 19:二元树遍历 Binary Tree Traversal

今天一起来认识二元树的三种遍历方式吧! 但是别急!我们先来认识二元搜寻树BST的定义! 二元搜寻树是...

【Day27】建立一个 QA Bot

今天要来跟各位一起解析 QnA Maker Bot,以下简称 QA Bot。 今天是参考 官方范例程...