refactor: 移除卡組功能,簡化詞卡管理系統
- 完全移除卡組分類功能,簡化詞卡管理邏輯 - 詞卡管理頁面只保留"所有詞卡"和"收藏詞卡"兩個tab - 移除卡組相關界面元素和統計信息 - 詞卡列表顯示創建時間取代卡組信息 - 詞卡詳細頁面移除開始學習按鈕 - CEFR標籤移至卡片右上角,移除"CEFR"文字前綴 - 底部操作按鈕採用平均延展布局(flex-1) - 強化搜尋和收藏功能作為主要組織方式 - 創建詞卡管理系統簡化規格文檔 - 專注詞彙學習本質,降低管理複雜度 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
080cbe14a6
commit
be236f7216
|
|
@ -263,71 +263,36 @@ function FlashcardDetailContent({ cardId }: { cardId: string }) {
|
|||
返回詞卡列表
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className={`px-3 py-1 rounded-full text-sm font-medium border ${getCEFRColor((flashcard as any).difficultyLevel || 'A1')}`}>
|
||||
CEFR {(flashcard as any).difficultyLevel || 'A1'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 主要詞卡內容 - 學習功能風格 */}
|
||||
<div className="bg-white rounded-xl shadow-lg overflow-hidden mb-6">
|
||||
<div className="bg-white rounded-xl shadow-lg overflow-hidden mb-6 relative">
|
||||
{/* CEFR標籤 - 右上角 */}
|
||||
<div className="absolute top-4 right-4 z-10">
|
||||
<span className={`px-3 py-1 rounded-full text-sm font-medium border ${getCEFRColor((flashcard as any).difficultyLevel || 'A1')}`}>
|
||||
{(flashcard as any).difficultyLevel || 'A1'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 標題區 */}
|
||||
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-6 border-b border-blue-200">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-3">{flashcard.word}</h1>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm bg-gray-100 text-gray-700 px-3 py-1 rounded-full">
|
||||
{flashcard.partOfSpeech}
|
||||
</span>
|
||||
<span className="text-lg text-gray-600">{flashcard.pronunciation}</span>
|
||||
<button className="w-10 h-10 bg-blue-600 rounded-full flex items-center justify-center text-white hover:bg-blue-700 transition-colors">
|
||||
<svg className="w-5 h-5" 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>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleToggleFavorite}
|
||||
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
|
||||
flashcard.isFavorite
|
||||
? 'bg-yellow-100 text-yellow-700 border border-yellow-300 hover:bg-yellow-200'
|
||||
: 'bg-gray-100 text-gray-600 border border-gray-300 hover:bg-yellow-50 hover:text-yellow-600'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="w-4 h-4" fill={flashcard.isFavorite ? "currentColor" : "none"} stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
|
||||
</svg>
|
||||
{flashcard.isFavorite ? '已收藏' : '收藏'}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setIsEditing(!isEditing)}
|
||||
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
|
||||
isEditing
|
||||
? 'bg-gray-100 text-gray-700 border border-gray-300'
|
||||
: 'bg-blue-100 text-blue-700 border border-blue-300 hover:bg-blue-200'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-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="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
{isEditing ? '取消編輯' : '編輯詞卡'}
|
||||
</div>
|
||||
<div className="pr-16">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-3">{flashcard.word}</h1>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm bg-gray-100 text-gray-700 px-3 py-1 rounded-full">
|
||||
{flashcard.partOfSpeech}
|
||||
</span>
|
||||
<span className="text-lg text-gray-600">{flashcard.pronunciation}</span>
|
||||
<button className="w-10 h-10 bg-blue-600 rounded-full flex items-center justify-center text-white hover:bg-blue-700 transition-colors">
|
||||
<svg className="w-5 h-5" 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>
|
||||
</div>
|
||||
|
||||
{/* 學習統計 */}
|
||||
<div className="grid grid-cols-3 gap-4 text-center">
|
||||
<div className="grid grid-cols-3 gap-4 text-center mt-4">
|
||||
<div className="bg-white bg-opacity-60 rounded-lg p-3">
|
||||
<div className="text-2xl font-bold text-gray-900">{flashcard.masteryLevel}%</div>
|
||||
<div className="text-sm text-gray-600">掌握程度</div>
|
||||
|
|
@ -455,8 +420,8 @@ function FlashcardDetailContent({ cardId }: { cardId: string }) {
|
|||
<h3 className="font-semibold text-gray-900 mb-3 text-left">詞卡資訊</h3>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-600">所屬卡組:</span>
|
||||
<span className="ml-2 font-medium">{flashcard.cardSet.name}</span>
|
||||
<span className="text-gray-600">詞性:</span>
|
||||
<span className="ml-2 font-medium">{flashcard.partOfSpeech}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600">創建時間:</span>
|
||||
|
|
@ -501,26 +466,50 @@ function FlashcardDetailContent({ cardId }: { cardId: string }) {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* 底部操作區 */}
|
||||
<div className="flex gap-4">
|
||||
{/* 底部操作區 - 平均延展按鈕 */}
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={() => router.push('/learn')}
|
||||
className="flex-1 bg-primary text-white py-3 rounded-lg font-medium hover:bg-primary-hover transition-colors flex items-center justify-center gap-2"
|
||||
onClick={handleToggleFavorite}
|
||||
className={`flex-1 py-3 rounded-lg font-medium transition-colors ${
|
||||
flashcard.isFavorite
|
||||
? 'bg-yellow-100 text-yellow-700 border border-yellow-300 hover:bg-yellow-200'
|
||||
: 'bg-gray-100 text-gray-600 border border-gray-300 hover:bg-yellow-50 hover:text-yellow-600'
|
||||
}`}
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
||||
</svg>
|
||||
開始學習
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<svg className="w-4 h-4" fill={flashcard.isFavorite ? "currentColor" : "none"} stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
|
||||
</svg>
|
||||
{flashcard.isFavorite ? '已收藏' : '收藏'}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setIsEditing(!isEditing)}
|
||||
className={`flex-1 py-3 rounded-lg font-medium transition-colors ${
|
||||
isEditing
|
||||
? 'bg-gray-100 text-gray-700 border border-gray-300'
|
||||
: 'bg-blue-100 text-blue-700 border border-blue-300 hover:bg-blue-200'
|
||||
}`}
|
||||
>
|
||||
<div className="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="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
{isEditing ? '取消編輯' : '編輯詞卡'}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="px-6 py-3 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700 transition-colors flex items-center gap-2"
|
||||
className="flex-1 py-3 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
刪除詞卡
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
刪除詞卡
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { useRouter } from 'next/navigation'
|
|||
|
||||
function FlashcardsContent() {
|
||||
const router = useRouter()
|
||||
const [activeTab, setActiveTab] = useState('my-cards')
|
||||
const [activeTab, setActiveTab] = useState('all-cards')
|
||||
const [selectedSet, setSelectedSet] = useState<string | null>(null)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [showAdvancedSearch, setShowAdvancedSearch] = useState(false)
|
||||
|
|
@ -200,11 +200,6 @@ function FlashcardsContent() {
|
|||
}
|
||||
}
|
||||
|
||||
// Filter data - 合併真實資料和假資料
|
||||
const filteredSets = cardSets.filter(set =>
|
||||
set.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
set.description.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
)
|
||||
|
||||
const allCards = [...flashcards, ...mockFlashcards] // 合併真實和假資料
|
||||
|
||||
|
|
@ -328,28 +323,12 @@ function FlashcardsContent() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
{/* 簡化的Tabs - 移除卡組功能 */}
|
||||
<div className="flex space-x-8 mb-6 border-b border-gray-200">
|
||||
<button
|
||||
onClick={() => {
|
||||
setActiveTab('my-cards')
|
||||
setSelectedSet(null) // 清除卡組選擇
|
||||
}}
|
||||
onClick={() => setActiveTab('all-cards')}
|
||||
className={`pb-4 px-1 border-b-2 font-medium text-sm ${
|
||||
activeTab === 'my-cards'
|
||||
? 'border-primary text-primary'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
我的卡組 ({filteredSets.length})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setActiveTab('all-cards')
|
||||
setSelectedSet(null) // 清除卡組選擇
|
||||
}}
|
||||
className={`pb-4 px-1 border-b-2 font-medium text-sm ${
|
||||
activeTab === 'all-cards' && !selectedSet
|
||||
activeTab === 'all-cards'
|
||||
? 'border-primary text-primary'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
|
|
@ -357,10 +336,7 @@ function FlashcardsContent() {
|
|||
所有詞卡 ({filteredCards.length})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setActiveTab('favorites')
|
||||
setSelectedSet(null) // 清除卡組選擇
|
||||
}}
|
||||
onClick={() => setActiveTab('favorites')}
|
||||
className={`pb-4 px-1 border-b-2 font-medium text-sm flex items-center gap-1 ${
|
||||
activeTab === 'favorites'
|
||||
? 'border-primary text-primary'
|
||||
|
|
@ -370,22 +346,6 @@ function FlashcardsContent() {
|
|||
<span className="text-yellow-500">⭐</span>
|
||||
收藏詞卡 ({allCards.filter(card => card.isFavorite).length})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
const defaultSet = cardSets.find(set => set.isDefault)
|
||||
if (defaultSet) {
|
||||
setSelectedSet(defaultSet.id)
|
||||
setActiveTab('all-cards')
|
||||
}
|
||||
}}
|
||||
className={`pb-4 px-1 border-b-2 font-medium text-sm ${
|
||||
activeTab === 'all-cards' && selectedSet && cardSets.find(set => set.id === selectedSet)?.isDefault
|
||||
? 'border-primary text-primary'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
📂 未分類詞卡
|
||||
</button>
|
||||
</div>
|
||||
{/* 進階搜尋區域 */}
|
||||
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
|
|
@ -571,67 +531,12 @@ function FlashcardsContent() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Card Sets Tab */}
|
||||
{activeTab === 'my-cards' && (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold">共 {filteredSets.length} 個卡組</h3>
|
||||
</div>
|
||||
|
||||
{filteredSets.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-500 mb-4">還沒有詞卡集合</p>
|
||||
<Link
|
||||
href="/generate"
|
||||
className="bg-primary text-white px-4 py-2 rounded-lg hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
創建第一個卡組
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{filteredSets.map(set => (
|
||||
<div
|
||||
key={set.id}
|
||||
className={`border rounded-lg hover:shadow-lg transition-shadow cursor-pointer ${
|
||||
set.isDefault ? 'ring-2 ring-gray-300' : ''
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedSet(set.id)
|
||||
setActiveTab('all-cards')
|
||||
}}
|
||||
>
|
||||
<div className={`${set.isDefault ? 'bg-slate-700' : set.color} text-white p-4 rounded-t-lg`}>
|
||||
<div className="flex items-center space-x-2">
|
||||
{set.isDefault && <span>📂</span>}
|
||||
<h4 className="font-semibold text-lg">
|
||||
{set.name}
|
||||
{set.isDefault && <span className="text-xs ml-2 opacity-75">(預設)</span>}
|
||||
</h4>
|
||||
</div>
|
||||
<p className="text-sm opacity-90">{set.description}</p>
|
||||
</div>
|
||||
<div className="p-4 bg-white rounded-b-lg">
|
||||
<div className="flex justify-between items-center text-sm text-gray-600">
|
||||
<span>{set.cardCount} 張詞卡</span>
|
||||
<span>進度: {set.progress}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Favorites Tab */}
|
||||
{activeTab === 'favorites' && (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||
<span className="text-yellow-500">⭐</span>
|
||||
收藏詞卡 ({allCards.filter(card => card.isFavorite).length} 個)
|
||||
</h3>
|
||||
<h3 className="text-lg font-semibold">共 {allCards.filter(card => card.isFavorite).length} 個詞卡</h3>
|
||||
</div>
|
||||
|
||||
{allCards.filter(card => card.isFavorite).length === 0 ? (
|
||||
|
|
@ -643,7 +548,7 @@ function FlashcardsContent() {
|
|||
) : (
|
||||
<div className="space-y-2">
|
||||
{allCards.filter(card => card.isFavorite).map(card => (
|
||||
<div key={card.id} className="bg-white border border-yellow-200 rounded-lg hover:shadow-md transition-all duration-200 relative ring-1 ring-yellow-100">
|
||||
<div key={card.id} className="bg-white border border-gray-200 rounded-lg hover:shadow-md transition-all duration-200 relative">
|
||||
<div className="p-4">
|
||||
{/* 收藏詞卡內容 - 與普通詞卡相同的佈局 */}
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -707,54 +612,76 @@ function FlashcardsContent() {
|
|||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 mt-2 text-sm text-gray-500">
|
||||
<span>卡組: {card.cardSet.name}</span>
|
||||
<span>創建: {new Date(card.createdAt).toLocaleDateString()}</span>
|
||||
<span>掌握度: {card.masteryLevel}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex gap-1" onClick={(e) => e.stopPropagation()}>
|
||||
<button
|
||||
onClick={() => handleToggleFavorite(card)}
|
||||
className="p-2 text-yellow-500 hover:text-yellow-600 hover:bg-yellow-50 rounded-lg transition-colors"
|
||||
title="取消收藏"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="currentColor" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{/* 右側:重新設計的操作按鈕區 */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* 收藏按鈕 */}
|
||||
<button
|
||||
onClick={() => handleToggleFavorite(card)}
|
||||
className={`px-3 py-2 rounded-lg font-medium transition-colors ${
|
||||
card.isFavorite
|
||||
? 'bg-yellow-100 text-yellow-700 border border-yellow-300 hover:bg-yellow-200'
|
||||
: 'bg-gray-100 text-gray-600 border border-gray-300 hover:bg-yellow-50 hover:text-yellow-600 hover:border-yellow-300'
|
||||
}`}
|
||||
title={card.isFavorite ? "取消收藏" : "加入收藏"}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<svg className="w-4 h-4" fill={card.isFavorite ? "currentColor" : "none"} stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEdit(card)}
|
||||
className="p-2 text-blue-600 hover:text-blue-700 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
title="編輯詞卡"
|
||||
>
|
||||
<span className="text-sm">
|
||||
{card.isFavorite ? '已收藏' : '收藏'}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* 編輯按鈕 */}
|
||||
<button
|
||||
onClick={() => handleEdit(card)}
|
||||
className="px-3 py-2 bg-blue-100 text-blue-700 border border-blue-300 rounded-lg font-medium hover:bg-blue-200 transition-colors"
|
||||
title="編輯詞卡"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(card)}
|
||||
className="p-2 text-red-600 hover:text-red-700 hover:bg-red-50 rounded-lg transition-colors"
|
||||
title="刪除詞卡"
|
||||
>
|
||||
<span className="text-sm">編輯</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* 刪除按鈕 */}
|
||||
<button
|
||||
onClick={() => handleDelete(card)}
|
||||
className="px-3 py-2 bg-red-100 text-red-700 border border-red-300 rounded-lg font-medium hover:bg-red-200 transition-colors"
|
||||
title="刪除詞卡"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-sm">刪除</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* 進入詳細頁面箭頭 - 導航到詳細頁面 */}
|
||||
{/* 查看詳情按鈕 - 導航到詳細頁面 */}
|
||||
<button
|
||||
onClick={() => {
|
||||
router.push(`/flashcards/${card.id}`)
|
||||
}}
|
||||
className="text-gray-400 hover:text-gray-600 p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
||||
className="px-4 py-2 bg-gray-100 text-gray-700 border border-gray-300 rounded-lg font-medium hover:bg-gray-200 hover:text-gray-900 transition-colors"
|
||||
title="查看詳細資訊"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-sm">詳細</span>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -771,35 +698,8 @@ function FlashcardsContent() {
|
|||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold">共 {filteredCards.length} 個詞卡</h3>
|
||||
{selectedSet && (
|
||||
<button
|
||||
onClick={() => setSelectedSet(null)}
|
||||
className="text-sm text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
顯示所有詞卡
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 未分類提醒 */}
|
||||
{selectedSet && cardSets.find(set => set.id === selectedSet)?.isDefault && filteredCards.length > 15 && (
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-blue-600">💡</span>
|
||||
<div className="flex-1">
|
||||
<p className="text-blue-800 text-sm">
|
||||
您有 {filteredCards.length} 個未分類詞卡,建議整理到不同主題的卡組中,有助於更好地組織學習內容。
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setActiveTab('my-cards')}
|
||||
className="text-blue-600 text-sm font-medium hover:text-blue-800"
|
||||
>
|
||||
查看卡組
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{filteredCards.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
|
|
@ -883,7 +783,7 @@ function FlashcardsContent() {
|
|||
|
||||
{/* 簡要統計 */}
|
||||
<div className="flex items-center gap-4 mt-2 text-sm text-gray-500">
|
||||
<span>卡組: {card.cardSet.name}</span>
|
||||
<span>創建: {new Date(card.createdAt).toLocaleDateString()}</span>
|
||||
<span>掌握度: {card.masteryLevel}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,616 @@
|
|||
# 詞卡管理系統簡化規格
|
||||
|
||||
## 設計理念
|
||||
|
||||
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詞卡管理系統簡化版
|
||||
Loading…
Reference in New Issue