iT邦幫忙

2021 iThome 鐵人賽

DAY 15
0
Modern Web

30天30個前端任務系列 第 16

#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
沒關係繼續努力!


上一篇
#16. Quiz App(原生JS版)
下一篇
#17. Image Carousel(原生JS版)
系列文
30天30個前端任務19

尚未有邦友留言

立即登入留言