Website Structure
This commit is contained in:
parent
62812f2090
commit
71f0676a62
22365 changed files with 4265753 additions and 791 deletions
237
Frontend-Learner/components/course/CourseCard.vue
Normal file
237
Frontend-Learner/components/course/CourseCard.vue
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
<script setup lang="ts">
|
||||
/**
|
||||
* @file CourseCard.vue
|
||||
* @description Card component for displaying course information.
|
||||
* Supports various states: default, in-progress, completed.
|
||||
* Handles action events (view, continue, certificate).
|
||||
*/
|
||||
|
||||
interface CourseCardProps {
|
||||
/** Course Title */
|
||||
title: string
|
||||
/** Difficulty Level (Beginner, Intermediate, etc.) */
|
||||
level?: string
|
||||
/** Visual type for level badge (color coding) */
|
||||
levelType?: 'neutral' | 'warning' | 'success'
|
||||
/** Course Price (e.g., 'Free', '฿990') */
|
||||
price?: string
|
||||
/** Short description */
|
||||
description?: string
|
||||
/** Rating score (e.g., '4.8') */
|
||||
rating?: string
|
||||
/** Number of lessons */
|
||||
lessons?: string
|
||||
/** Learning progress percentage (0-100) */
|
||||
progress?: number
|
||||
/** Whether the course is completed */
|
||||
completed?: boolean
|
||||
/** Show 'View Details' button */
|
||||
showViewDetails?: boolean
|
||||
/** Show 'Continue Learning' button */
|
||||
showContinue?: boolean
|
||||
/** Show 'Download Certificate' button (if completed) */
|
||||
showCertificate?: boolean
|
||||
/** Show 'Study Again' button (if completed) */
|
||||
showStudyAgain?: boolean
|
||||
/** URL for course thumbnail image */
|
||||
image?: string
|
||||
}
|
||||
|
||||
defineProps<CourseCardProps>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
viewDetails: []
|
||||
continue: []
|
||||
viewCertificate: []
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card course-card group">
|
||||
<!-- Thumbnail -->
|
||||
<div class="thumbnail-wrapper relative h-44 overflow-hidden rounded-t-[1.5rem]">
|
||||
<img
|
||||
:src="image || 'https://images.unsplash.com/photo-1498050108023-c5249f4df085?w=400&q=80'"
|
||||
:alt="title"
|
||||
class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700"
|
||||
>
|
||||
<div v-if="completed" class="absolute inset-0 bg-emerald-600/60 backdrop-blur-[2px] flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<span class="text-white bg-emerald-500 w-12 h-12 rounded-full flex items-center justify-center shadow-lg transform scale-0 group-hover:scale-100 transition-transform duration-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="20 20 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Badges overlay -->
|
||||
<div class="absolute top-4 left-4 flex gap-2">
|
||||
<span v-if="level" :class="`status-pill status-${levelType || 'neutral'} shadow-sm text-[9px]`">
|
||||
{{ level }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="price" class="absolute top-4 right-4 glass px-3 py-1 rounded-full text-xs font-black text-white shadow-sm">
|
||||
{{ price }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="p-6 flex flex-col flex-1">
|
||||
<!-- Title -->
|
||||
<h3 class="font-black text-lg mb-2 line-clamp-1 text-slate-900 dark:text-white group-hover:text-blue-400 transition-colors">{{ title }}</h3>
|
||||
|
||||
<!-- Description -->
|
||||
<div v-if="description" class="text-sm mb-6 line-clamp-2 leading-relaxed text-slate-600 dark:text-slate-400">{{ description }}</div>
|
||||
|
||||
<!-- Rating & Lessons -->
|
||||
<div v-if="rating || lessons" class="flex items-center gap-4 text-[11px] font-bold mb-6 uppercase tracking-wider text-slate-500 dark:text-slate-400">
|
||||
<span v-if="rating" class="flex items-center gap-1"><span class="text-amber-400 text-sm">★</span> {{ rating }}</span>
|
||||
<span v-if="lessons" class="flex items-center gap-1"><span class="text-blue-400">📚</span> {{ lessons }} บทเรียน</span>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div v-if="progress !== undefined && !completed" class="mb-8 p-3 rounded-2xl" style="background-color: rgba(148, 163, 184, 0.1);">
|
||||
<div class="flex justify-between items-center text-[10px] font-black uppercase tracking-widest mb-1.5">
|
||||
<span style="color: var(--text-secondary);">Progress</span>
|
||||
<span class="text-blue-600">{{ progress }}%</span>
|
||||
</div>
|
||||
<div class="w-full h-1.5 bg-slate-300 dark:bg-slate-700 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-blue-600 shadow-[0_0_10px_rgba(37,99,235,0.4)] rounded-full transition-all duration-1000" :style="{ width: `${progress}%` }"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Completed Badge -->
|
||||
<div v-if="completed" class="mb-6">
|
||||
<span class="status-pill status-success text-[10px] font-black uppercase tracking-widest flex items-center justify-center gap-2">
|
||||
<span class="text-sm">✓</span> เรียนจบเรียบร้อย
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<button v-if="showViewDetails" class="btn-premium-secondary w-full mt-auto" @click="emit('viewDetails')">
|
||||
ดูรายละเอียด
|
||||
</button>
|
||||
|
||||
<NuxtLink v-if="showContinue" to="/classroom/learning" class="btn-premium-primary w-full mt-auto shadow-lg shadow-blue-600/20">
|
||||
เรียนต่อทันที
|
||||
</NuxtLink>
|
||||
|
||||
<div v-if="completed && (showCertificate || showStudyAgain)" class="flex flex-col gap-2 mt-auto">
|
||||
<NuxtLink v-if="showStudyAgain" to="/classroom/learning" class="btn-premium-secondary w-full">
|
||||
ทบทวนบทเรียน
|
||||
</NuxtLink>
|
||||
<button v-if="showCertificate" class="btn-premium-success w-full shadow-lg shadow-emerald-600/20" @click="emit('viewCertificate')">
|
||||
ดาวน์โหลดประกาศนียบัตร
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.course-card {
|
||||
padding: 0;
|
||||
border-radius: 2rem;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.course-card:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: var(--shadow-xl);
|
||||
}
|
||||
|
||||
/* Remove explicit dark mode block as default variables handle it */
|
||||
/* But keep specific tweaks if needed */
|
||||
|
||||
.btn-premium-primary {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
color: white;
|
||||
padding: 0.875rem;
|
||||
border-radius: 1rem;
|
||||
font-weight: 800;
|
||||
font-size: 0.875rem;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.btn-premium-primary:hover {
|
||||
filter: brightness(110%);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-premium-secondary {
|
||||
background: transparent;
|
||||
color: var(--text-main);
|
||||
padding: 0.875rem;
|
||||
border-radius: 1rem;
|
||||
font-weight: 800;
|
||||
font-size: 0.875rem;
|
||||
border: 1px solid var(--border-color);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.btn-premium-secondary:hover {
|
||||
background: var(--bg-body);
|
||||
border-color: #cbd5e1;
|
||||
}
|
||||
|
||||
:global(.dark) .btn-premium-secondary {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: #e2e8f0;
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
:global(.dark) .btn-premium-secondary:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.btn-premium-success {
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
color: white;
|
||||
padding: 0.875rem;
|
||||
border-radius: 1rem;
|
||||
font-weight: 800;
|
||||
font-size: 0.875rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(229, 231, 235, 0.5);
|
||||
}
|
||||
|
||||
:global(.dark) .glass {
|
||||
background: rgba(15, 23, 42, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
@apply px-3 py-1 rounded-full font-black uppercase tracking-wider;
|
||||
}
|
||||
.status-success {
|
||||
@apply bg-emerald-100 text-emerald-700;
|
||||
}
|
||||
.status-warning {
|
||||
@apply bg-amber-100 text-amber-700;
|
||||
}
|
||||
.status-neutral {
|
||||
@apply bg-slate-100 text-slate-700;
|
||||
}
|
||||
|
||||
:global(.dark) .status-success {
|
||||
@apply bg-emerald-900/40 text-emerald-300;
|
||||
}
|
||||
|
||||
:global(.dark) .status-warning {
|
||||
@apply bg-amber-900/40 text-amber-300;
|
||||
}
|
||||
|
||||
:global(.dark) .status-neutral {
|
||||
@apply bg-slate-700 text-slate-300;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue