clean: 移除展示頁面和舊的規格文件

- 刪除 /vocab-designs 展示頁面(已完成使命)
- 清理過時的中文規格文件
- Portal重構完成後不再需要樣式對比頁面

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
鄭沛軒 2025-09-21 01:03:23 +08:00
parent 421edd0589
commit 5583b763bc
6 changed files with 0 additions and 5879 deletions

View File

@ -1,743 +0,0 @@
'use client'
import { useState } from 'react'
import { Navigation } from '@/components/Navigation'
import { ProtectedRoute } from '@/components/ProtectedRoute'
export default function VocabDesignsPage() {
return (
<ProtectedRoute>
<VocabDesignsContent />
</ProtectedRoute>
)
}
function VocabDesignsContent() {
const [selectedDesign, setSelectedDesign] = useState('modern')
const [showPopup, setShowPopup] = useState(false)
// 假詞彙數據
const mockWord = {
word: 'elaborate',
pronunciation: '/ɪˈlæbərət/',
partOfSpeech: 'verb',
translation: '詳細說明',
definition: 'To explain something in more detail; to develop or present a theory, policy, or system in further detail',
synonyms: ['explain', 'detail', 'expand', 'clarify'],
difficultyLevel: 'B2',
isHighValue: true,
example: 'Could you elaborate on your proposal?',
exampleTranslation: '你能詳細說明一下你的提案嗎?'
}
const designs = [
{ id: 'modern', name: '現代玻璃', description: '毛玻璃效果,現代感設計' },
{ id: 'classic', name: '經典卡片', description: '傳統卡片風格,清晰分區' },
{ id: 'minimal', name: '極簡風格', description: '簡潔乾淨,突出重點' },
{ id: 'magazine', name: '雜誌排版', description: '類似雜誌的排版風格' },
{ id: 'mobile', name: '移動應用', description: 'iOS/Android應用風格' },
{ id: 'learning', name: '學習卡片', description: '與學習功能一致的風格' },
{ id: 'flashcard', name: '詞卡風格', description: '參考詞卡詳細頁面的設計' }
]
return (
<div className="min-h-screen bg-gray-50">
<Navigation />
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* 頁面標題 */}
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-4"></h1>
<p className="text-xl text-gray-600 mb-6">6</p>
{/* 測試詞彙 */}
<div className="inline-block bg-white rounded-lg shadow-sm p-4 border border-gray-200">
<span className="text-gray-600 text-sm"></span>
<button
onClick={() => setShowPopup(true)}
className="ml-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium"
>
"elaborate"
</button>
</div>
</div>
{/* 版型選擇器 */}
<div className="flex justify-center mb-8">
<div className="bg-white rounded-xl shadow-sm p-2 inline-flex flex-wrap gap-2">
{designs.map((design) => (
<button
key={design.id}
onClick={() => setSelectedDesign(design.id)}
className={`px-4 py-2 rounded-lg transition-all ${
selectedDesign === design.id
? 'bg-primary text-white shadow-md'
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
}`}
>
{design.name}
</button>
))}
</div>
</div>
{/* 版型預覽 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* 左側:版型說明 */}
<div className="bg-white rounded-xl shadow-lg p-6">
<h2 className="text-2xl font-bold text-gray-900 mb-4">
{designs.find(d => d.id === selectedDesign)?.name}
</h2>
<p className="text-gray-600 mb-6">
{designs.find(d => d.id === selectedDesign)?.description}
</p>
<div className="space-y-4">
<div>
<h3 className="font-semibold text-gray-900 mb-2"></h3>
<ul className="text-sm text-gray-700 space-y-1">
{getDesignFeatures(selectedDesign).map((feature, idx) => (
<li key={idx} className="flex items-center gap-2">
<span className="text-blue-500"></span>
{feature}
</li>
))}
</ul>
</div>
<div>
<h3 className="font-semibold text-gray-900 mb-2"></h3>
<p className="text-sm text-gray-700">
{getDesignScenario(selectedDesign)}
</p>
</div>
</div>
</div>
{/* 右側:版型預覽 */}
<div className="bg-gradient-to-br from-gray-100 to-gray-200 rounded-xl p-8 flex items-center justify-center min-h-[600px] relative">
<div className="text-center">
<p className="text-gray-600 mb-4"></p>
<button
onClick={() => setShowPopup(true)}
className="px-6 py-3 bg-primary text-white rounded-lg hover:bg-primary-hover transition-colors font-medium shadow-lg"
>
"{mockWord.word}"
</button>
</div>
{/* 模擬背景文字 */}
<div className="absolute inset-4 opacity-10 text-gray-500 text-lg leading-relaxed pointer-events-none">
This is a sample sentence where you can click on any word to see the elaborate definition and detailed explanation.
</div>
</div>
</div>
</div>
{/* 詞彙彈窗 - 根據選擇的設計風格渲染 */}
{showPopup && (
<>
{/* 背景遮罩 */}
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40"
onClick={() => setShowPopup(false)}
/>
{/* 彈窗內容 */}
<div className="fixed inset-0 flex items-center justify-center z-50 p-4">
{renderVocabPopup(selectedDesign, mockWord, () => setShowPopup(false))}
</div>
</>
)}
</div>
)
}
// 渲染不同風格的詞彙彈窗
function renderVocabPopup(design: string, word: any, onClose: () => void) {
const handleSave = () => {
alert(`✅ 已將「${word.word}」保存到詞卡!`)
onClose()
}
switch (design) {
case 'modern':
return <ModernGlassDesign word={word} onClose={onClose} onSave={handleSave} />
case 'classic':
return <ClassicCardDesign word={word} onClose={onClose} onSave={handleSave} />
case 'minimal':
return <MinimalDesign word={word} onClose={onClose} onSave={handleSave} />
case 'magazine':
return <MagazineDesign word={word} onClose={onClose} onSave={handleSave} />
case 'mobile':
return <MobileAppDesign word={word} onClose={onClose} onSave={handleSave} />
case 'learning':
return <LearningCardDesign word={word} onClose={onClose} onSave={handleSave} />
case 'flashcard':
return <FlashcardDetailDesign word={word} onClose={onClose} onSave={handleSave} />
default:
return <ModernGlassDesign word={word} onClose={onClose} onSave={handleSave} />
}
}
// 1. 現代玻璃風格
function ModernGlassDesign({ word, onClose, onSave }: any) {
return (
<div
className="bg-white rounded-2xl shadow-2xl border-0 w-80 backdrop-blur-sm"
style={{
background: 'rgba(255, 255, 255, 0.98)',
backdropFilter: 'blur(12px)',
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.8)'
}}
>
<div className="relative p-5 pb-0">
<button
onClick={onClose}
className="absolute top-3 right-3 w-6 h-6 rounded-full bg-gray-100 hover:bg-gray-200 transition-colors flex items-center justify-center text-gray-500"
>
</button>
<div className="pr-8">
<div className="flex items-baseline gap-3 mb-1">
<h3 className="text-2xl font-bold text-gray-900">{word.word}</h3>
{word.isHighValue && <span className="text-yellow-500 text-lg"></span>}
</div>
<div className="flex items-center gap-3 text-gray-600">
<span className="text-sm font-medium">{word.pronunciation}</span>
<span className="text-xs px-2 py-1 rounded-full font-medium bg-blue-100 text-blue-700">
{word.difficultyLevel}
</span>
</div>
</div>
</div>
<div className="px-5 py-4 space-y-4">
<div>
<div className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2"></div>
<div className="text-lg font-semibold text-gray-900">{word.translation}</div>
</div>
<div>
<div className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2"></div>
<div className="text-sm text-gray-700 leading-relaxed">{word.definition}</div>
</div>
<div>
<div className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2"></div>
<div className="flex flex-wrap gap-1">
{word.synonyms.map((synonym: string, idx: number) => (
<span key={idx} className="bg-blue-50 text-blue-700 px-2 py-1 rounded text-xs font-medium border border-blue-200">
{synonym}
</span>
))}
</div>
</div>
</div>
<div className="p-5 pt-2 border-t border-gray-100">
<button
onClick={onSave}
className="w-full bg-gradient-to-r from-blue-600 to-blue-700 text-white py-3 px-4 rounded-xl font-medium hover:from-blue-700 hover:to-blue-800 transition-all duration-200 transform hover:scale-105 active:scale-95 flex items-center justify-center gap-2 shadow-lg"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
<span className="text-sm font-medium"></span>
</button>
</div>
</div>
)
}
// 2. 經典卡片風格
function ClassicCardDesign({ word, onClose, onSave }: any) {
return (
<div className="bg-white rounded-lg shadow-lg border border-gray-200 w-96 max-w-md">
<div className="bg-gradient-to-r from-blue-600 to-blue-700 text-white p-4 rounded-t-lg">
<div className="flex items-center justify-between">
<div>
<h3 className="text-xl font-bold">{word.word}</h3>
<p className="text-blue-100 text-sm">{word.pronunciation}</p>
</div>
<button
onClick={onClose}
className="text-blue-100 hover:text-white w-8 h-8 rounded-full hover:bg-blue-800 transition-colors flex items-center justify-center"
>
</button>
</div>
</div>
<div className="p-4 space-y-4">
<div className="flex items-center gap-3">
<span className="bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm font-medium">
{word.partOfSpeech}
</span>
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
word.difficultyLevel === 'A1' || word.difficultyLevel === 'A2' ? 'bg-green-100 text-green-700' :
word.difficultyLevel === 'B1' || word.difficultyLevel === 'B2' ? 'bg-yellow-100 text-yellow-700' :
'bg-red-100 text-red-700'
}`}>
{word.difficultyLevel}
</span>
{word.isHighValue && (
<span className="bg-yellow-100 text-yellow-700 px-2 py-1 rounded-full text-xs font-medium">
</span>
)}
</div>
<div className="bg-blue-50 rounded-lg p-3 border-l-4 border-blue-400">
<h4 className="font-semibold text-blue-900 mb-1"></h4>
<p className="text-blue-800 font-medium">{word.translation}</p>
</div>
<div className="bg-gray-50 rounded-lg p-3">
<h4 className="font-semibold text-gray-900 mb-2"></h4>
<p className="text-gray-700 text-sm leading-relaxed">{word.definition}</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 mb-2"></h4>
<div className="flex flex-wrap gap-2">
{word.synonyms.map((synonym: string, idx: number) => (
<span key={idx} className="bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm">
{synonym}
</span>
))}
</div>
</div>
<button
onClick={onSave}
className="w-full bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors flex items-center justify-center gap-2"
>
<span>💾</span>
<span></span>
</button>
</div>
</div>
)
}
// 3. 極簡風格
function MinimalDesign({ word, onClose, onSave }: any) {
return (
<div className="bg-white rounded-lg shadow-lg w-72 border border-gray-100">
<div className="p-4 border-b border-gray-100">
<div className="flex items-center justify-between">
<h3 className="text-xl font-bold text-gray-900">{word.word}</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 w-5 h-5 flex items-center justify-center"
>
</button>
</div>
<p className="text-sm text-gray-500 mt-1">{word.pronunciation}</p>
</div>
<div className="p-4 space-y-3">
<div>
<span className="text-lg font-medium text-gray-900">{word.translation}</span>
<span className="ml-2 text-xs text-gray-500">({word.partOfSpeech})</span>
</div>
<p className="text-sm text-gray-600 leading-relaxed">{word.definition}</p>
<div className="flex flex-wrap gap-1 pt-2">
{word.synonyms.slice(0, 3).map((synonym: string, idx: number) => (
<span key={idx} className="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded">
{synonym}
</span>
))}
</div>
<button
onClick={onSave}
className="w-full mt-4 bg-gray-900 text-white py-2 rounded text-sm font-medium hover:bg-gray-800 transition-colors"
>
</button>
</div>
</div>
)
}
// 4. 雜誌排版風格
function MagazineDesign({ word, onClose, onSave }: any) {
return (
<div className="bg-white shadow-xl w-96 max-w-md" style={{ fontFamily: 'Georgia, serif' }}>
<div className="p-6 border-b-2 border-gray-900">
<div className="flex items-start justify-between">
<div>
<h3 className="text-3xl font-bold text-gray-900 mb-1" style={{ fontFamily: 'Georgia, serif' }}>
{word.word}
</h3>
<div className="flex items-center gap-4 text-sm text-gray-600">
<span className="italic">{word.pronunciation}</span>
<span className="font-semibold">{word.partOfSpeech}</span>
<span className="bg-gray-900 text-white px-2 py-1 text-xs">{word.difficultyLevel}</span>
</div>
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 text-xl"
>
×
</button>
</div>
</div>
<div className="p-6">
<div className="mb-4">
<h4 className="text-xs font-bold text-gray-500 uppercase tracking-widest mb-2 border-b border-gray-200 pb-1">
Translation
</h4>
<p className="text-xl font-semibold text-gray-900" style={{ fontFamily: 'Georgia, serif' }}>
{word.translation}
</p>
</div>
<div className="mb-4">
<h4 className="text-xs font-bold text-gray-500 uppercase tracking-widest mb-2">Definition</h4>
<p className="text-sm text-gray-700 leading-relaxed" style={{ textAlign: 'justify' }}>
{word.definition}
</p>
</div>
<div className="mb-6">
<h4 className="text-xs font-bold text-gray-500 uppercase tracking-widest mb-2">Synonyms</h4>
<p className="text-sm text-gray-700 italic">
{word.synonyms.join(', ')}
</p>
</div>
<button
onClick={onSave}
className="w-full bg-gray-900 text-white py-3 font-semibold uppercase tracking-wider text-sm hover:bg-gray-800 transition-colors"
>
Add to Collection
</button>
</div>
</div>
)
}
// 5. 移動應用風格
function MobileAppDesign({ word, onClose, onSave }: any) {
return (
<div className="bg-white rounded-3xl shadow-xl w-80 overflow-hidden">
<div className="bg-gray-50 px-4 py-3 border-b border-gray-200">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center text-white font-bold text-sm">
{word.word[0].toUpperCase()}
</div>
<span className="font-medium text-gray-900"></span>
</div>
<button
onClick={onClose}
className="text-blue-500 font-medium text-sm"
>
</button>
</div>
</div>
<div className="p-4">
<div className="text-center mb-4">
<h3 className="text-2xl font-bold text-gray-900 mb-1">{word.word}</h3>
<p className="text-gray-500 text-sm">{word.pronunciation}</p>
<div className="flex items-center justify-center gap-2 mt-2">
<span className="bg-gray-100 text-gray-700 px-2 py-1 rounded-full text-xs">
{word.partOfSpeech}
</span>
<span className={`px-2 py-1 rounded-full text-xs ${
word.difficultyLevel === 'A1' || word.difficultyLevel === 'A2' ? 'bg-green-100 text-green-700' :
word.difficultyLevel === 'B1' || word.difficultyLevel === 'B2' ? 'bg-yellow-100 text-yellow-700' :
'bg-red-100 text-red-700'
}`}>
{word.difficultyLevel}
</span>
</div>
</div>
<div className="space-y-3">
<div className="flex items-start gap-3 py-2">
<div className="w-8 text-center">🌏</div>
<div className="flex-1">
<div className="font-medium text-gray-900">{word.translation}</div>
<div className="text-xs text-gray-500"></div>
</div>
</div>
<div className="flex items-start gap-3 py-2 border-t border-gray-100">
<div className="w-8 text-center">📝</div>
<div className="flex-1">
<div className="text-sm text-gray-700 leading-relaxed">{word.definition}</div>
<div className="text-xs text-gray-500 mt-1"></div>
</div>
</div>
<div className="flex items-start gap-3 py-2 border-t border-gray-100">
<div className="w-8 text-center">🔗</div>
<div className="flex-1">
<div className="flex flex-wrap gap-1">
{word.synonyms.slice(0, 3).map((synonym: string, idx: number) => (
<span key={idx} className="bg-gray-100 text-gray-700 px-2 py-1 rounded-full text-xs">
{synonym}
</span>
))}
</div>
<div className="text-xs text-gray-500 mt-1"></div>
</div>
</div>
</div>
<button
onClick={onSave}
className="w-full mt-6 bg-blue-500 text-white py-3 rounded-xl font-medium text-base hover:bg-blue-600 transition-colors active:bg-blue-700"
>
</button>
</div>
</div>
)
}
// 6. 學習卡片風格
function LearningCardDesign({ word, onClose, onSave }: any) {
return (
<div className="bg-white rounded-xl shadow-lg w-96 max-w-md overflow-hidden">
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-5 border-b border-blue-200">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center text-white font-bold">
{word.word[0].toUpperCase()}
</div>
<div>
<h3 className="text-lg font-bold text-gray-900">{word.word}</h3>
<p className="text-sm text-gray-600">{word.partOfSpeech}</p>
</div>
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
>
</button>
</div>
<div className="flex items-center gap-3">
<span className="text-sm font-medium text-gray-700">{word.pronunciation}</span>
<button className="text-blue-600 hover:text-blue-700 p-1 rounded-full hover:bg-blue-50 transition-colors">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
</button>
</div>
</div>
<div className="p-5 space-y-4">
<div className="bg-green-50 rounded-lg p-4">
<h4 className="font-semibold text-green-900 mb-2 text-left"></h4>
<p className="text-green-800 font-medium text-left">{word.translation}</p>
</div>
<div className="bg-gray-50 rounded-lg p-4">
<h4 className="font-semibold text-gray-900 mb-2 text-left"></h4>
<p className="text-gray-700 text-sm text-left leading-relaxed">{word.definition}</p>
</div>
<div className="bg-blue-50 rounded-lg p-4">
<h4 className="font-semibold text-blue-900 mb-2 text-left"></h4>
<div className="flex flex-wrap gap-2">
{word.synonyms.map((synonym: string, idx: number) => (
<span key={idx} className="bg-white text-blue-700 px-3 py-1 rounded-full text-sm border border-blue-200">
{synonym}
</span>
))}
</div>
</div>
<button
onClick={onSave}
className="w-full bg-primary text-white py-3 rounded-lg font-medium hover:bg-primary-hover transition-colors flex items-center justify-center gap-2"
>
<span>💾</span>
<span></span>
</button>
</div>
</div>
)
}
// 7. 詞卡風格 - 參考詞卡詳細頁面
function FlashcardDetailDesign({ word, onClose, onSave }: any) {
// 獲取CEFR等級顏色
const getCEFRColor = (level: string) => {
switch (level) {
case 'A1': return 'bg-green-100 text-green-700 border-green-200'
case 'A2': return 'bg-blue-100 text-blue-700 border-blue-200'
case 'B1': return 'bg-yellow-100 text-yellow-700 border-yellow-200'
case 'B2': return 'bg-orange-100 text-orange-700 border-orange-200'
case 'C1': return 'bg-red-100 text-red-700 border-red-200'
case 'C2': return 'bg-purple-100 text-purple-700 border-purple-200'
default: return 'bg-gray-100 text-gray-700 border-gray-200'
}
}
return (
<div className="bg-white rounded-xl shadow-lg w-96 max-w-md overflow-hidden">
{/* 標題區 - 漸層背景 */}
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-5 border-b border-blue-200">
{/* 關閉按鈕 - 獨立一行 */}
<div className="flex justify-end mb-3">
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 w-6 h-6 rounded-full bg-white bg-opacity-80 hover:bg-opacity-100 transition-all flex items-center justify-center"
>
</button>
</div>
{/* 詞彙標題 */}
<div className="mb-3">
<h3 className="text-2xl font-bold text-gray-900">{word.word}</h3>
</div>
{/* 詞性、發音、播放按鈕、CEFR */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="text-sm bg-gray-100 text-gray-700 px-3 py-1 rounded-full">
{word.partOfSpeech}
</span>
<span className="text-base text-gray-600">{word.pronunciation}</span>
<button className="w-8 h-8 bg-blue-600 rounded-full flex items-center justify-center text-white hover:bg-blue-700 transition-colors">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
</svg>
</button>
</div>
{/* CEFR標籤 - 在播放按鈕那一行的最右邊 */}
<span className={`px-3 py-1 rounded-full text-sm font-medium border ${getCEFRColor(word.difficultyLevel)}`}>
{word.difficultyLevel}
</span>
</div>
</div>
{/* 內容區 - 彩色區塊設計 */}
<div className="p-4 space-y-4">
{/* 翻譯區塊 - 綠色 */}
<div className="bg-green-50 rounded-lg p-3 border border-green-200">
<h4 className="font-semibold text-green-900 mb-2 text-left text-sm"></h4>
<p className="text-green-800 font-medium text-left">{word.translation}</p>
</div>
{/* 定義區塊 - 灰色 */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
<h4 className="font-semibold text-gray-900 mb-2 text-left text-sm"></h4>
<p className="text-gray-700 text-left text-sm leading-relaxed">{word.definition}</p>
</div>
{/* 同義詞區塊 - 紫色 */}
<div className="bg-purple-50 rounded-lg p-3 border border-purple-200">
<h4 className="font-semibold text-purple-900 mb-2 text-left text-sm"></h4>
<div className="flex flex-wrap gap-1">
{word.synonyms.slice(0, 4).map((synonym: string, idx: number) => (
<span key={idx} className="bg-white text-purple-700 px-2 py-1 rounded-full text-xs border border-purple-200 font-medium">
{synonym}
</span>
))}
</div>
</div>
</div>
{/* 保存按鈕 - 底部平均延展 */}
<div className="p-4 pt-2">
<button
onClick={onSave}
className="w-full bg-primary text-white py-3 rounded-lg font-medium hover:bg-primary-hover transition-colors flex items-center justify-center gap-2"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
<span className="font-medium"></span>
</button>
</div>
</div>
)
}
// 輔助函數
function getDesignFeatures(design: string): string[] {
const features = {
modern: [
'毛玻璃背景效果',
'大尺寸陰影和圓角',
'漸層按鈕設計',
'微互動動畫',
'現代配色方案'
],
classic: [
'藍色漸層標題欄',
'清晰的區塊分隔',
'傳統卡片佈局',
'顏色編碼標籤',
'穩重的設計風格'
],
minimal: [
'極簡配色方案',
'去除多餘裝飾',
'重點信息突出',
'輕量化設計',
'快速瀏覽體驗'
],
magazine: [
'雜誌式字體',
'專業排版設計',
'大標題小說明',
'黑白主色調',
'閱讀導向佈局'
],
mobile: [
'iOS/Android風格',
'圓角和圖標設計',
'列表式信息展示',
'觸控友好設計',
'移動端優化'
],
learning: [
'學習功能一致性',
'彩色區塊設計',
'教育導向佈局',
'清晰的信息分類',
'學習體驗優化'
],
flashcard: [
'詞卡詳細頁面風格',
'右上角CEFR標籤',
'漸層標題背景',
'彩色內容區塊',
'響應式設計優化'
]
}
return features[design as keyof typeof features] || []
}
function getDesignScenario(design: string): string {
const scenarios = {
modern: '適合追求現代感和科技感的用戶,特別是年輕用戶群體。設計前衛,視覺效果佳。',
classic: '適合喜歡傳統界面的用戶,信息展示清晰,功能分區明確,適合學術或商務場景。',
minimal: '適合追求效率的用戶,減少視覺干擾,快速獲取核心信息,適合頻繁使用的場景。',
magazine: '適合喜歡閱讀體驗的用戶,類似字典或雜誌的專業排版,適合深度學習。',
mobile: '適合手機用戶,觸控友好,符合移動端應用的使用習慣,適合隨時隨地學習。',
learning: '與現有學習功能保持一致,用戶體驗連貫,適合在學習流程中使用。',
flashcard: '完全參考詞卡詳細頁面設計,提供最一致的用戶體驗,適合需要詳細信息展示的場景。'
}
return scenarios[design as keyof typeof scenarios] || ''
}

View File

@ -1,509 +0,0 @@
# 個人化詞彙庫功能規格
## 🎯 功能概述
個人化詞彙庫是一個用戶專屬的詞彙管理系統,允許用戶收集、組織和追蹤自己的學習詞彙,並根據學習表現提供個人化的學習建議。
## 📋 核心功能需求
### 1. 詞彙收集功能
#### 1.1 手動添加詞彙
- **功能描述**:用戶可以手動輸入新詞彙到個人詞彙庫
- **輸入欄位**
- 英文詞彙(必填)
- 詞性(可選,下拉選單)
- 發音(可選,自動生成或手動輸入)
- 定義(可選,自動生成或手動輸入)
- 中文翻譯(可選,自動生成或手動輸入)
- 個人筆記(可選,用戶自訂)
- **自動補全**:系統自動查詢並填入詞彙資訊
- **重複檢查**:避免添加重複詞彙
#### 1.2 學習中收集
- **學習頁面收藏**:在任何測驗模式中點擊「收藏」按鈕
- **困難詞彙標記**:答錯的詞彙自動標記為需要加強
- **快速收集**:一鍵添加當前學習的詞彙到個人庫
#### 1.3 批量導入
- **文字檔導入**:支援 .txt 格式的詞彙列表
- **CSV 導入**:支援結構化的詞彙資料
- **從學習記錄導入**:將過往答錯的詞彙批量加入
### 2. 詞彙組織功能
#### 2.1 分類管理
- **預設分類**
- 新學詞彙New
- 學習中Learning
- 熟悉Familiar
- 精通Mastered
- 困難詞彙Difficult
- **自訂分類**:用戶可創建自己的分類標籤
- **多重分類**:單一詞彙可屬於多個分類
#### 2.2 標籤系統
- **難度標籤**A1, A2, B1, B2, C1, C2
- **主題標籤**:商業、旅遊、學術、日常等
- **來源標籤**:書籍、電影、新聞、會話等
- **自訂標籤**:用戶可創建個人標籤
#### 2.3 優先級管理
- **高優先級**:急需掌握的詞彙
- **中優先級**:重要但不緊急的詞彙
- **低優先級**:選擇性學習的詞彙
### 3. 學習追蹤功能
#### 3.1 熟悉度評分
- **評分機制**0-100 分的熟悉度評分
- **多維度評估**
- 認識度Recognition看到詞彙能理解
- 回想度Recall能主動想起詞彙
- 應用度Application能在語境中正確使用
- **動態調整**:根據測驗表現自動調整評分
#### 3.2 學習歷史
- **學習次數**:詞彙被學習的總次數
- **正確率**:各種測驗模式的正確率統計
- **最後學習時間**:記錄最近一次學習時間
- **學習軌跡**:詳細的學習歷程記錄
#### 3.3 遺忘曲線追蹤
- **複習提醒**:基於遺忘曲線的智能提醒
- **複習間隔**:動態調整複習時間間隔
- **記憶強度**:評估詞彙在記憶中的鞏固程度
### 4. 個人化學習功能
#### 4.1 智能推薦
- **弱點分析**:識別用戶的學習弱點
- **相似詞彙**:推薦語義相關的詞彙
- **同根詞擴展**:推薦同詞根的相關詞彙
- **搭配詞推薦**:推薦常見的詞彙搭配
#### 4.2 個人化測驗
- **客製化題組**:根據個人詞彙庫生成測驗
- **弱點加強**:針對困難詞彙的專門訓練
- **複習模式**:基於遺忘曲線的複習測驗
- **混合練習**:結合不同來源詞彙的綜合測驗
#### 4.3 學習計劃
- **每日目標**:設定每日學習詞彙數量
- **週期計劃**:制定短期和長期學習目標
- **進度追蹤**:視覺化顯示學習進度
- **成就系統**:學習里程碑和獎勵機制
## 🗃️ 資料結構設計
### 個人詞彙資料模型
```typescript
interface PersonalVocabulary {
id: string;
userId: string;
word: string;
partOfSpeech?: string;
pronunciation?: string;
definition?: string;
translation?: string;
personalNotes?: string;
// 分類和標籤
categories: string[];
tags: string[];
priority: 'high' | 'medium' | 'low';
// 學習追蹤
familiarityScore: number; // 0-100
recognitionScore: number; // 0-100
recallScore: number; // 0-100
applicationScore: number; // 0-100
// 學習統計
totalPractices: number;
correctAnswers: number;
incorrectAnswers: number;
lastPracticed: Date;
nextReview: Date;
// 測驗模式統計
flipMemoryStats: TestModeStats;
vocabChoiceStats: TestModeStats;
sentenceFillStats: TestModeStats;
// ... 其他測驗模式
// 元資料
createdAt: Date;
updatedAt: Date;
source?: string; // 詞彙來源
}
interface TestModeStats {
attempts: number;
correct: number;
averageTime: number; // 平均回答時間(秒)
lastAttempt: Date;
}
```
### 學習會話記錄
```typescript
interface LearningSession {
id: string;
userId: string;
startTime: Date;
endTime: Date;
mode: string;
vocabulariesPracticed: string[]; // 詞彙 IDs
totalQuestions: number;
correctAnswers: number;
timeSpent: number; // 秒
performance: SessionPerformance;
}
interface SessionPerformance {
accuracy: number; // 正確率
speed: number; // 平均回答速度
improvement: number; // 相對上次的進步
weakWords: string[]; // 表現較差的詞彙
strongWords: string[]; // 表現較好的詞彙
}
```
## 🔧 技術實現方案
### 前端實現
#### 1. 狀態管理
```typescript
// 使用 Context API 或 Zustand
interface PersonalVocabStore {
vocabularies: PersonalVocabulary[];
currentSession: LearningSession | null;
filters: VocabFilters;
// Actions
addVocabulary: (vocab: Partial<PersonalVocabulary>) => void;
updateVocabulary: (id: string, updates: Partial<PersonalVocabulary>) => void;
deleteVocabulary: (id: string) => void;
updateFamiliarity: (id: string, testResult: TestResult) => void;
// ...
}
```
#### 2. 本地儲存策略
- **IndexedDB**:大量詞彙資料的本地儲存
- **localStorage**:用戶偏好和設定
- **同步機制**:與伺服器的雙向同步
#### 3. UI 組件結構
```
/components/PersonalVocab/
├── VocabLibrary.tsx # 詞彙庫主頁面
├── VocabCard.tsx # 單一詞彙卡片
├── VocabForm.tsx # 新增/編輯詞彙表單
├── VocabFilters.tsx # 篩選和搜尋
├── VocabStats.tsx # 學習統計
├── CategoryManager.tsx # 分類管理
├── TagManager.tsx # 標籤管理
└── ReviewScheduler.tsx # 複習排程
```
### 後端實現
#### 1. API 端點設計
```
GET /api/personal-vocab # 獲取用戶詞彙庫
POST /api/personal-vocab # 新增詞彙
PUT /api/personal-vocab/:id # 更新詞彙
DELETE /api/personal-vocab/:id # 刪除詞彙
POST /api/personal-vocab/batch # 批量操作
GET /api/personal-vocab/stats # 獲取學習統計
POST /api/personal-vocab/practice # 記錄練習結果
GET /api/personal-vocab/review # 獲取需要複習的詞彙
GET /api/learning-sessions # 獲取學習會話記錄
POST /api/learning-sessions # 記錄學習會話
```
#### 2. 資料庫設計
```sql
-- 個人詞彙表
CREATE TABLE personal_vocabularies (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
word VARCHAR(100) NOT NULL,
part_of_speech VARCHAR(20),
pronunciation VARCHAR(200),
definition TEXT,
translation TEXT,
personal_notes TEXT,
familiarity_score INTEGER DEFAULT 0,
recognition_score INTEGER DEFAULT 0,
recall_score INTEGER DEFAULT 0,
application_score INTEGER DEFAULT 0,
total_practices INTEGER DEFAULT 0,
correct_answers INTEGER DEFAULT 0,
incorrect_answers INTEGER DEFAULT 0,
last_practiced TIMESTAMP,
next_review TIMESTAMP,
priority VARCHAR(10) DEFAULT 'medium',
source VARCHAR(100),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 詞彙分類表
CREATE TABLE vocab_categories (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
name VARCHAR(50) NOT NULL,
color VARCHAR(7), -- HEX color
description TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- 詞彙-分類關聯表
CREATE TABLE vocab_category_relations (
vocab_id UUID REFERENCES personal_vocabularies(id),
category_id UUID REFERENCES vocab_categories(id),
PRIMARY KEY (vocab_id, category_id)
);
-- 學習會話表
CREATE TABLE learning_sessions (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
mode VARCHAR(50) NOT NULL,
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP,
total_questions INTEGER DEFAULT 0,
correct_answers INTEGER DEFAULT 0,
time_spent INTEGER DEFAULT 0, -- 秒
created_at TIMESTAMP DEFAULT NOW()
);
-- 詞彙練習記錄表
CREATE TABLE vocab_practice_records (
id UUID PRIMARY KEY,
session_id UUID REFERENCES learning_sessions(id),
vocab_id UUID REFERENCES personal_vocabularies(id),
test_mode VARCHAR(50) NOT NULL,
is_correct BOOLEAN NOT NULL,
response_time INTEGER, -- 毫秒
user_answer TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
```
## 🎨 使用者介面設計
### 主要頁面結構
#### 1. 詞彙庫總覽頁面 (`/personal-vocab`)
```
┌─────────────────────────────────────┐
│ 🏠 個人詞彙庫 (1,247 個詞彙) │
├─────────────────────────────────────┤
│ [搜尋框] [篩選] [排序] [新增詞彙] │
├─────────────────────────────────────┤
│ 📊 學習統計 │
│ • 今日學習12 個詞彙 │
│ • 本週進度85% 完成 │
│ • 平均正確率78% │
├─────────────────────────────────────┤
│ 📚 詞彙分類 │
│ [新學詞彙 124] [學習中 89] [熟悉 856] │
│ [困難詞彙 45] [我的收藏 67] │
├─────────────────────────────────────┤
│ 📝 詞彙列表 │
│ ┌─────────────────────────────────┐ │
│ │ brought (動詞) ⭐⭐⭐⭐☆ │ │
│ │ 發音: /brɔːt/ | 熟悉度: 80% │ │
│ │ 定義: Past tense of bring... │ │
│ │ [編輯] [練習] [刪除] │ │
│ └─────────────────────────────────┘ │
│ (更多詞彙卡片...) │
└─────────────────────────────────────┘
```
#### 2. 詞彙詳情頁面 (`/personal-vocab/:id`)
```
┌─────────────────────────────────────┐
│ ← 返回詞彙庫 │
├─────────────────────────────────────┤
│ 📝 brought │
│ 動詞 | 難度: B1 | 優先級: 高 │
├─────────────────────────────────────┤
│ 🔊 發音: /brɔːt/ [播放] │
│ 📖 定義: Past tense of bring... │
│ 🈲 翻譯: 提出、帶來 │
│ 📝 個人筆記: [編輯區域] │
├─────────────────────────────────────┤
│ 📊 學習統計 │
│ • 熟悉度: ████████░░ 80% │
│ • 總練習: 25 次 │
│ • 正確率: 85% │
│ • 上次練習: 2 小時前 │
│ • 下次複習: 明天 14:00 │
├─────────────────────────────────────┤
│ 🎯 各模式表現 │
│ • 翻卡記憶: 90% (15/15) │
│ • 詞彙選擇: 75% (12/16) │
│ • 例句填空: 80% (8/10) │
├─────────────────────────────────────┤
│ 🏷️ 分類標籤 │
│ [學習中] [商業英語] [重要詞彙] │
│ [+ 添加標籤] │
├─────────────────────────────────────┤
│ 🎮 快速練習 │
│ [翻卡記憶] [詞彙選擇] [例句填空] │
└─────────────────────────────────────┘
```
#### 3. 新增詞彙頁面 (`/personal-vocab/add`)
```
┌─────────────────────────────────────┐
新增詞彙到個人庫 │
├─────────────────────────────────────┤
│ 📝 英文詞彙: [輸入框] *必填 │
│ 🔍 [智能查詢] - 自動填入詞彙資訊 │
├─────────────────────────────────────┤
│ 📖 詞彙資訊 │
│ • 詞性: [下拉選單] │
│ • 發音: [輸入框] [生成] │
│ • 定義: [文字區域] [自動生成] │
│ • 翻譯: [輸入框] [自動翻譯] │
├─────────────────────────────────────┤
│ 🏷️ 分類設定 │
│ • 分類: [多選下拉] [新增分類] │
│ • 標籤: [標籤選擇器] [新增標籤] │
│ • 優先級: ⚫ 高 ⚪ 中 ⚪ 低 │
├─────────────────────────────────────┤
│ 📝 個人筆記 │
│ [多行文字輸入區域] │
├─────────────────────────────────────┤
│ [取消] [儲存詞彙] │
└─────────────────────────────────────┘
```
## 🔄 學習流程整合
### 1. 學習中的詞彙收集
- **收藏按鈕**:每個測驗頁面都有收藏功能
- **自動收集**:答錯的詞彙自動標記為需要加強
- **學習後提醒**:學習會話結束後推薦收藏的詞彙
### 2. 個人化測驗生成
- **我的詞彙測驗**:從個人庫選取詞彙生成測驗
- **弱點強化**:針對低熟悉度詞彙的專門練習
- **混合模式**:結合系統詞彙和個人詞彙的測驗
### 3. 複習提醒系統
- **智能排程**:基於遺忘曲線安排複習時間
- **推送通知**:瀏覽器通知提醒複習時間
- **複習優化**:根據表現調整複習頻率
## 📱 響應式設計考量
### 桌面版 (>= 1024px)
- **三欄布局**:側邊欄(分類)+ 詞彙列表 + 詳情面板
- **拖拉操作**:支援拖拉詞彙到不同分類
- **快速鍵**:鍵盤快速鍵支援
### 平板版 (768px - 1023px)
- **兩欄布局**:詞彙列表 + 詳情面板
- **觸控優化**:適合觸控操作的按鈕尺寸
### 手機版 (< 768px)
- **單欄布局**:全螢幕顯示當前頁面
- **底部導航**:快速切換功能
- **手勢支援**:滑動操作和長按功能
## 🚀 實施階段規劃
### 階段 1基礎詞彙管理 (第 1-2 週)
- [ ] 資料庫設計和建立
- [ ] 基本 CRUD API 開發
- [ ] 詞彙列表頁面
- [ ] 新增/編輯詞彙功能
- [ ] 基本搜尋和篩選
### 階段 2學習追蹤系統 (第 3-4 週)
- [ ] 熟悉度評分系統
- [ ] 學習歷史記錄
- [ ] 測驗結果整合
- [ ] 學習統計儀表板
### 階段 3智能化功能 (第 5-6 週)
- [ ] 遺忘曲線算法
- [ ] 複習提醒系統
- [ ] 個人化推薦
- [ ] 弱點分析
### 階段 4高級功能 (第 7-8 週)
- [ ] 批量導入/導出
- [ ] 學習計劃制定
- [ ] 成就系統
- [ ] 社交分享功能
## 📊 成功指標
### 用戶行為指標
- **詞彙庫使用率**> 80% 用戶建立個人詞彙庫
- **收藏率**> 60% 學習中的詞彙被收藏
- **複習完成率**> 70% 的複習提醒被完成
- **熟悉度提升**:平均熟悉度每週提升 5%
### 學習效果指標
- **記憶保持率**:複習詞彙的正確率 > 85%
- **學習效率**:個人詞彙的學習時間縮短 30%
- **長期記憶**30 天後的詞彙記憶率 > 70%
### 系統性能指標
- **回應時間**:詞彙庫載入時間 < 2
- **同步效率**:資料同步成功率 > 99%
- **儲存效率**:本地儲存空間使用 < 50MB
## 🔐 隱私和安全考量
### 資料隱私
- **用戶授權**:明確的隱私政策和使用條款
- **資料加密**:敏感資料的端到端加密
- **匿名化**:學習統計資料的匿名化處理
### 資料安全
- **備份機制**:定期備份用戶資料
- **版本控制**:資料變更的版本記錄
- **災難恢復**:資料遺失的恢復機制
## 🔮 未來擴展功能
### 社交學習功能
- **詞彙分享**:分享個人詞彙庫給其他用戶
- **學習小組**:創建詞彙學習小組
- **競賽模式**:與朋友的詞彙學習競賽
### AI 智能功能
- **智能生成**AI 生成個人化例句
- **發音評估**AI 評估發音準確度
- **學習建議**AI 提供個人化學習建議
### 多媒體功能
- **語音筆記**:錄音形式的個人筆記
- **圖片聯想**:為詞彙添加個人化圖片
- **影片連結**:連結相關的學習影片
---
## 📝 附註
本規格文件為個人化詞彙庫功能的完整設計,包含前後端實現細節和用戶體驗考量。實際開發時可根據優先級和資源情況分階段實施。
**建議優先實施階段 1 和階段 2**,建立穩固的基礎功能,再逐步添加智能化和高級功能。
---
*最後更新2025-09-20*
*版本v1.0*

File diff suppressed because it is too large Load Diff

View File

@ -1,616 +0,0 @@
# 詞卡管理系統簡化規格
## 設計理念
DramaLing詞卡管理採用**極簡設計理念**,專注於詞彙本身的學習價值,去除複雜的分類系統,讓用戶能夠專注於詞彙學習而非管理工作。
---
## 1. 簡化設計原則
### 1.1 去除卡組分類
- **原因**: 避免用戶花費過多時間在分類管理上
- **好處**: 降低認知負荷,專注學習本質
- **替代**: 使用搜尋和收藏功能進行詞卡組織
### 1.2 核心功能保留
- ✅ **詞卡展示**: 清晰的詞卡列表展示
- ✅ **搜尋功能**: 強大的搜尋和篩選功能
- ✅ **收藏系統**: 重要詞卡的標記和管理
- ✅ **學習統計**: 詞卡的學習進度和統計
---
## 2. 簡化後的系統架構
### 2.1 詞卡管理界面
#### 主要頁面結構
```
詞卡管理
├── 所有詞卡 (主要tab)
│ ├── 進階搜尋功能
│ ├── 多維度篩選
│ └── 詞卡列表展示
└── 收藏詞卡 (特殊tab)
├── 僅顯示收藏詞卡
└── 相同的操作功能
```
#### 簡化的Tab設計
```typescript
// 移除的Tab
❌ 我的卡組
❌ 未分類詞卡
// 保留的Tab
✅ 所有詞卡
✅ 收藏詞卡
```
### 2.2 詞卡展示優化
#### 展示信息調整
```
原來: 卡組: {cardSet.name}
修改: 創建: {createdAt}
原來: 卡組信息顯示
修改: 學習統計信息
```
#### 統計信息重構
```typescript
// 詞卡列表顯示
┌─────────────────────────────┐ [A1]
│ │ hello noun
│ 例句圖片 │ 你好
│ │ /həˈloʊ/ ▶️
└─────────────────────────────┘ 創建: 2025-09-17 | 掌握度: 95% | 複習: 15 次
[⭐收藏] [編輯] [刪除] [詳細] →
```
---
## 3. 功能重新設計
### 3.1 搜尋與篩選增強
#### 替代卡組的組織方式
```typescript
// 透過搜尋找到相關詞卡
1. 關鍵字搜尋: "商務" → 找到所有商務相關詞卡
2. CEFR篩選: "C1" → 找到所有高級詞卡
3. 詞性篩選: "verb" → 找到所有動詞
4. 掌握度篩選: "需加強" → 找到需要練習的詞卡
// 快速篩選按鈕
[需加強詞卡] [收藏詞卡] [高級詞彙] [清除全部]
```
#### 進階搜尋功能
- **多欄位搜尋**: 詞彙、翻譯、定義同時搜尋
- **智能篩選**: CEFR等級、詞性、掌握程度組合篩選
- **收藏篩選**: 快速找到重要詞卡
- **搜尋高亮**: 關鍵字黃色標記
### 3.2 收藏系統強化
#### 收藏作為主要組織方式
```typescript
// 收藏的多重意義
⭐ 重要詞彙: 學習價值高的詞卡
⭐ 困難詞彙: 需要重點練習的詞卡
⭐ 常用詞彙: 經常使用的實用詞卡
⭐ 個人標記: 用戶自定義的重要性標記
```
#### 收藏功能設計
- **一鍵收藏**: 詞卡列表和詳細頁面都可收藏
- **收藏統計**: 顯示收藏詞卡的數量
- **收藏過濾**: 專門的收藏詞卡tab頁面
- **狀態清晰**: 收藏按鈕的視覺狀態明確
---
## 4. 用戶體驗優化
### 4.1 簡化的學習流程
#### 核心學習路徑
```
1. 生成/保存詞卡 → 2. 搜尋/瀏覽詞卡 → 3. 收藏重要詞卡 → 4. 開始學習
```
#### 去除的複雜流程
```
❌ 創建卡組 → 分配詞卡 → 管理卡組 → 卡組學習
✅ 直接學習 → 搜尋篩選 → 收藏管理 → 重點練習
```
### 4.2 認知負荷降低
#### 決策簡化
- **原來**: 這個詞卡應該放在哪個卡組?
- **現在**: 這個詞卡重要嗎?需要收藏嗎?
#### 操作簡化
- **原來**: 創建卡組 → 命名 → 選顏色 → 分配詞卡
- **現在**: 點擊收藏 → 完成標記
### 4.3 專注學習本質
#### 學習導向設計
- **詞彙為主**: 界面以詞彙內容為核心
- **學習統計**: 突出學習進度和掌握程度
- **收藏引導**: 鼓勵用戶標記重要詞彙
- **搜尋優先**: 通過搜尋快速找到相關詞卡
---
## 5. 技術實現簡化
### 5.1 前端架構簡化
#### 移除的組件和邏輯
```typescript
❌ CardSetSelector // 卡組選擇器
❌ CardSetGrid // 卡組網格展示
❌ CreateCardSetForm // 創建卡組表單
❌ CardSetManagement // 卡組管理邏輯
✅ FlashcardList // 詞卡列表(保留)
✅ FlashcardDetail // 詞卡詳情(保留)
✅ SearchAndFilter // 搜尋篩選(增強)
✅ FavoriteManagement // 收藏管理(保留)
```
#### 狀態管理簡化
```typescript
// 移除的狀態
❌ selectedCardSet
❌ cardSets
❌ showCreateCardSetForm
// 保留的狀態
✅ flashcards
✅ searchTerm
✅ searchFilters
✅ favorites
```
### 5.2 API使用簡化
#### 需要的API端點
```typescript
✅ GET /api/flashcards // 獲取詞卡列表
✅ POST /api/flashcards // 創建詞卡
✅ PUT /api/flashcards/{id} // 更新詞卡
✅ DELETE /api/flashcards/{id} // 刪除詞卡
✅ POST /api/flashcards/{id}/favorite // 切換收藏
❌ GET /api/cardsets // 不再需要
❌ POST /api/cardsets // 不再需要
❌ PUT /api/cardsets/{id} // 不再需要
❌ DELETE /api/cardsets/{id} // 不再需要
```
---
## 6. 資料結構調整
### 6.1 詞卡實體簡化
#### Flashcard實體調整
```csharp
public class Flashcard
{
// 保留的核心欄位
public Guid Id { get; set; }
public Guid UserId { get; set; }
public string Word { get; set; } // 詞彙
public string Translation { get; set; } // 翻譯
public string Definition { get; set; } // 定義
public string? PartOfSpeech { get; set; } // 詞性
public string? Pronunciation { get; set; } // 發音
public string? Example { get; set; } // 例句
public string? ExampleTranslation { get; set; } // 例句翻譯
// SM-2學習算法欄位保留
public float EasinessFactor { get; set; }
public int Repetitions { get; set; }
public int IntervalDays { get; set; }
public DateTime NextReviewDate { get; set; }
// 學習統計(保留)
public int MasteryLevel { get; set; }
public int TimesReviewed { get; set; }
public int TimesCorrect { get; set; }
public DateTime? LastReviewedAt { get; set; }
// 用戶標記(保留並增強)
public bool IsFavorite { get; set; }
public bool IsArchived { get; set; }
public string? DifficultyLevel { get; set; } // CEFR等級
// 時間戳(保留)
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
// 移除的欄位
❌ public Guid CardSetId { get; set; } // 不再需要卡組關聯
❌ public virtual CardSet CardSet { get; set; } // 不再需要導航屬性
}
```
### 6.2 資料庫調整建議
#### 可選的架構調整
```sql
-- 如果要完全移除卡組功能,可以執行:
-- 1. 移除外鍵約束
ALTER TABLE flashcards DROP CONSTRAINT FK_flashcards_card_sets;
-- 2. 移除卡組ID欄位可選
ALTER TABLE flashcards DROP COLUMN card_set_id;
-- 3. 刪除卡組相關表格(可選)
DROP TABLE card_sets;
DROP TABLE flashcard_tags; -- 如果有標籤關聯
```
---
## 7. 用戶界面重新設計
### 7.1 主頁面布局
#### 簡化的詞卡管理頁面
```
詞卡管理
├── 頁面標題: "我的詞卡"
├── 操作按鈕: [新增詞卡] [AI生成詞卡]
├── Tab導航: [所有詞卡] [收藏詞卡]
├── 搜尋區域: 進階搜尋和篩選功能
└── 詞卡列表: 統一的詞卡展示
```
#### 詞卡展示信息
```
┌─────────────────────────────┐ [CEFR]
│ │ 詞彙 [詞性]
│ 例句圖片 │ 翻譯
│ │ /音標/ ▶️
└─────────────────────────────┘ 創建時間 | 掌握度 | 複習次數
[收藏] [編輯] [刪除] [詳細] →
```
### 7.2 詞卡詳細頁面
#### 移除卡組相關信息
```typescript
// 詞卡資訊區塊
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-gray-600">詞性:</span>
<span className="ml-2 font-medium">{flashcard.partOfSpeech}</span>
</div>
<div>
<span className="text-gray-600">創建時間:</span>
<span className="ml-2 font-medium">{createdAt}</span>
</div>
<div>
<span className="text-gray-600">下次複習:</span>
<span className="ml-2 font-medium">{nextReviewDate}</span>
</div>
<div>
<span className="text-gray-600">複習次數:</span>
<span className="ml-2 font-medium">{timesReviewed} 次</span>
</div>
<!-- 移除:所屬卡組信息 -->
</div>
```
---
## 8. 替代組織方案
### 8.1 基於搜尋的組織
#### 虛擬分類
用戶可以通過搜尋實現類似卡組的效果:
- **商務詞彙**: 搜尋"business, meeting, presentation"
- **日常對話**: 搜尋"hello, thanks, please"
- **高級詞彙**: 篩選"C1, C2"等級
- **需要練習**: 篩選"掌握度 < 60%"
#### 搜尋記憶功能
```typescript
// 可以考慮添加搜尋歷史
interface SearchHistory {
query: string;
filters: SearchFilters;
timestamp: Date;
resultCount: number;
}
// 常用搜尋快捷方式
const commonSearches = [
{ name: "需要練習", filters: { masteryLevel: 'low' } },
{ name: "收藏詞卡", filters: { onlyFavorites: true } },
{ name: "高級詞彙", filters: { cefrLevel: 'C1' } },
{ name: "動詞類", filters: { partOfSpeech: 'verb' } }
];
```
### 8.2 基於收藏的組織
#### 收藏的多重意義
- **⭐ 重點學習**: 困難或重要的詞卡
- **⭐ 常用詞彙**: 日常會用到的詞卡
- **⭐ 考試重點**: 考試必備的詞卡
- **⭐ 個人偏好**: 用戶特別喜歡的詞卡
#### 收藏增強功能(未來可選)
```typescript
// 可以考慮的收藏擴展
interface FavoriteEnhancement {
favoriteReason?: string; // 收藏原因
favoriteTags?: string[]; // 收藏標籤
favoriteNote?: string; // 個人筆記
favoriteDate: Date; // 收藏時間
}
```
---
## 9. 學習體驗優化
### 9.1 專注學習本質
#### 簡化的學習流程
```
1. 詞彙發現 (AI生成/句子分析)
2. 詞卡保存 (一鍵保存)
3. 詞卡瀏覽 (搜尋/篩選)
4. 重點標記 (收藏重要詞卡)
5. 開始學習 (進入學習模式)
```
#### 避免的複雜流程
```
❌ 詞彙發現 → 選擇卡組 → 分配詞卡 → 管理卡組 → 卡組學習
✅ 詞彙發現 → 保存詞卡 → 標記重要 → 直接學習
```
### 9.2 學習動機維持
#### 成就感來源
- **詞卡總數**: 累積學習的詞彙數量
- **掌握程度**: 每個詞卡的學習進展
- **收藏數量**: 重要詞卡的積累
- **學習統計**: 複習次數、正確率等
#### 進度可視化
```typescript
// 整體學習統計
interface OverallProgress {
totalWords: number; // 總詞卡數
masteredWords: number; // 已掌握詞卡數
favoriteWords: number; // 收藏詞卡數
averageMastery: number; // 平均掌握度
dailyProgress: number; // 每日學習進度
streak: number; // 連續學習天數
}
```
---
## 10. 技術債務清理
### 10.1 後端清理(可選)
#### 可以移除的服務
```csharp
❌ CardSetsController // 卡組管理API
❌ ICardSetService // 卡組業務邏輯
❌ CardSetRepository // 卡組資料存取
❌ CardSet Entity // 卡組實體(可選)
```
#### 簡化的FlashcardsController
```csharp
[ApiController]
[Route("api/[controller]")]
public class FlashcardsController : ControllerBase
{
// 簡化的詞卡CRUD
[HttpGet]
public async Task<ActionResult> GetFlashcards(
[FromQuery] string? search,
[FromQuery] bool favoritesOnly = false,
[FromQuery] int limit = 50,
[FromQuery] int offset = 0)
[HttpPost]
public async Task<ActionResult> CreateFlashcard([FromBody] CreateFlashcardRequest request)
[HttpPut("{id}")]
public async Task<ActionResult> UpdateFlashcard(Guid id, [FromBody] UpdateFlashcardRequest request)
[HttpDelete("{id}")]
public async Task<ActionResult> DeleteFlashcard(Guid id)
[HttpPost("{id}/favorite")]
public async Task<ActionResult> ToggleFavorite(Guid id)
}
```
### 10.2 前端清理
#### 移除的組件
```typescript
❌ CardSetSelector.tsx // 卡組選擇器
❌ CardSetGrid.tsx // 卡組網格展示
❌ CreateCardSetModal.tsx // 創建卡組彈窗
❌ CardSetManagement.tsx // 卡組管理邏輯
```
#### 簡化的狀態管理
```typescript
interface FlashcardPageState {
// 保留
flashcards: Flashcard[];
searchTerm: string;
searchFilters: SearchFilters;
activeTab: 'all-cards' | 'favorites';
// 移除
❌ cardSets: CardSet[];
❌ selectedCardSet: string | null;
❌ showCreateCardSetForm: boolean;
}
```
---
## 11. 用戶遷移策略
### 11.1 現有用戶處理
#### 卡組資料處理
```sql
-- 如果現有用戶已有卡組資料
-- 可以選擇以下策略之一:
-- 策略1: 保留但隱藏卡組功能
-- 在前端隱藏卡組相關界面,後端保留資料結構
-- 策略2: 將卡組信息轉為詞卡標籤
UPDATE flashcards
SET notes = CONCAT('來源: ', (SELECT name FROM card_sets WHERE id = flashcards.card_set_id))
WHERE card_set_id IS NOT NULL;
-- 策略3: 完全移除卡組,保留詞卡
-- 所有詞卡保留,移除卡組關聯
```
### 11.2 功能遷移指南
#### 給用戶的建議
```markdown
# 卡組功能移除通知
為了簡化學習體驗,我們移除了卡組分類功能。
您可以使用以下方式管理詞卡:
**搜尋功能**: 輸入關鍵字快速找到相關詞卡
**進階篩選**: 使用CEFR等級、詞性等篩選條件
**收藏系統**: 標記重要詞卡,建立個人重點學習列表
**快速篩選**: 一鍵找到需要練習的詞卡
這些功能比卡組分類更靈活,讓您專注於詞彙學習本身。
```
---
## 12. 優勢分析
### 12.1 簡化帶來的好處
#### 用戶體驗提升
- **學習專注**: 減少分類管理的時間投入
- **操作簡化**: 更少的決策點,更直觀的操作
- **認知減負**: 降低界面複雜度,專注學習內容
#### 開發維護優勢
- **代碼簡潔**: 移除複雜的卡組管理邏輯
- **測試簡化**: 減少測試場景和邊界條件
- **性能提升**: 減少資料庫查詢和前端狀態管理
#### 功能聚焦
- **核心功能**: 突出詞彙學習的核心價值
- **搜尋優化**: 強化搜尋功能作為主要組織方式
- **收藏增強**: 收藏功能成為主要的個人化標記
### 12.2 潛在挑戰與解決
#### 可能的用戶疑慮
- **擔心**: "沒有分類會不會很亂?"
- **解答**: 搜尋功能比分類更靈活,可以找到任何相關詞卡
- **擔心**: "如何管理大量詞卡?"
- **解答**: 收藏+搜尋+篩選的組合比卡組更強大
- **擔心**: "如何進行主題學習?"
- **解答**: 通過搜尋關鍵字實現主題學習,更靈活
---
## 13. 實施建議
### 13.1 分階段實施
#### 第一階段:界面簡化
- ✅ 移除卡組相關Tab和界面元素
- ✅ 簡化詞卡展示信息
- ✅ 強化搜尋和收藏功能
#### 第二階段:邏輯清理
- 🔄 移除前端卡組相關狀態和邏輯
- 🔄 簡化API調用
- 🔄 更新假資料結構
#### 第三階段:後端清理(可選)
- ⚠️ 評估是否移除後端卡組功能
- ⚠️ 資料庫架構調整
- ⚠️ API端點清理
### 13.2 回滾方案
#### 如果需要恢復卡組功能
- **前端**: Git回滾到卡組功能存在的版本
- **後端**: 保持現有API不變
- **資料**: 卡組資料結構保留,隨時可恢復
---
## 14. 總結
### 14.1 簡化價值
詞卡管理系統的簡化體現了**"少即是多"**的設計哲學:
- **專注本質**: 將注意力集中在詞彙學習本身
- **降低門檻**: 新用戶更容易上手使用
- **提升效率**: 減少管理時間,增加學習時間
- **靈活組織**: 搜尋比固定分類更靈活
### 14.2 功能對照
| 需求 | 卡組方案 | 簡化方案 |
|------|----------|----------|
| 詞彙分類 | 創建不同主題卡組 | 使用關鍵字搜尋 |
| 重點標記 | 創建"重點"卡組 | 使用收藏功能 |
| 進度追蹤 | 卡組級別進度 | 整體學習統計 |
| 主題學習 | 選擇特定卡組 | 搜尋相關詞彙 |
| 內容組織 | 卡組顏色分類 | CEFR+收藏+搜尋 |
### 14.3 設計哲學
**"完美不是無法再加,而是無法再減"** - 簡化的詞卡管理系統移除了非必要的複雜性,讓用戶能夠:
- 🎯 **專注學習**: 將時間用在學習詞彙而非管理分類
- 🔍 **靈活查找**: 搜尋比預設分類更精確靈活
- ⭐ **個人標記**: 收藏功能滿足個人化需求
- 📊 **進度清晰**: 整體統計比分散統計更有意義
---
**文件版本**: 1.0
**建立日期**: 2025-09-20
**設計理念**: 極簡主義,專注學習本質
**適用範圍**: DramaLing詞卡管理系統簡化版

File diff suppressed because it is too large Load Diff

View File

@ -1,711 +0,0 @@
# 詞彙生成與儲存系統規格
## 系統概述
DramaLing詞彙學習系統是一個基於AI的英語詞彙學習平台主要功能包括詞彙生成、智能分析、儲存管理和學習追蹤。系統採用前後端分離架構前端使用Next.js 14後端使用.NET 8 Web API資料庫使用SQLite + Entity Framework Core。
## 1. 詞彙生成功能規格
### 1.1 句子分析功能
#### 核心特性
- **AI分析引擎**: 使用Google Gemini API進行句子深度分析
- **多層次分析**: 包含詞彙分析、語法檢查、整句翻譯
- **個人化適配**: 根據用戶英語程度(A1-C2)調整分析深度
- **智能快取**: 使用快取機制避免重複AI調用
- **使用量追蹤**: 追蹤用戶查詢使用量並實施限制
#### API端點
- **端點**: `POST /api/ai/analyze-sentence`
- **功能**: 句子綜合分析
- **輸入參數** (AnalyzeSentenceRequest):
```json
{
"inputText": "string", // 用戶輸入的英文句子 (最多1000字元)
"userLevel": "string", // 用戶英語程度 (A1-C2, 預設A2)
"forceRefresh": boolean, // 是否強制重新分析
"analysisMode": "string" // 分析模式 ("full")
}
```
- **回應格式**:
```json
{
"success": boolean,
"data": {
"wordAnalysis": {
"[word]": {
"word": "string",
"translation": "string",
"definition": "string",
"partOfSpeech": "string",
"pronunciation": "string",
"isHighValue": boolean,
"difficultyLevel": "string" // CEFR等級
}
},
"sentenceMeaning": {
"translation": "string",
"explanation": "string"
},
"grammarCorrection": {
"hasErrors": boolean,
"originalText": "string",
"correctedText": "string",
"corrections": [
{
"errorType": "string",
"original": "string",
"corrected": "string",
"reason": "string"
}
]
},
"finalAnalysisText": "string",
"highValueWords": ["string"]
}
}
```
#### 快取機制 (SentenceAnalysisCache)
- **表名**: `SentenceAnalysisCache`
- **主要欄位**:
```csharp
public class SentenceAnalysisCache
{
public Guid Id { get; set; }
public string InputTextHash { get; set; } // SHA-256雜湊
public string InputText { get; set; } // 原始輸入文本
public string? CorrectedText { get; set; } // 修正後文本
public bool HasGrammarErrors { get; set; } // 是否有語法錯誤
public string? GrammarCorrections { get; set; } // JSON格式語法修正
public string AnalysisResult { get; set; } // JSON格式分析結果
public string? HighValueWords { get; set; } // JSON格式高價值詞彙
public string? PhrasesDetected { get; set; } // JSON格式檢測片語
public DateTime CreatedAt { get; set; }
public DateTime ExpiresAt { get; set; }
public int AccessCount { get; set; } // 存取次數
public DateTime? LastAccessedAt { get; set; }
}
```
#### 實現位置
- **前端**: `frontend/app/generate/page.tsx:29-100`
- **後端控制器**: `backend/DramaLing.Api/Controllers/AIController.cs`
- **服務層**: `backend/DramaLing.Api/Services/GeminiService.cs`
- **快取服務**: `backend/DramaLing.Api/Services/AnalysisCacheService.cs`
### 1.2 詞卡生成功能
#### 核心特性
- **智能萃取**: 支援詞彙萃取(vocabulary)和智能萃取(smart)兩種模式
- **批量生成**: 一次可生成1-20張詞卡
- **多元內容**: 包含單字、翻譯、定義、例句、同義詞、難度等級
- **測試模式**: 支援無認證的測試端點
#### API端點
- **端點**: `POST /api/ai/test/generate` (測試用,無需認證)
- **功能**: 生成詞卡
- **輸入參數** (GenerateCardsRequest):
```json
{
"inputText": "string", // 原始文本 (最多5000字元)
"extractionType": "string", // "vocabulary" | "smart"
"cardCount": number // 1-20
}
```
- **回應格式**:
```json
{
"success": boolean,
"data": {
"taskId": "guid",
"status": "completed",
"generatedCards": [
{
"word": "string",
"translation": "string",
"definition": "string",
"partOfSpeech": "string",
"pronunciation": "string",
"example": "string",
"exampleTranslation": "string",
"synonyms": ["string"],
"difficultyLevel": "string",
"score": number // AI生成評分
}
]
},
"message": "string"
}
```
#### 實現位置
- **前端**: `frontend/app/generate/page.tsx:114-151`
- **後端**: `backend/DramaLing.Api/Controllers/AIController.cs:42-100`
### 1.3 互動式詞彙查詢
#### 核心特性
- **點擊查詢**: 用戶可點擊句子中任意單字查看詳細資訊
- **即時分析**: 動態調用AI API獲取單字分析
- **高價值標示**: 自動標示高價值單字和片語
- **使用量計費**: 區分高價值(免費)和低價值(計費)詞彙
#### API端點
- **端點**: `POST /api/ai/query-word`
- **輸入參數** (QueryWordRequest):
```json
{
"word": "string", // 要查詢的單字
"sentence": "string", // 上下文句子
"analysisId": "guid?" // 分析ID (可選)
}
```
#### 使用量統計 (WordQueryUsageStats)
- **表名**: `WordQueryUsageStats`
- **主要欄位**:
```csharp
public class WordQueryUsageStats
{
public Guid Id { get; set; }
public Guid UserId { get; set; }
public DateOnly Date { get; set; } // 日期
public int SentenceAnalysisCount { get; set; } // 句子分析次數
public int HighValueWordClicks { get; set; } // 高價值詞彙點擊(免費)
public int LowValueWordClicks { get; set; } // 低價值詞彙點擊(收費)
public int TotalApiCalls { get; set; } // 總API調用次數
public int UniqueWordsQueried { get; set; } // 查詢的獨特詞彙數
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
```
#### 實現位置
- **組件**: `frontend/components/ClickableTextV2.tsx`
- **前端邏輯**: `frontend/app/generate/page.tsx:402-421`
- **使用量服務**: `backend/DramaLing.Api/Services/UsageTrackingService.cs`
## 2. 詞彙儲存系統規格
### 2.1 資料庫架構
#### 2.1.1 用戶管理 (User)
- **表名**: `user_profiles`
- **主要欄位**:
```csharp
public class User
{
public Guid Id { get; set; }
public string Username { get; set; } // 用戶名 (唯一)
public string Email { get; set; } // 信箱 (唯一)
public string PasswordHash { get; set; } // 密碼雜湊
public string? DisplayName { get; set; } // 顯示名稱
public string? AvatarUrl { get; set; } // 頭像URL
public string SubscriptionType { get; set; } = "free"; // 訂閱類型
public Dictionary<string, object> Preferences { get; set; } // JSON偏好設定
// 個人化學習相關
public string EnglishLevel { get; set; } = "A2"; // 英語程度(A1-C2)
public DateTime LevelUpdatedAt { get; set; } // 程度更新時間
public bool IsLevelVerified { get; set; } = false; // 是否通過測試驗證
public string? LevelNotes { get; set; } // 程度設定備註
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
```
#### 2.1.2 詞卡實體 (Flashcard)
- **表名**: `flashcards`
- **主要欄位**:
```csharp
public class Flashcard
{
public Guid Id { get; set; }
public Guid UserId { get; set; } // 所屬用戶
public Guid CardSetId { get; set; } // 所屬卡組
// 詞卡內容
[Required, MaxLength(255)]
public string Word { get; set; } // 單字
[Required]
public string Translation { get; set; } // 翻譯
[Required]
public string Definition { get; set; } // 定義
[MaxLength(50)]
public string? PartOfSpeech { get; set; } // 詞性
[MaxLength(255)]
public string? Pronunciation { get; set; } // 發音
public string? Example { get; set; } // 例句
public string? ExampleTranslation { get; set; } // 例句翻譯
// SM-2 間隔重複算法參數
public float EasinessFactor { get; set; } = 2.5f; // 難易度係數
public int Repetitions { get; set; } = 0; // 重複次數
public int IntervalDays { get; set; } = 1; // 間隔天數
public DateTime NextReviewDate { get; set; } // 下次複習日期
// 學習統計
[Range(0, 100)]
public int MasteryLevel { get; set; } = 0; // 掌握程度(0-100)
public int TimesReviewed { get; set; } = 0; // 複習次數
public int TimesCorrect { get; set; } = 0; // 正確次數
public DateTime? LastReviewedAt { get; set; } // 最後複習時間
// 狀態管理
public bool IsFavorite { get; set; } = false; // 是否收藏
public bool IsArchived { get; set; } = false; // 是否封存
[MaxLength(10)]
public string? DifficultyLevel { get; set; } // 難度等級(A1-C2)
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
```
#### 2.1.3 卡組實體 (CardSet)
- **表名**: `card_sets`
- **主要欄位**:
```csharp
public class CardSet
{
public Guid Id { get; set; }
public Guid UserId { get; set; } // 所屬用戶
[Required, MaxLength(255)]
public string Name { get; set; } // 卡組名稱
public string? Description { get; set; } // 描述
[MaxLength(50)]
public string Color { get; set; } = "bg-blue-500"; // 顏色標籤
public int CardCount { get; set; } = 0; // 詞卡數量
public bool IsDefault { get; set; } = false; // 是否為預設卡組
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
```
#### 2.1.4 標籤系統 (Tag & FlashcardTag)
- **表名**: `tags`, `flashcard_tags`
- **主要欄位**:
```csharp
public class Tag
{
public Guid Id { get; set; }
public Guid UserId { get; set; }
[Required, MaxLength(100)]
public string Name { get; set; } // 標籤名稱
[Required, MaxLength(50)]
public string Color { get; set; } // 標籤顏色
public int UsageCount { get; set; } = 0; // 使用次數
public DateTime CreatedAt { get; set; }
}
public class FlashcardTag // 多對多關聯表
{
public Guid FlashcardId { get; set; }
public Guid TagId { get; set; }
}
```
#### 2.1.5 學習追蹤 (StudySession & StudyRecord)
- **表名**: `study_sessions`, `study_records`
- **主要欄位**:
```csharp
public class StudySession
{
public Guid Id { get; set; }
public Guid UserId { get; set; }
[Required, MaxLength(50)]
public string SessionType { get; set; } // 學習模式
public DateTime StartedAt { get; set; } // 開始時間
public DateTime? EndedAt { get; set; } // 結束時間
public int TotalCards { get; set; } = 0; // 總詞卡數
public int CorrectCount { get; set; } = 0; // 正確數量
public int DurationSeconds { get; set; } = 0; // 持續時間(秒)
public int AverageResponseTimeMs { get; set; } = 0; // 平均回應時間(毫秒)
}
public class StudyRecord
{
public Guid Id { get; set; }
public Guid UserId { get; set; }
public Guid FlashcardId { get; set; }
public Guid SessionId { get; set; }
[Required, MaxLength(50)]
public string StudyMode { get; set; } // 學習模式
public int QualityRating { get; set; } // 品質評分(0-5)
public int? ResponseTimeMs { get; set; } // 回應時間(毫秒)
public string? UserAnswer { get; set; } // 用戶答案
public bool IsCorrect { get; set; } // 是否正確
public DateTime StudiedAt { get; set; } // 學習時間
// SM-2算法歷史記錄
public float PreviousEasinessFactor { get; set; }
public int PreviousRepetitions { get; set; }
public int PreviousIntervalDays { get; set; }
public float NewEasinessFactor { get; set; }
public int NewRepetitions { get; set; }
public int NewIntervalDays { get; set; }
public DateTime NextReviewDate { get; set; }
}
```
#### 2.1.6 錯誤回報 (ErrorReport)
- **表名**: `error_reports`
- **主要欄位**:
```csharp
public class ErrorReport
{
public Guid Id { get; set; }
public Guid UserId { get; set; }
public Guid FlashcardId { get; set; }
[Required, MaxLength(100)]
public string ReportType { get; set; } // 錯誤類型
public string? Description { get; set; } // 描述
[MaxLength(50)]
public string? StudyMode { get; set; } // 學習模式
[Required, MaxLength(50)]
public string Status { get; set; } = "pending"; // 狀態
public string? AdminNotes { get; set; } // 管理員備註
public Guid? ResolvedBy { get; set; } // 解決者ID
public DateTime? ResolvedAt { get; set; } // 解決時間
public DateTime CreatedAt { get; set; }
}
```
#### 2.1.7 每日統計 (DailyStats)
- **表名**: `daily_stats`
- **主要欄位**:
```csharp
public class DailyStats
{
public Guid Id { get; set; }
public Guid UserId { get; set; }
public DateOnly Date { get; set; } // 日期
public int WordsStudied { get; set; } = 0; // 學習單字數
public int WordsCorrect { get; set; } = 0; // 正確單字數
public int StudyTimeSeconds { get; set; } = 0; // 學習時間(秒)
public int SessionCount { get; set; } = 0; // 學習場次
public int CardsGenerated { get; set; } = 0; // 生成詞卡數
public int AiApiCalls { get; set; } = 0; // AI API調用次數
public DateTime CreatedAt { get; set; }
}
```
### 2.2 儲存服務API
#### 2.2.1 詞卡管理API
- **取得詞卡列表**: `GET /api/flashcards`
- **查詢參數**:
- `setId` (Guid?): 指定卡組ID
- `search` (string?): 搜尋關鍵字(詞彙/翻譯)
- `favoritesOnly` (bool): 僅顯示收藏
- `limit` (int): 限制數量(預設50最多100)
- `offset` (int): 偏移量(分頁用)
- **回應格式**:
```json
{
"success": true,
"data": {
"flashcards": [...],
"total": number,
"hasMore": boolean
}
}
```
- **實現位置**: `FlashcardsController.cs:58-135`
- **建立詞卡**: `POST /api/flashcards`
- **請求體** (CreateFlashcardRequest):
```json
{
"cardSetId": "guid?", // 可選,未指定則使用預設卡組
"word": "string", // 必填
"translation": "string", // 必填
"definition": "string", // 必填
"partOfSpeech": "string?",
"pronunciation": "string?",
"example": "string?",
"exampleTranslation": "string?"
}
```
- **自動功能**: 如無指定卡組,自動分配到預設卡組或創建新的預設卡組
- **實現位置**: `FlashcardsController.cs:137-200`
- **更新詞卡**: `PUT /api/flashcards/{id}`
- **請求體** (UpdateFlashcardRequest): 支援部分欄位更新
- **實現位置**: `FlashcardsController.cs:233-287`
- **刪除詞卡**: `DELETE /api/flashcards/{id}`
- **實現位置**: `FlashcardsController.cs:289-324`
- **切換收藏狀態**: `POST /api/flashcards/{id}/favorite`
- **功能**: 切換詞卡的收藏狀態
#### 2.2.2 卡組管理API
- **取得卡組列表**: `GET /api/cardsets`
- **回應**: 包含卡組基本資訊和詞卡數量統計
- **建立卡組**: `POST /api/cardsets`
- **請求體** (CreateCardSetRequest):
```json
{
"name": "string", // 必填
"description": "string?", // 可選
"isPublic": boolean // 可選預設false
}
```
- **更新卡組**: `PUT /api/cardsets/{id}`
- **請求體** (UpdateCardSetRequest): 支援部分欄位更新
- **刪除卡組**: `DELETE /api/cardsets/{id}`
- **限制**: 無法刪除預設卡組
- **確保預設卡組**: `POST /api/cardsets/ensure-default`
- **功能**: 確保用戶有預設卡組,如無則自動創建
#### 2.2.3 前端服務層
- **檔案位置**: `frontend/lib/services/flashcards.ts`
- **核心介面**:
```typescript
export interface Flashcard {
id: string;
word: string;
translation: string;
definition: string;
partOfSpeech: string;
pronunciation: string;
example: string;
exampleTranslation?: string;
masteryLevel: number; // 0-100掌握程度
timesReviewed: number; // 複習次數
isFavorite: boolean; // 是否收藏
nextReviewDate: string; // 下次複習日期
createdAt: string;
cardSet: {
name: string;
color: string;
};
}
export interface CardSet {
id: string;
name: string;
description: string;
color: string;
cardCount: number; // 詞卡數量
createdAt: string;
updatedAt: string;
isDefault: boolean; // 是否為預設卡組
progress: number; // 學習進度
lastStudied: string; // 最後學習時間
tags: string[]; // 標籤
}
class FlashcardsService {
// 詞卡CRUD操作
async getFlashcards(cardSetId?: string): Promise<ApiResponse<{flashcards: Flashcard[]; total: number; hasMore: boolean}>>
async createFlashcard(data: CreateFlashcardRequest): Promise<ApiResponse<Flashcard>>
async updateFlashcard(id: string, data: Partial<CreateFlashcardRequest>): Promise<ApiResponse<Flashcard>>
async deleteFlashcard(id: string): Promise<ApiResponse<void>>
async toggleFavorite(id: string): Promise<ApiResponse<Flashcard>>
// 卡組CRUD操作
async getCardSets(): Promise<ApiResponse<{sets: CardSet[]}>>
async createCardSet(data: CreateCardSetRequest): Promise<ApiResponse<CardSet>>
async deleteCardSet(id: string): Promise<ApiResponse<void>>
async ensureDefaultCardSet(): Promise<ApiResponse<CardSet>>
}
```
#### 2.2.4 資料庫關聯與索引
- **外鍵關聯**:
- `flashcards.user_id``user_profiles.id` (CASCADE)
- `flashcards.card_set_id``card_sets.id` (CASCADE)
- `card_sets.user_id``user_profiles.id` (CASCADE)
- `flashcard_tags.flashcard_id``flashcards.id` (CASCADE)
- `flashcard_tags.tag_id``tags.id` (CASCADE)
- **重要索引**:
- `user_profiles`: email(UNIQUE), username(UNIQUE)
- `flashcards`: user_id, card_set_id
- `card_sets`: user_id
- `tags`: user_id
- `daily_stats`: (user_id, date)(UNIQUE)
- `SentenceAnalysisCache`: input_text_hash(UNIQUE), expires_at
## 3. 系統整合流程
### 3.1 完整學習流程
1. **用戶認證** → JWT Token驗證與用戶程度讀取
2. **句子輸入** → 用戶在生成頁面輸入英文句子(最多300字元)
3. **快取檢查** → 檢查是否已有分析結果快取
4. **AI分析** → 調用Gemini API進行句子深度分析
5. **結果快取** → 將分析結果儲存到`SentenceAnalysisCache`
6. **互動探索** → 用戶點擊單字查看詳細分析(使用量追蹤)
7. **詞卡生成** → 基於分析結果生成個人化詞卡
8. **儲存管理** → 詞卡儲存到預設或指定卡組
9. **學習追蹤** → 使用SM-2算法追蹤學習進度
### 3.2 資料流向圖
```
用戶請求 → JWT認證 → 程度檢查 → 輸入驗證 → 快取檢查 → AI API調用
結果快取 → 使用量記錄 → 前端顯示 → 用戶互動 → 詞卡生成 → 資料庫儲存
學習記錄 → SM-2更新 → 統計更新 → 進度追蹤
```
### 3.3 關鍵業務邏輯
#### 3.3.1 預設卡組管理
- 用戶首次創建詞卡時,系統自動創建名為「未分類」的預設卡組
- 預設卡組無法刪除,確保用戶始終有存放詞卡的地方
- 實現位置: `FlashcardsController.cs:33-56`
#### 3.3.2 SM-2間隔重複算法
- 基於用戶答題品質(0-5分)調整複習間隔
- 記錄詳細的學習歷史用於算法優化
- 實現位置: `backend/DramaLing.Api/Services/SM2Algorithm.cs`
#### 3.3.3 使用量限制機制
- 免費用戶3小時內最多5次句子分析
- 高價值詞彙點擊免費,低價值詞彙計費
- 實現位置: `UsageTrackingService.cs`
## 4. 技術架構
### 4.1 前端技術棧
- **框架**: Next.js 14 (App Router)
- **語言**: TypeScript
- **樣式**: Tailwind CSS
- **狀態管理**: React Hooks + Context API
- **HTTP客戶端**: Fetch API
- **認證**: JWT + localStorage
- **路由保護**: ProtectedRoute組件
### 4.2 後端技術棧
- **框架**: .NET 8 Web API
- **ORM**: Entity Framework Core 8.0
- **資料庫**: SQLite (開發)
- **認證**: JWT Bearer Token
- **AI服務**: Google Gemini API
- **快取**: 內建EF快取 + 自定義快取服務
- **日誌**: Microsoft.Extensions.Logging
### 4.3 資料庫設計特點
- **Snake_case命名**: 所有資料表和欄位使用snake_case
- **GUID主鍵**: 所有實體使用Guid作為主鍵
- **軟刪除**: 支援IsArchived標記而非硬刪除
- **審計欄位**: CreatedAt、UpdatedAt自動管理
- **JSON欄位**: 使用JSON存儲複雜結構(如Preferences)
### 4.4 API設計原則
- **RESTful風格**: 遵循REST慣例
- **統一回應格式**:
```json
{
"success": boolean,
"data": object,
"error": string,
"message": string,
"timestamp": datetime
}
```
- **認證保護**: 所有業務API需JWT認證
- **錯誤處理**: 統一錯誤處理中間件
- **請求驗證**: DataAnnotations + 自定義驗證
- **分頁支援**: 統一的limit/offset分頁機制
## 5. 效能與優化
### 5.1 快取策略
- **句子分析快取**:
- 使用SHA-256雜湊避重
- 設定過期時間(ExpiresAt)
- 記錄存取次數和最後存取時間
- **詞彙查詢快取**:
- 減少重複AI API調用
- 提升查詞響應速度
- **清理機制**:
- 定期清理過期快取
- 實現位置: `CacheCleanupService.cs`
### 5.2 效能監控
- **日常統計**: DailyStats記錄用戶活動指標
- **使用量追蹤**: WordQueryUsageStats追蹤API使用
- **錯誤報告**: ErrorReport系統收集問題回饋
### 5.3 限制與配額
- **免費用戶限制**:
- 句子分析: 3小時內最多5次
- 手動輸入: 最多300字元
- 詞卡生成: 一次最多20張
- **付費用戶**: 無限制使用
- **API速率限制**: 防止濫用攻擊
## 6. 安全性考量
### 6.1 認證與授權
- **JWT Token**: 包含用戶ID和過期時間
- **用戶隔離**: 所有資料按UserId嚴格隔離
- **端點保護**: [Authorize]屬性保護敏感API
- **測試端點**: 部分功能提供[AllowAnonymous]測試
### 6.2 資料安全
- **密碼安全**: BCrypt雜湊儲存
- **輸入驗證**: 前後端雙重驗證
- **SQL注入防護**: EF Core參數化查詢
- **XSS防護**: 自動HTML編碼
### 6.3 API安全
- **CORS設定**: 限制來源域名
- **請求大小限制**: 防止大檔案攻擊
- **錯誤資訊隱藏**: 生產環境隱藏敏感錯誤
## 7. 監控與維護
### 7.1 系統監控
- **健康檢查**: API健康狀態監控
- **效能指標**: 回應時間、吞吐量追蹤
- **錯誤追蹤**: 異常日誌和錯誤報告
- **資源監控**: 資料庫和儲存空間監控
### 7.2 維護策略
- **資料備份**: 定期資料庫備份機制
- **日誌清理**: 定期清理舊日誌和快取
- **版本控制**: Git版本管理和部署追蹤
- **文件更新**: 與程式碼同步更新文件
## 8. 未來擴展規劃
### 8.1 短期功能擴展
- **語音功能**: 整合Azure Speech Service
- **個人化推薦**: 基於學習歷史的智能推薦
- **社群功能**: 卡組分享和協作學習
- **多語言支援**: 擴展到其他語言學習
### 8.2 技術架構升級
- **資料庫升級**: 從SQLite遷移到PostgreSQL
- **快取優化**: 引入Redis分散式快取
- **微服務化**: 拆分AI服務和業務服務
- **容器化部署**: Docker + Kubernetes部署
### 8.3 擴展性考量
- **水平擴展**: 支援多實例負載平衡
- **資料分割**: 大規模用戶資料分割策略
- **CDN整合**: 靜態資源和多媒體加速
- **國際化**: 多地區部署和資料同步
---
**文件版本**: 2.0
**最後更新**: 2025-09-20
**維護者**: DramaLing開發團隊
**更新說明**: 基於實際程式碼架構重新整理,修正與系統實現的差距