feat: Implement comprehensive quiz functionality, new browse pages, and a classroom learning page with i18n support.
This commit is contained in:
parent
b96f85e650
commit
e082c77946
6 changed files with 234 additions and 107 deletions
|
|
@ -143,5 +143,37 @@
|
|||
"invalidPhone": "Invalid phone number",
|
||||
"passwordTooShort": "At least 6 characters",
|
||||
"passwordsDoNotMatch": "Passwords do not match"
|
||||
},
|
||||
"classroom": {
|
||||
"backToDashboard": "Back to My Courses",
|
||||
"loadingCurriculum": "Loading curriculum...",
|
||||
"readingMaterial": "This lesson is a reading material.",
|
||||
"lessonContent": "Lesson Content",
|
||||
"notAvailable": "This lesson is not yet available.",
|
||||
"loadingTitle": "Loading...",
|
||||
"chapter": "Chapter",
|
||||
"lessons": "Lessons"
|
||||
},
|
||||
"quiz": {
|
||||
"exitTitle": "Exit Quiz",
|
||||
"exitConfirm": "You are taking a quiz. If you leave now, your progress will be lost. Are you sure you want to exit?",
|
||||
"startTitle": "End of Chapter Quiz",
|
||||
"preparationTitle": "Preparation before starting",
|
||||
"instructionTitle": "Instructions",
|
||||
"instruction1": "Pay attention to the questions to measure your learning progress.",
|
||||
"startBtn": "Start Quiz",
|
||||
"nextBtn": "Next Question",
|
||||
"prevBtn": "Previous Question",
|
||||
"submitBtn": "Submit Answers",
|
||||
"submitConfirm": "Are you sure you want to submit your answers?",
|
||||
"scoreTitle": "Quiz Results",
|
||||
"passMessage": "Congratulations! You passed the quiz.",
|
||||
"failMessage": "You didn't pass this time. Please review the lessons and try again.",
|
||||
"backToLesson": "Back to Lesson",
|
||||
"reviewAnswers": "Review Answers",
|
||||
"timeLeft": "Time Left",
|
||||
"placeholderAPI": "This part is currently being connected to the real quiz system.",
|
||||
"placeholderDesc": "The quiz system will be available once the API integration is complete.",
|
||||
"skipToResult": "Skip to Result (Simulated)"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,5 +143,37 @@
|
|||
"invalidPhone": "เบอร์โทรศัพท์ไม่ถูกต้อง",
|
||||
"passwordTooShort": "รหัสผ่านต้องมีอย่างน้อย 6 ตัวอักษร",
|
||||
"passwordsDoNotMatch": "รหัสผ่านใหม่ไม่ตรงกัน"
|
||||
},
|
||||
"classroom": {
|
||||
"backToDashboard": "กลับไปคอร์สของฉัน",
|
||||
"loadingCurriculum": "กำลังโหลดเนื้อหา...",
|
||||
"readingMaterial": "บทเรียนนี้เป็นเอกสารประกอบการเรียน",
|
||||
"lessonContent": "เนื้อหาบทเรียน",
|
||||
"notAvailable": "บทเรียนนี้ยังไม่เปิดให้เข้าชม",
|
||||
"loadingTitle": "กำลังโหลด...",
|
||||
"chapter": "บทที่",
|
||||
"lessons": "บทเรียน"
|
||||
},
|
||||
"quiz": {
|
||||
"exitTitle": "ออกจากแบบทดสอบ",
|
||||
"exitConfirm": "คุณกำลังทำแบบทดสอบอยู่ หากออกตอนนี้ความคืบหน้าจะหายไป ยืนยันที่จะออก?",
|
||||
"startTitle": "แบบทดสอบท้ายบท",
|
||||
"preparationTitle": "เตรียมความพร้อมก่อนเริ่มทำ",
|
||||
"instructionTitle": "คำชี้แจง",
|
||||
"instruction1": "ตั้งใจทำแบบทดสอบเพื่อวัดผลการเรียนรู้",
|
||||
"startBtn": "เริ่มทำแบบทดสอบ",
|
||||
"nextBtn": "ข้อถัดไป",
|
||||
"prevBtn": "ข้อก่อนหน้า",
|
||||
"submitBtn": "ส่งคำตอบ",
|
||||
"submitConfirm": "คุณแน่ใจหรือไม่ว่าต้องการส่งคำตอบ?",
|
||||
"scoreTitle": "ผลการทดสอบ",
|
||||
"passMessage": "ยินดีด้วย! คุณสอบผ่านเกณฑ์",
|
||||
"failMessage": "คุณยังไม่ผ่านเกณฑ์ในครั้งนี้ ลองทบทวนบทเรียนและทำใหม่อีกครั้ง",
|
||||
"backToLesson": "กลับไปหน้าบทเรียน",
|
||||
"reviewAnswers": "ตรวจดูคำตอบ",
|
||||
"timeLeft": "เวลาที่เหลือ",
|
||||
"placeholderAPI": "ส่วนนี้อยู่ระหว่างการเชื่อมต่อกับระบบข้อสอบจริง",
|
||||
"placeholderDesc": "ระบบข้อสอบจะสามารถใช้งานได้เมื่อมีการเชื่อมต่อ API สมบูรณ์",
|
||||
"skipToResult": "ข้ามไปหน้าผลลัพธ์ (จำลอง)"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ onMounted(() => {
|
|||
v-else
|
||||
class="flex flex-col items-center justify-center py-20 bg-slate-50 dark:bg-slate-800/50 rounded-3xl border-2 border-dashed border-slate-200 dark:border-slate-700"
|
||||
>
|
||||
<div class="text-6xl mb-4">🔍</div>
|
||||
|
||||
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-2">{{ $t('discovery.emptyTitle') }}</h3>
|
||||
<p class="text-slate-500 dark:text-slate-400 text-center max-w-md">
|
||||
{{ $t('discovery.emptyDesc') }}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ const filteredCourses = computed(() => {
|
|||
<div class="mb-20 relative max-w-2xl mx-auto slide-up" style="animation-delay: 0.3s;">
|
||||
<div class="relative group">
|
||||
<div class="absolute inset-y-0 left-5 flex items-center pointer-events-none">
|
||||
<span class="text-xl opacity-50">🔍</span>
|
||||
|
||||
</div>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* @file learning.vue
|
||||
* @description หน้าเรียนออนไลน์ (Classroom Interface)
|
||||
|
|
@ -17,6 +17,7 @@ useHead({
|
|||
})
|
||||
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
const { fetchCourseLearningInfo, fetchLessonContent, saveVideoProgress, markLessonComplete, checkLessonAccess } = useCourse()
|
||||
|
||||
// State
|
||||
|
|
@ -43,8 +44,9 @@ const saveProgressInterval = ref<any>(null) // ตัวแปรเก็บ se
|
|||
|
||||
// Helper for localization
|
||||
const getLocalizedText = (text: any) => {
|
||||
if (!text) return ''
|
||||
if (typeof text === 'string') return text
|
||||
return text?.th || text?.en || ''
|
||||
return text.th || text.en || ''
|
||||
}
|
||||
|
||||
const toggleSidebar = () => {
|
||||
|
|
@ -108,7 +110,7 @@ const loadLesson = async (lessonId: number) => {
|
|||
// Optional: Check access first
|
||||
const accessRes = await checkLessonAccess(courseId.value, lessonId)
|
||||
if (accessRes.success && !accessRes.data.is_accessible) {
|
||||
alert('บทเรียนนี้ยังไม่เปิดให้เข้าชม')
|
||||
alert(t('classroom.notAvailable'))
|
||||
isLessonLoading.value = false
|
||||
return
|
||||
}
|
||||
|
|
@ -245,10 +247,10 @@ onBeforeUnmount(() => {
|
|||
<q-toolbar>
|
||||
<q-btn flat round dense icon="menu" class="lg:hidden mr-2" @click="toggleSidebar" />
|
||||
|
||||
<q-btn flat dense no-caps icon="arrow_back" label="กลับไปหน้าหลัก" to="/dashboard/my-courses" class="text-slate-600 dark:text-slate-300 mobile-hide-label" />
|
||||
<q-btn flat dense no-caps icon="arrow_back" :label="$t('classroom.backToDashboard')" to="/dashboard/my-courses" class="text-slate-600 dark:text-slate-300 mobile-hide-label" />
|
||||
|
||||
<q-toolbar-title class="text-sm font-bold text-center lg:text-left truncate">
|
||||
{{ courseData ? getLocalizedText(courseData.course.title) : 'กำลังโหลด...' }}
|
||||
{{ courseData ? getLocalizedText(courseData.course.title) : $t('classroom.loadingTitle') }}
|
||||
</q-toolbar-title>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
|
|
@ -306,7 +308,7 @@ onBeforeUnmount(() => {
|
|||
</div>
|
||||
<div v-else-if="isLoading" class="p-6 text-center text-slate-500">
|
||||
<q-spinner color="primary" size="2em" />
|
||||
<div class="mt-2 text-xs">กำลังโหลดเนื้อหา...</div>
|
||||
<div class="mt-2 text-xs">{{ $t('classroom.loadingCurriculum') }}</div>
|
||||
</div>
|
||||
</q-drawer>
|
||||
|
||||
|
|
@ -314,9 +316,8 @@ onBeforeUnmount(() => {
|
|||
<q-page-container class="bg-white dark:bg-slate-900">
|
||||
<q-page class="flex flex-col h-full bg-slate-50 dark:bg-[#0B0F1A]">
|
||||
<!-- Video Player & Content Area -->
|
||||
<!-- Note: Using existing logic but wrapped -->
|
||||
<div class="w-full max-w-7xl mx-auto p-4 md:p-6 flex-grow">
|
||||
<!-- Video Player Component Placeholder logic -->
|
||||
<!-- Video Player -->
|
||||
<div v-if="currentLesson" class="bg-black rounded-xl overflow-hidden shadow-lg mb-6 aspect-video relative group">
|
||||
<video
|
||||
ref="videoRef"
|
||||
|
|
@ -331,7 +332,7 @@ onBeforeUnmount(() => {
|
|||
<div v-else class="flex items-center justify-center h-full text-white/50 bg-slate-900">
|
||||
<div class="text-center">
|
||||
<q-icon name="article" size="xl" />
|
||||
<p class="mt-2">บทเรียนนี้เป็นเอกสารประกอบการเรียน</p>
|
||||
<p class="mt-2">{{ $t('classroom.readingMaterial') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -351,11 +352,10 @@ onBeforeUnmount(() => {
|
|||
<div v-if="currentLesson" class="bg-white dark:bg-slate-800 p-6 rounded-2xl shadow-sm border border-slate-100 dark:border-white/5">
|
||||
<h1 class="text-2xl font-bold mb-2">{{ getLocalizedText(currentLesson.title) }}</h1>
|
||||
<p class="text-slate-500 dark:text-slate-400" v-if="currentLesson.description">{{ currentLesson.description }}</p>
|
||||
<div class="mt-6 prose dark:prose-invert max-w-none">
|
||||
<!-- Content description or attachments here -->
|
||||
<div v-if="!videoSrc" class="p-4 bg-slate-50 dark:bg-slate-900/50 rounded-xl border border-dashed border-slate-300 dark:border-slate-700 text-center">
|
||||
<p>เนื้อหาบทเรียน</p>
|
||||
</div>
|
||||
|
||||
<!-- Lesson Content Area -->
|
||||
<div v-if="!videoSrc && currentLesson.content" class="mt-6 prose dark:prose-invert max-w-none p-6 bg-slate-50 dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/5">
|
||||
<div v-html="getLocalizedText(currentLesson.content)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -376,4 +376,3 @@ onBeforeUnmount(() => {
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -11,32 +11,64 @@ definePageMeta({
|
|||
middleware: 'auth'
|
||||
})
|
||||
|
||||
useHead({
|
||||
title: 'แบบทดสอบ - e-Learning'
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { fetchCourseLearningInfo, fetchLessonContent } = useCourse()
|
||||
|
||||
// Quiz State Management
|
||||
// Tracks the current screen/step in the quiz flow
|
||||
// State Management
|
||||
const currentScreen = ref<'start' | 'taking' | 'result' | 'review'>('start')
|
||||
const timeLeft = ref(30 * 60) // 30 minutes in seconds
|
||||
let timerInterval: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
// Display formatted time (MM:SS)
|
||||
const courseId = Number(route.query.course_id)
|
||||
const lessonId = Number(route.query.lesson_id)
|
||||
|
||||
const courseData = ref<any>(null)
|
||||
const quizData = ref<any>(null)
|
||||
const isLoading = ref(true)
|
||||
|
||||
// Helper for localization
|
||||
const getLocalizedText = (text: any) => {
|
||||
if (!text) return ''
|
||||
if (typeof text === 'string') return text
|
||||
return text.th || text.en || ''
|
||||
}
|
||||
|
||||
// Data Fetching
|
||||
const loadData = async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
if (courseId) {
|
||||
const courseRes = await fetchCourseLearningInfo(courseId)
|
||||
if (courseRes.success) courseData.value = courseRes.data
|
||||
}
|
||||
|
||||
if (courseId && lessonId) {
|
||||
const lessonRes = await fetchLessonContent(courseId, lessonId)
|
||||
if (lessonRes.success) {
|
||||
quizData.value = lessonRes.data.quiz || lessonRes.data
|
||||
if (quizData.value?.time_limit) {
|
||||
timeLeft.value = quizData.value.time_limit * 60
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading quiz data:', error)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Timer Logic
|
||||
const timerDisplay = computed(() => {
|
||||
const minutes = Math.floor(timeLeft.value / 60)
|
||||
const seconds = timeLeft.value % 60
|
||||
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
|
||||
})
|
||||
|
||||
const showReview = () => {
|
||||
currentScreen.value = 'review'
|
||||
}
|
||||
|
||||
const startQuiz = () => {
|
||||
currentScreen.value = 'taking'
|
||||
// Start countdown
|
||||
timerInterval = setInterval(() => {
|
||||
if (timeLeft.value > 0) timeLeft.value--
|
||||
else submitQuiz(true)
|
||||
|
|
@ -44,20 +76,17 @@ const startQuiz = () => {
|
|||
}
|
||||
|
||||
const submitQuiz = (auto = false) => {
|
||||
if (auto || confirm('คุณแน่ใจหรือไม่ว่าต้องการส่งคำตอบ?')) {
|
||||
if (auto || confirm(t('quiz.submitConfirm'))) {
|
||||
if (timerInterval) clearInterval(timerInterval)
|
||||
currentScreen.value = 'result'
|
||||
}
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const confirmExit = () => {
|
||||
const courseId = route.query.course_id
|
||||
const target = courseId ? `/classroom/learning?course_id=${courseId}` : '/classroom/learning'
|
||||
const target = courseId ? `/classroom/learning?course_id=${courseId}` : '/dashboard/my-courses'
|
||||
|
||||
if (currentScreen.value === 'taking') {
|
||||
if (confirm('คุณกำลังทำแบบทดสอบอยู่ หากออกตอนนี้ความคืบหน้าจะหายไป ยืนยันที่จะออก?')) {
|
||||
if (confirm(t('quiz.exitConfirm'))) {
|
||||
router.push(target)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -65,101 +94,136 @@ const confirmExit = () => {
|
|||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timerInterval) clearInterval(timerInterval)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="quiz-shell min-h-screen bg-slate-50 dark:bg-[#0b0f1a] text-slate-900 dark:text-slate-200 font-main antialiased selection:bg-blue-500/20 transition-colors">
|
||||
<div class="quiz-shell min-h-screen bg-slate-50 dark:bg-[#0b0f1a] text-slate-900 dark:text-slate-200 font-inter antialiased selection:bg-blue-500/20 transition-colors">
|
||||
<!-- Header -->
|
||||
<header class="h-14 bg-white dark:bg-[#161b22] fixed top-0 inset-x-0 z-[100] flex items-center px-6 border-b border-slate-200 dark:border-white/5 transition-colors">
|
||||
<div class="flex items-center">
|
||||
<button class="flex items-center gap-2 text-sm font-bold text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-white transition-colors" @click="confirmExit">
|
||||
<span>←</span>
|
||||
<span>ออกจากแบบทดสอบ</span>
|
||||
</button>
|
||||
<div class="w-[1px] h-4 bg-slate-300 dark:bg-white/10 mx-4"/>
|
||||
<h1 class="text-base font-black text-slate-900 dark:text-white uppercase tracking-tight">แบบทดสอบท้ายบท: พื้นฐาน UX</h1>
|
||||
<div class="flex items-center w-full justify-between">
|
||||
<div class="flex items-center">
|
||||
<button class="flex items-center gap-2 text-sm font-bold text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-white transition-colors" @click="confirmExit">
|
||||
<q-icon name="arrow_back" />
|
||||
<span>{{ $t('quiz.exitTitle') }}</span>
|
||||
</button>
|
||||
<div class="w-[1px] h-4 bg-slate-300 dark:bg-white/10 mx-4"/>
|
||||
<h1 class="text-sm font-black text-slate-900 dark:text-white uppercase tracking-tight truncate max-w-[200px] md:max-w-md">
|
||||
{{ quizData ? getLocalizedText(quizData.title) : (courseData ? getLocalizedText(courseData.course.title) : $t('quiz.startTitle')) }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div v-if="currentScreen === 'taking'" class="flex items-center gap-3">
|
||||
<div class="hidden md:block text-[10px] font-black uppercase tracking-widest text-slate-400">{{ $t('quiz.timeLeft') }}</div>
|
||||
<div class="bg-blue-50 dark:bg-blue-500/10 text-blue-600 dark:text-blue-400 px-3 py-1 rounded-full font-mono font-bold text-sm border border-blue-100 dark:border-blue-500/20">
|
||||
{{ timerDisplay }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="pt-14 h-screen flex items-center justify-center overflow-y-auto px-4 custom-scrollbar">
|
||||
|
||||
<!-- 1. START SCREEN -->
|
||||
<div v-if="currentScreen === 'start'" class="w-full max-w-[640px] animate-fade-in py-12">
|
||||
<div class="bg-white dark:!bg-[#1e293b] border border-slate-200 dark:border-white/5 rounded-[32px] p-8 md:p-14 shadow-lg dark:shadow-2xl relative overflow-hidden transition-colors">
|
||||
|
||||
<!-- Top Icon Wrapper -->
|
||||
<div class="flex justify-center mb-10">
|
||||
<div class="w-20 h-20 rounded-3xl bg-blue-50 dark:bg-blue-500/10 border border-blue-100 dark:border-blue-500/20 flex items-center justify-center shadow-inner">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 text-blue-600 dark:text-blue-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/>
|
||||
<polyline points="14 2 14 8 20 8"/>
|
||||
<path d="M10 12h4"/><path d="M10 16h4"/><path d="M10 8h1"/>
|
||||
<circle cx="16" cy="16" r="3" fill="currentColor" class="text-orange-500 opacity-80" />
|
||||
</svg>
|
||||
<div v-if="isLoading" class="flex flex-col items-center gap-4">
|
||||
<q-spinner color="primary" size="3rem" />
|
||||
<p class="text-sm font-medium text-slate-500">{{ $t('classroom.loadingTitle') }}</p>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- 1. START SCREEN -->
|
||||
<div v-if="currentScreen === 'start'" class="w-full max-w-[640px] animate-fade-in py-12">
|
||||
<div class="bg-white dark:bg-[#1e293b] border border-slate-200 dark:border-white/5 rounded-[32px] p-8 md:p-14 shadow-lg dark:shadow-2xl relative overflow-hidden transition-colors">
|
||||
|
||||
<div class="flex justify-center mb-10">
|
||||
<div class="w-20 h-20 rounded-3xl bg-blue-50 dark:bg-blue-500/10 border border-blue-100 dark:border-blue-500/20 flex items-center justify-center shadow-inner">
|
||||
<q-icon name="quiz" size="2rem" color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-2xl font-black text-slate-900 dark:text-white mb-2 tracking-tight">
|
||||
{{ quizData ? getLocalizedText(quizData.title) : $t('quiz.startTitle') }}
|
||||
</h2>
|
||||
<p class="text-[13px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest leading-none">
|
||||
{{ $t('quiz.preparationTitle') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Instruction Box -->
|
||||
<div class="bg-slate-50 dark:bg-[#0b121f]/80 p-8 rounded-3xl mb-8 border border-slate-100 dark:border-white/5">
|
||||
<h3 class="text-[12px] font-black text-slate-500 dark:text-slate-400 mb-6 uppercase tracking-[0.2em] flex items-center gap-2">
|
||||
{{ $t('quiz.instructionTitle') }}
|
||||
</h3>
|
||||
<ul class="space-y-4">
|
||||
<li class="flex items-start gap-3">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-blue-500 mt-1.5 flex-shrink-0"/>
|
||||
<span class="text-sm text-slate-600 dark:text-slate-300 font-medium leading-relaxed">
|
||||
{{ quizData?.description ? getLocalizedText(quizData.description) : $t('quiz.instruction1') }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="w-full py-5 bg-blue-600 hover:bg-blue-500 text-white rounded-[20px] font-black text-sm tracking-wider transition-all shadow-xl shadow-blue-600/20 active:scale-[0.98]"
|
||||
@click="startQuiz"
|
||||
>
|
||||
{{ $t('quiz.startBtn') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-[32px] font-black text-slate-900 dark:text-white mb-2 tracking-tight">แบบทดสอบท้ายบท</h2>
|
||||
<p class="text-[13px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest leading-none">เตรียมความพร้อมก่อนเริ่มทำ</p>
|
||||
</div>
|
||||
|
||||
<!-- Instruction Box -->
|
||||
<div class="bg-slate-50 dark:bg-[#0b121f]/80 p-8 rounded-3xl mb-8 border border-slate-100 dark:border-white/5">
|
||||
<h3 class="text-[12px] font-black text-slate-500 dark:text-slate-400 mb-6 uppercase tracking-[0.2em] flex items-center gap-2">
|
||||
คำชี้แจง
|
||||
</h3>
|
||||
<ul class="space-y-4">
|
||||
<li class="flex items-start gap-3">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-blue-500 mt-1.5 flex-shrink-0"/>
|
||||
<span class="text-[14px] text-slate-600 dark:text-slate-300 font-medium leading-relaxed">ตั้งใจทำแบบทดสอบเพื่อวัดผลการเรียนรู้</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Action Button -->
|
||||
<button
|
||||
class="w-full py-5 bg-blue-600 hover:bg-blue-500 text-white rounded-[20px] font-black text-[14px] tracking-wider transition-all shadow-xl shadow-blue-600/20 active:scale-[0.98]"
|
||||
@click="startQuiz"
|
||||
>
|
||||
เริ่มทำแบบทดสอบ
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. TAKING SCREEN (Placeholder for Real API) -->
|
||||
<div v-if="currentScreen === 'taking'" class="w-full max-w-[840px] animate-fade-in py-12 text-center">
|
||||
<div class="bg-white dark:!bg-[#1e293b] rounded-[32px] p-10 shadow-xl">
|
||||
<h2 class="text-xl font-bold mb-4">ส่วนนี้อยู่ระหว่างการเชื่อมต่อกับระบบข้อสอบจริง</h2>
|
||||
<p class="text-slate-500">ระบบข้อสอบจะสามารถใช้งานได้เมื่อมีการเชื่อมต่อ API สมบูรณ์</p>
|
||||
<button @click="currentScreen = 'result'" class="mt-8 btn btn-primary">ข้ามไปหน้าผลลัพธ์ (จำลอง)</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. RESULT SCREEN (Empty/Placeholder) -->
|
||||
<div v-if="currentScreen === 'result'" class="w-full max-w-[640px] animate-fade-in py-12">
|
||||
<div class="bg-white dark:!bg-[#1e293b] rounded-[40px] p-10 shadow-2xl text-center">
|
||||
<h2 class="text-2xl font-black mb-4">จบการทำแบบทดสอบ</h2>
|
||||
<div class="space-y-4 mt-8">
|
||||
<NuxtLink
|
||||
to="/classroom/learning"
|
||||
class="w-full py-5 bg-slate-100 text-slate-600 rounded-[24px] font-black text-[14px] block"
|
||||
>
|
||||
กลับไปหน้าบทเรียน
|
||||
</NuxtLink>
|
||||
<!-- 2. TAKING SCREEN -->
|
||||
<div v-if="currentScreen === 'taking'" class="w-full max-w-[840px] animate-fade-in py-12 text-center">
|
||||
<div class="bg-white dark:bg-[#1e293b] border border-slate-200 dark:border-white/5 rounded-[32px] p-10 shadow-xl">
|
||||
<div class="mb-10">
|
||||
<q-icon name="construction" size="4rem" class="text-blue-500 mb-4 opacity-50" />
|
||||
<h2 class="text-xl font-bold mb-2 text-slate-900 dark:text-white">{{ $t('quiz.placeholderAPI') }}</h2>
|
||||
<p class="text-slate-500 dark:text-slate-400">{{ $t('quiz.placeholderDesc') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="pt-8 border-t border-slate-100 dark:border-white/5">
|
||||
<button @click="submitQuiz(false)" class="px-8 py-4 bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 rounded-2xl font-bold hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors">
|
||||
{{ $t('quiz.skipToResult') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. RESULT SCREEN -->
|
||||
<div v-if="currentScreen === 'result'" class="w-full max-w-[640px] animate-fade-in py-12">
|
||||
<div class="bg-white dark:bg-[#1e293b] border border-slate-200 dark:border-white/5 rounded-[40px] p-10 shadow-2xl text-center">
|
||||
<div class="mb-8">
|
||||
<div class="w-24 h-24 bg-emerald-50 dark:bg-emerald-500/10 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<q-icon name="check_circle" size="3rem" class="text-emerald-500" />
|
||||
</div>
|
||||
<h2 class="text-2xl font-black mb-2 text-slate-900 dark:text-white">{{ $t('quiz.scoreTitle') }}</h2>
|
||||
<p class="text-slate-500 dark:text-slate-400">{{ $t('quiz.passMessage') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4 mt-8">
|
||||
<button
|
||||
@click="confirmExit"
|
||||
class="w-full py-5 bg-blue-600 text-white rounded-[24px] font-black text-sm shadow-lg shadow-blue-500/20"
|
||||
>
|
||||
{{ $t('quiz.backToLesson') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue