992 lines
26 KiB
Vue
992 lines
26 KiB
Vue
<template>
|
|
<div class="vocabulary-hub">
|
|
<!-- 頁面標題 -->
|
|
<div class="page-header">
|
|
<div class="header-content">
|
|
<div class="title-section">
|
|
<h1 class="page-title">詞彙學習中心</h1>
|
|
<p class="page-subtitle">透過多種練習模式提升你的詞彙量</p>
|
|
</div>
|
|
|
|
<!-- 書籤和工具按鈕 -->
|
|
<div class="header-actions">
|
|
<q-btn
|
|
:icon="isBookmarked ? 'bookmark' : 'bookmark_border'"
|
|
:color="isBookmarked ? 'amber' : 'grey-6'"
|
|
round
|
|
flat
|
|
size="md"
|
|
@click="toggleBookmarkStatus"
|
|
:title="isBookmarked ? '移除書籤 (Ctrl+D)' : '加入書籤 (Ctrl+D)'"
|
|
>
|
|
<q-tooltip>{{ isBookmarked ? '移除書籤' : '加入書籤' }} (Ctrl+D)</q-tooltip>
|
|
</q-btn>
|
|
|
|
<q-btn
|
|
icon="more_vert"
|
|
round
|
|
flat
|
|
size="md"
|
|
color="grey-6"
|
|
>
|
|
<q-menu>
|
|
<q-list style="min-width: 200px">
|
|
<q-item clickable @click="openBookmarkManager">
|
|
<q-item-section avatar>
|
|
<q-icon name="bookmarks" />
|
|
</q-item-section>
|
|
<q-item-section>管理書籤</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item clickable @click="exportBookmarksToFile">
|
|
<q-item-section avatar>
|
|
<q-icon name="download" />
|
|
</q-item-section>
|
|
<q-item-section>匯出書籤</q-item-section>
|
|
</q-item>
|
|
|
|
<q-separator />
|
|
|
|
<q-item clickable @click="showShortcuts = true">
|
|
<q-item-section avatar>
|
|
<q-icon name="keyboard" />
|
|
</q-item-section>
|
|
<q-item-section>快捷鍵說明</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-menu>
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 學習統計概覽 -->
|
|
<div class="stats-overview">
|
|
<q-card flat class="stat-card">
|
|
<q-card-section>
|
|
<div class="stat-content">
|
|
<q-icon name="book" size="xl" color="primary" />
|
|
<div class="stat-info">
|
|
<div class="stat-value">{{ vocabularyStore.vocabularies.length }}</div>
|
|
<div class="stat-label">總詞彙數</div>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card flat class="stat-card">
|
|
<q-card-section>
|
|
<div class="stat-content">
|
|
<q-icon name="trending_up" size="xl" color="green" />
|
|
<div class="stat-info">
|
|
<div class="stat-value">{{ vocabularyStore.masteredWords.length }}</div>
|
|
<div class="stat-label">已掌握</div>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card flat class="stat-card">
|
|
<q-card-section>
|
|
<div class="stat-content">
|
|
<q-icon name="schedule" size="xl" color="orange" />
|
|
<div class="stat-info">
|
|
<div class="stat-value">{{ vocabularyStore.wordsForReview.length }}</div>
|
|
<div class="stat-label">待複習</div>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card flat class="stat-card">
|
|
<q-card-section>
|
|
<div class="stat-content">
|
|
<q-icon name="school" size="xl" color="blue" />
|
|
<div class="stat-info">
|
|
<div class="stat-value">{{ vocabularyStore.learningWords.length }}</div>
|
|
<div class="stat-label">學習中</div>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
|
|
<!-- 快速開始練習 -->
|
|
<div class="quick-start-section">
|
|
<h2 class="section-title">快速開始</h2>
|
|
|
|
<div class="practice-modes">
|
|
<q-card class="practice-card" clickable @click="startPractice('multiple_choice_definition')">
|
|
<q-card-section>
|
|
<div class="practice-icon">
|
|
<q-icon name="quiz" size="3rem" color="primary" />
|
|
</div>
|
|
<div class="practice-info">
|
|
<h3>選擇題練習</h3>
|
|
<p>測試詞彙定義理解</p>
|
|
<div class="practice-meta">
|
|
<q-chip size="sm" color="primary" text-color="white">10題</q-chip>
|
|
<q-chip size="sm" outline>基礎-中級</q-chip>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card class="practice-card" clickable @click="startPractice('multiple_choice_translation')">
|
|
<q-card-section>
|
|
<div class="practice-icon">
|
|
<q-icon name="translate" size="3rem" color="secondary" />
|
|
</div>
|
|
<div class="practice-info">
|
|
<h3>翻譯練習</h3>
|
|
<p>英中翻譯能力測試</p>
|
|
<div class="practice-meta">
|
|
<q-chip size="sm" color="secondary" text-color="white">10題</q-chip>
|
|
<q-chip size="sm" outline>中級-高級</q-chip>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card class="practice-card" clickable @click="startPractice('multiple_choice_synonym')">
|
|
<q-card-section>
|
|
<div class="practice-icon">
|
|
<q-icon name="compare_arrows" size="3rem" color="accent" />
|
|
</div>
|
|
<div class="practice-info">
|
|
<h3>同義詞練習</h3>
|
|
<p>詞彙關聯性訓練</p>
|
|
<div class="practice-meta">
|
|
<q-chip size="sm" color="accent" text-color="white">10題</q-chip>
|
|
<q-chip size="sm" outline>高級</q-chip>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
|
|
<!-- 自定義練習按鈕 -->
|
|
<div class="custom-practice">
|
|
<q-btn
|
|
size="lg"
|
|
color="primary"
|
|
icon="settings"
|
|
label="自定義練習設定"
|
|
@click="goToCustomPractice"
|
|
outline
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 學習進度 -->
|
|
<div class="progress-section" v-if="vocabularyStore.progress.length > 0">
|
|
<h2 class="section-title">學習進度</h2>
|
|
|
|
<q-card flat class="progress-card">
|
|
<q-card-section>
|
|
<div class="progress-header">
|
|
<div class="progress-title">掌握度分佈</div>
|
|
<div class="progress-info">
|
|
{{ Math.round(overallProgress) }}% 整體掌握度
|
|
</div>
|
|
</div>
|
|
|
|
<div class="progress-bars">
|
|
<div class="progress-bar-item">
|
|
<div class="bar-label">初學者 (0-25%)</div>
|
|
<q-linear-progress
|
|
:value="masteryDistribution.beginner / vocabularyStore.progress.length"
|
|
color="red"
|
|
track-color="grey-3"
|
|
size="12px"
|
|
rounded
|
|
/>
|
|
<div class="bar-value">{{ masteryDistribution.beginner }} 詞</div>
|
|
</div>
|
|
|
|
<div class="progress-bar-item">
|
|
<div class="bar-label">學習中 (26-50%)</div>
|
|
<q-linear-progress
|
|
:value="masteryDistribution.intermediate / vocabularyStore.progress.length"
|
|
color="orange"
|
|
track-color="grey-3"
|
|
size="12px"
|
|
rounded
|
|
/>
|
|
<div class="bar-value">{{ masteryDistribution.intermediate }} 詞</div>
|
|
</div>
|
|
|
|
<div class="progress-bar-item">
|
|
<div class="bar-label">熟悉 (51-75%)</div>
|
|
<q-linear-progress
|
|
:value="masteryDistribution.advanced / vocabularyStore.progress.length"
|
|
color="blue"
|
|
track-color="grey-3"
|
|
size="12px"
|
|
rounded
|
|
/>
|
|
<div class="bar-value">{{ masteryDistribution.advanced }} 詞</div>
|
|
</div>
|
|
|
|
<div class="progress-bar-item">
|
|
<div class="bar-label">已掌握 (76-100%)</div>
|
|
<q-linear-progress
|
|
:value="masteryDistribution.mastered / vocabularyStore.progress.length"
|
|
color="green"
|
|
track-color="grey-3"
|
|
size="12px"
|
|
rounded
|
|
/>
|
|
<div class="bar-value">{{ masteryDistribution.mastered }} 詞</div>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
|
|
<!-- 待複習詞彙 -->
|
|
<div class="review-section" v-if="vocabularyStore.wordsForReview.length > 0">
|
|
<div class="section-header">
|
|
<h2 class="section-title">今日複習</h2>
|
|
<q-btn
|
|
color="orange"
|
|
icon="refresh"
|
|
label="開始複習"
|
|
@click="startReview"
|
|
/>
|
|
</div>
|
|
|
|
<div class="review-words">
|
|
<q-card
|
|
v-for="progress in vocabularyStore.wordsForReview.slice(0, 6)"
|
|
:key="progress.vocabulary_id"
|
|
class="review-word-card"
|
|
flat
|
|
>
|
|
<q-card-section>
|
|
<div class="word-info">
|
|
<div class="word">{{ getVocabularyById(progress.vocabulary_id)?.word }}</div>
|
|
<div class="mastery">{{ progress.mastery_level }}% 掌握</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
|
|
<div v-if="vocabularyStore.wordsForReview.length > 6" class="more-words">
|
|
還有 {{ vocabularyStore.wordsForReview.length - 6 }} 個詞彙待複習
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 系統狀態 (開發用) -->
|
|
<div v-if="isDev" class="dev-info">
|
|
<q-card flat class="dev-card">
|
|
<q-card-section>
|
|
<div class="text-h6">系統狀態 (開發模式)</div>
|
|
<div class="dev-status">
|
|
<div>✅ Vue 3 + Composition API</div>
|
|
<div>✅ Quasar Framework</div>
|
|
<div>✅ Pinia 狀態管理</div>
|
|
<div>✅ 詞彙練習系統</div>
|
|
<div>✅ 認證狀態: {{ authStore.isAuthenticated ? '已登入' : '未登入' }}</div>
|
|
<div>✅ 路由: {{ $route.path }}</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
|
|
<!-- 書籤管理對話框 -->
|
|
<q-dialog v-model="showBookmarkManager" persistent>
|
|
<q-card style="min-width: 600px; max-width: 800px">
|
|
<q-card-section class="row items-center q-pb-none">
|
|
<div class="text-h6">書籤管理</div>
|
|
<q-space />
|
|
<q-btn icon="close" flat round dense v-close-popup />
|
|
</q-card-section>
|
|
|
|
<q-card-section>
|
|
<div class="row q-gutter-sm q-mb-md">
|
|
<q-input
|
|
v-model="bookmarkSearch"
|
|
placeholder="搜尋書籤..."
|
|
outlined
|
|
dense
|
|
class="col"
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="search" />
|
|
</template>
|
|
</q-input>
|
|
|
|
<q-btn
|
|
color="primary"
|
|
icon="upload"
|
|
label="匯入"
|
|
@click="importBookmarksDialog"
|
|
/>
|
|
</div>
|
|
|
|
<q-list separator v-if="filteredBookmarks.length > 0">
|
|
<q-item
|
|
v-for="bookmark in filteredBookmarks"
|
|
:key="bookmark.id"
|
|
clickable
|
|
@click="navigateToBookmark(bookmark)"
|
|
>
|
|
<q-item-section avatar>
|
|
<q-icon name="bookmark" color="amber" />
|
|
</q-item-section>
|
|
|
|
<q-item-section>
|
|
<q-item-label>{{ bookmark.title }}</q-item-label>
|
|
<q-item-label caption>{{ bookmark.description }}</q-item-label>
|
|
<q-item-label caption class="text-grey">
|
|
{{ formatDate(bookmark.createdAt) }}
|
|
</q-item-label>
|
|
</q-item-section>
|
|
|
|
<q-item-section side>
|
|
<q-btn
|
|
icon="delete"
|
|
flat
|
|
round
|
|
size="sm"
|
|
color="negative"
|
|
@click.stop="removeBookmarkById(bookmark.id)"
|
|
/>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
|
|
<div v-else class="text-center q-py-xl text-grey-5">
|
|
<q-icon name="bookmark_border" size="4rem" />
|
|
<div class="q-mt-md">尚無書籤</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
<!-- 快捷鍵說明對話框 -->
|
|
<q-dialog v-model="showShortcuts">
|
|
<q-card style="min-width: 500px">
|
|
<q-card-section class="row items-center q-pb-none">
|
|
<div class="text-h6">快捷鍵說明</div>
|
|
<q-space />
|
|
<q-btn icon="close" flat round dense v-close-popup />
|
|
</q-card-section>
|
|
|
|
<q-card-section>
|
|
<div class="shortcut-categories">
|
|
<div class="shortcut-category">
|
|
<div class="category-title">導航</div>
|
|
<div class="shortcut-item">
|
|
<kbd>Ctrl + H</kbd>
|
|
<span>返回學習首頁</span>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<kbd>Ctrl + V</kbd>
|
|
<span>打開詞彙學習</span>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<kbd>Ctrl + R</kbd>
|
|
<span>打開智能複習</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="shortcut-category">
|
|
<div class="category-title">學習工具</div>
|
|
<div class="shortcut-item">
|
|
<kbd>Ctrl + D</kbd>
|
|
<span>切換書籤</span>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<kbd>Ctrl + F</kbd>
|
|
<span>搜尋</span>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<kbd>F1</kbd>
|
|
<span>開啟字典</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="shortcut-category">
|
|
<div class="category-title">其他</div>
|
|
<div class="shortcut-item">
|
|
<kbd>Shift + ?</kbd>
|
|
<span>顯示此說明</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</q-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { useVocabularyStore } from '@/stores/vocabulary'
|
|
import { useBrowserBookmarks } from '@/composables/useBrowserBookmarks'
|
|
import { useKeyboardShortcuts } from '@/composables/useKeyboardShortcuts'
|
|
import { useQuasar } from 'quasar'
|
|
import type { ExerciseType } from '@/types/vocabulary'
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const authStore = useAuthStore()
|
|
const vocabularyStore = useVocabularyStore()
|
|
const $q = useQuasar()
|
|
|
|
// 書籤功能
|
|
const {
|
|
bookmarks,
|
|
isBookmarked,
|
|
toggleBookmark,
|
|
checkBookmarkStatus,
|
|
removeBookmark,
|
|
searchBookmarks,
|
|
exportBookmarks,
|
|
importBookmarks
|
|
} = useBrowserBookmarks()
|
|
|
|
// 快捷鍵
|
|
const { registerShortcut } = useKeyboardShortcuts()
|
|
|
|
const isDev = ref(import.meta.env.DEV)
|
|
|
|
// 對話框狀態
|
|
const showBookmarkManager = ref(false)
|
|
const showShortcuts = ref(false)
|
|
const bookmarkSearch = ref('')
|
|
|
|
// 計算屬性
|
|
const masteryDistribution = computed(() => {
|
|
const progress = vocabularyStore.progress
|
|
return {
|
|
beginner: progress.filter(p => p.mastery_level <= 25).length,
|
|
intermediate: progress.filter(p => p.mastery_level > 25 && p.mastery_level <= 50).length,
|
|
advanced: progress.filter(p => p.mastery_level > 50 && p.mastery_level <= 75).length,
|
|
mastered: progress.filter(p => p.mastery_level > 75).length
|
|
}
|
|
})
|
|
|
|
const overallProgress = computed(() => {
|
|
const progress = vocabularyStore.progress
|
|
if (progress.length === 0) return 0
|
|
const totalMastery = progress.reduce((sum, p) => sum + p.mastery_level, 0)
|
|
return totalMastery / progress.length
|
|
})
|
|
|
|
// 書籤相關計算屬性
|
|
const filteredBookmarks = computed(() => {
|
|
if (!bookmarkSearch.value) {
|
|
return bookmarks.value
|
|
}
|
|
return searchBookmarks(bookmarkSearch.value)
|
|
})
|
|
|
|
// 方法
|
|
const startPractice = async (exerciseType: ExerciseType) => {
|
|
try {
|
|
// 設定快速練習參數
|
|
vocabularyStore.updatePracticeSettings({
|
|
exercise_type: exerciseType,
|
|
difficulty_levels: [1, 2, 3],
|
|
question_count: 10,
|
|
enable_audio: true,
|
|
enable_hints: true,
|
|
shuffle_options: true
|
|
})
|
|
|
|
// 跳轉到練習頁面
|
|
router.push('/learning/vocabulary/practice')
|
|
} catch (error) {
|
|
console.error('開始練習失敗:', error)
|
|
}
|
|
}
|
|
|
|
const goToCustomPractice = () => {
|
|
router.push('/learning/vocabulary/practice')
|
|
}
|
|
|
|
const startReview = () => {
|
|
// 開始複習模式
|
|
router.push('/learning/vocabulary/review')
|
|
}
|
|
|
|
const getVocabularyById = (id: string) => {
|
|
return vocabularyStore.vocabularies.find(v => v.id === id)
|
|
}
|
|
|
|
// 書籤相關方法
|
|
const toggleBookmarkStatus = () => {
|
|
const currentUrl = `${window.location.origin}${route.fullPath}`
|
|
const result = toggleBookmark({
|
|
title: '詞彙學習中心 - Drama Ling',
|
|
url: currentUrl,
|
|
description: '透過多種練習模式提升你的詞彙量'
|
|
})
|
|
|
|
$q.notify({
|
|
message: result.bookmarked ? '已加入書籤' : '已移除書籤',
|
|
icon: result.bookmarked ? 'bookmark' : 'bookmark_border',
|
|
color: result.bookmarked ? 'positive' : 'info',
|
|
position: 'top'
|
|
})
|
|
}
|
|
|
|
const openBookmarkManager = () => {
|
|
showBookmarkManager.value = true
|
|
}
|
|
|
|
const navigateToBookmark = (bookmark: any) => {
|
|
if (bookmark.url.startsWith(window.location.origin)) {
|
|
const path = bookmark.url.replace(window.location.origin, '')
|
|
router.push(path)
|
|
} else {
|
|
window.open(bookmark.url, '_blank')
|
|
}
|
|
showBookmarkManager.value = false
|
|
}
|
|
|
|
const removeBookmarkById = (id: string) => {
|
|
$q.dialog({
|
|
title: '確認刪除',
|
|
message: '確定要移除此書籤嗎?',
|
|
cancel: true,
|
|
persistent: true
|
|
}).onOk(() => {
|
|
if (removeBookmark(id)) {
|
|
$q.notify({
|
|
message: '書籤已移除',
|
|
color: 'positive',
|
|
icon: 'check'
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
const exportBookmarksToFile = () => {
|
|
exportBookmarks()
|
|
$q.notify({
|
|
message: '書籤已匯出',
|
|
color: 'positive',
|
|
icon: 'download'
|
|
})
|
|
}
|
|
|
|
const importBookmarksDialog = () => {
|
|
const input = document.createElement('input')
|
|
input.type = 'file'
|
|
input.accept = '.json'
|
|
|
|
input.onchange = async (e) => {
|
|
const file = (e.target as HTMLInputElement).files?.[0]
|
|
if (file) {
|
|
try {
|
|
const importedCount = await importBookmarks(file)
|
|
$q.notify({
|
|
message: `已匯入 ${importedCount} 個書籤`,
|
|
color: 'positive',
|
|
icon: 'upload'
|
|
})
|
|
} catch (error) {
|
|
$q.notify({
|
|
message: '匯入失敗:' + (error as Error).message,
|
|
color: 'negative',
|
|
icon: 'error'
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
input.click()
|
|
}
|
|
|
|
const formatDate = (date: Date) => {
|
|
return new Intl.DateTimeFormat('zh-TW', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
}).format(date)
|
|
}
|
|
|
|
// 生命週期
|
|
onMounted(async () => {
|
|
// 載入詞彙數據
|
|
await vocabularyStore.fetchVocabularies()
|
|
|
|
// 檢查當前頁面書籤狀態
|
|
checkBookmarkStatus(`${window.location.origin}${route.fullPath}`)
|
|
|
|
// 註冊書籤快捷鍵
|
|
registerShortcut({
|
|
key: 'd',
|
|
ctrl: true,
|
|
action: toggleBookmarkStatus,
|
|
description: '切換書籤'
|
|
})
|
|
|
|
console.log('詞彙學習中心已載入')
|
|
console.log('詞彙數量:', vocabularyStore.vocabularies.length)
|
|
console.log('學習進度:', vocabularyStore.progress.length)
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.vocabulary-hub {
|
|
padding: $space-6;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.page-header {
|
|
margin-bottom: $space-8;
|
|
|
|
.header-content {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: $space-4;
|
|
|
|
@media (max-width: 768px) {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
}
|
|
}
|
|
|
|
.title-section {
|
|
text-align: center;
|
|
flex: 1;
|
|
|
|
.page-title {
|
|
font-size: $text-4xl;
|
|
font-weight: 800;
|
|
color: $text-primary;
|
|
margin-bottom: $space-2;
|
|
}
|
|
|
|
.page-subtitle {
|
|
font-size: $text-xl;
|
|
color: $text-secondary;
|
|
}
|
|
}
|
|
|
|
.header-actions {
|
|
display: flex;
|
|
gap: $space-2;
|
|
}
|
|
}
|
|
|
|
.stats-overview {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: $space-4;
|
|
margin-bottom: $space-8;
|
|
|
|
.stat-card {
|
|
background: $card-background;
|
|
border-radius: $radius-lg;
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: $shadow-lg;
|
|
}
|
|
|
|
.stat-content {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: $space-4;
|
|
|
|
.stat-info {
|
|
.stat-value {
|
|
font-size: $text-3xl;
|
|
font-weight: 700;
|
|
color: $text-primary;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: $text-base;
|
|
color: $text-secondary;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.quick-start-section {
|
|
margin-bottom: $space-8;
|
|
|
|
.section-title {
|
|
font-size: $text-2xl;
|
|
font-weight: 700;
|
|
color: $text-primary;
|
|
margin-bottom: $space-6;
|
|
}
|
|
|
|
.practice-modes {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: $space-6;
|
|
margin-bottom: $space-6;
|
|
|
|
.practice-card {
|
|
background: $card-background;
|
|
border-radius: $radius-lg;
|
|
transition: all 0.3s ease;
|
|
cursor: pointer;
|
|
|
|
&:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: $shadow-xl;
|
|
}
|
|
|
|
.practice-icon {
|
|
text-align: center;
|
|
margin-bottom: $space-4;
|
|
}
|
|
|
|
.practice-info {
|
|
text-align: center;
|
|
|
|
h3 {
|
|
font-size: $text-lg;
|
|
font-weight: 600;
|
|
color: $text-primary;
|
|
margin-bottom: $space-2;
|
|
}
|
|
|
|
p {
|
|
font-size: $text-base;
|
|
color: $text-secondary;
|
|
margin-bottom: $space-4;
|
|
}
|
|
|
|
.practice-meta {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: $space-2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.custom-practice {
|
|
text-align: center;
|
|
}
|
|
}
|
|
|
|
.progress-section {
|
|
margin-bottom: $space-8;
|
|
|
|
.section-title {
|
|
font-size: $text-2xl;
|
|
font-weight: 700;
|
|
color: $text-primary;
|
|
margin-bottom: $space-6;
|
|
}
|
|
|
|
.progress-card {
|
|
background: $card-background;
|
|
border-radius: $radius-lg;
|
|
|
|
.progress-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: $space-6;
|
|
|
|
.progress-title {
|
|
font-size: $text-lg;
|
|
font-weight: 600;
|
|
color: $text-primary;
|
|
}
|
|
|
|
.progress-info {
|
|
font-size: $text-base;
|
|
color: $text-secondary;
|
|
}
|
|
}
|
|
|
|
.progress-bars {
|
|
.progress-bar-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: $space-4;
|
|
margin-bottom: $space-4;
|
|
|
|
.bar-label {
|
|
min-width: 120px;
|
|
font-size: $text-sm;
|
|
color: $text-secondary;
|
|
}
|
|
|
|
.q-linear-progress {
|
|
flex: 1;
|
|
}
|
|
|
|
.bar-value {
|
|
min-width: 60px;
|
|
text-align: right;
|
|
font-size: $text-sm;
|
|
color: $text-primary;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.review-section {
|
|
margin-bottom: $space-8;
|
|
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: $space-6;
|
|
|
|
.section-title {
|
|
font-size: $text-2xl;
|
|
font-weight: 700;
|
|
color: $text-primary;
|
|
}
|
|
}
|
|
|
|
.review-words {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: $space-4;
|
|
margin-bottom: $space-4;
|
|
|
|
.review-word-card {
|
|
background: rgba($warning-orange, 0.1);
|
|
border: 1px solid rgba($warning-orange, 0.3);
|
|
border-radius: $radius-md;
|
|
|
|
.word-info {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
|
|
.word {
|
|
font-size: $text-lg;
|
|
font-weight: 600;
|
|
color: $text-primary;
|
|
}
|
|
|
|
.mastery {
|
|
font-size: $text-sm;
|
|
color: $warning-orange;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.more-words {
|
|
text-align: center;
|
|
color: $text-secondary;
|
|
font-size: $text-base;
|
|
}
|
|
}
|
|
|
|
.dev-info {
|
|
margin-top: $space-8;
|
|
|
|
.dev-card {
|
|
background: rgba($info-cyan, 0.1);
|
|
border: 1px solid rgba($info-cyan, 0.3);
|
|
border-radius: $radius-lg;
|
|
|
|
.dev-status {
|
|
margin-top: $space-4;
|
|
|
|
div {
|
|
margin-bottom: $space-1;
|
|
font-size: $text-sm;
|
|
color: $text-secondary;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.vocabulary-hub {
|
|
padding: $space-4;
|
|
}
|
|
|
|
.stats-overview {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: $space-3;
|
|
}
|
|
|
|
.practice-modes {
|
|
grid-template-columns: 1fr;
|
|
gap: $space-4;
|
|
}
|
|
|
|
.review-words {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.section-header {
|
|
flex-direction: column;
|
|
gap: $space-3;
|
|
align-items: stretch;
|
|
}
|
|
}
|
|
|
|
// 快捷鍵說明樣式
|
|
.shortcut-categories {
|
|
.shortcut-category {
|
|
margin-bottom: $space-4;
|
|
|
|
&:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.category-title {
|
|
font-size: $text-lg;
|
|
font-weight: 600;
|
|
color: $text-primary;
|
|
margin-bottom: $space-3;
|
|
border-bottom: 2px solid $divider;
|
|
padding-bottom: $space-1;
|
|
}
|
|
|
|
.shortcut-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: $space-2 0;
|
|
border-bottom: 1px solid rgba($divider, 0.5);
|
|
|
|
&:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
kbd {
|
|
background: rgba($text-secondary, 0.1);
|
|
border: 1px solid rgba($text-secondary, 0.3);
|
|
border-radius: $radius-sm;
|
|
padding: $space-1 $space-2;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: $text-xs;
|
|
color: $text-primary;
|
|
min-width: 60px;
|
|
text-align: center;
|
|
}
|
|
|
|
span {
|
|
color: $text-secondary;
|
|
font-size: $text-sm;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style> |