66. Rendering the Questions
npm create vue@latest
이 강의에서는 퀴즈 애플리케이션을 만드는 과정을 계속 진행합니다.
먼저 HTML과 CSS를 추가한 후, 이제 애플리케이션에 기능적인 로직을 추가하는 단계입니다.
애플리케이션을 두 개의 주요 섹션으로 나누어 각각을 별도의 컴포넌트로 만들고자 합니다.
이 두 섹션은 질문 컨테이너와 결과 부분입니다.
각 컴포넌트에 필요한 데이터를 전달하기 위해, app 컴포넌트에서 질문과 결과 데이터를 정의하고, 이를 하위 컴포넌트로 전달합니다.
그 다음, 각 질문과 답변을 루프를 통해 렌더링하고, 사용자가 답변을 선택할 때마다 진행 상태를 추적하는 로직을 구현합니다.
이 과정을 통해 퀴즈 애플리케이션의 기본적인 기능을 구축하는 것이 이번 강의의 목표입니다.
src/main.ts
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
src/App.vue
<script setup lang="ts">
import {ref} from "vue";
import Questions from "@/components/Questions.vue";
import Result from "@/components/Result.vue";
const questionsAnswered = ref(0);
const questions = ref([
{
q: 'What is 2 + 2?',
answers: [
{
text: '4',
is_correct: true
},
{
text: '3',
is_correct: false
},
{
text: 'Fish',
is_correct: false
},
{
text: '5',
is_correct: false
}
]
},
{
q: 'How many letters are in the word "Banana"?',
answers: [
{
text: '5',
is_correct: false
},
{
text: '7',
is_correct: false
},
{
text: '6',
is_correct: true
},
{
text: '12',
is_correct: false
}
]
},
{
q: 'Find the missing letter: C_ke',
answers: [
{
text: 'e',
is_correct: false
},
{
text: 'a',
is_correct: true
},
{
text: 'i',
is_correct: false
}
]
},
])
const results = ref([
{
min: 0,
max: 2,
title: "Try again!",
desc: "Do a little more studying and you may succeed!"
},
{
min: 3,
max: 3,
title: "Wow, you're a genius!",
desc: "Studying has definitely paid off for you!"
}
])
</script>
<template>
<div class="ctr">
<Questions v-if="questionsAnswered < questions.length" :questions="questions" />
<Result v-else />
<button type="button" class="reset-btn">Reset</button>
</div>
</template>
src/components/Questions.vue
<script setup lang="ts">
const props = withDefaults(
defineProps<{
questions: Array<{
q: string
answers: Array<{
text: string
is_correct: boolean
}>
}>
}>(),
{}
)
</script>
<template>
<div class="questions-ctr">
<div class="progress">
<div class="bar"></div>
<div class="status">1 out of 3 questions answered</div>
</div>
<div
class="single-question"
v-for="question in questions"
:key="question.q"
>
<div class="question">{{ question.q }}</div>
<div class="answers">
<div
class="answer"
v-for="answer in question.answers"
:key="answer.text"
>
{{ answer.text }}
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>
src/components/Result.vue
<script setup lang="ts">
</script>
<template>
<div class="result">
<div class="title">You got sample result 1!</div>
<div class="desc">
Enter a short description here about the result.
</div>
</div>
</template>
<style scoped>
</style>
src/assets/main.css
* {
box-sizing: border-box;
}
body {
font-size: 20px;
font-family: sans-serif;
padding-top: 20px;
background: #e6ecf1;
}
.ctr {
margin: 0 auto;
max-width: 600px;
width: 100%;
box-sizing: border-box;
position: relative;
}
.questions-ctr {
position: relative;
width: 100%;
}
.question {
width: 100%;
padding: 20px;
font-size: 32px;
font-weight: bold;
text-align: center;
background-color: #00ca8c;
color: #fff;
box-sizing: border-box;
}
.single-question {
position: relative;
width: 100%;
}
.answer {
border: 1px solid #8e959f;
padding: 20px;
font-size: 18px;
width: 100%;
background-color: #fff;
transition: 0.2s linear all;
}
.answer span {
display: inline-block;
margin-left: 5px;
font-size: 0.75em;
font-style: italic;
}
.progress {
height: 50px;
margin-top: 10px;
background-color: #ddd;
position: relative;
}
.bar {
height: 50px;
background-color: #ff6372;
transition: all 0.3s linear;
}
.status {
position: absolute;
top: 15px;
left: 0;
text-align: center;
color: #fff;
width: 100%;
}
.answer:not(.is-answered) {
cursor: pointer;
}
.answer:not(.is-answered):hover {
background-color: #8ce200;
border-color: #8ce200;
color: #fff;
}
.title {
width: 100%;
padding: 20px;
font-size: 32px;
font-weight: bold;
text-align: center;
background-color: #12cbc4;
color: #fff;
box-sizing: border-box;
}
.desc {
border: 1px solid #8e959f;
padding: 20px;
font-size: 18px;
width: 100%;
background-color: #fff;
transition: 0.4s linear all;
text-align: center;
}
.fade-enter-from {
opacity: 0;
}
.fade-enter-active {
transition: all 0.3s linear;
}
.fade-leave-active {
transition: all 0.3s linear;
opacity: 0;
position: absolute;
}
.fade-leave-to {
opacity: 0;
}
.reset-btn {
background-color: #ff6372;
border: 0;
font-size: 22px;
color: #fff;
padding: 10px 25px;
margin: 10px auto;
display: block;
}
.result{
width: 100%;
}
.reset-btn:active, .reset-btn:focus, .reset-btn:hover{
border: 0;
outline: 0;
}