From 33b291b505467f4fa850f226a15e11b7f0b42980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=84=AD=E6=B2=9B=E8=BB=92?= Date: Sat, 20 Sep 2025 18:19:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E8=A9=9E=E5=8D=A1?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E9=A0=81=E9=9D=A2=E7=9A=84=E6=90=9C=E5=B0=8B?= =?UTF-8?q?=E5=92=8C=E4=BA=A4=E4=BA=92=E9=AB=94=E9=A9=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 實現進階搜尋功能,支援CEFR等級、詞性、掌握度、收藏狀態篩選 - 新增搜尋結果高亮顯示,關鍵字會被黃色標記 - 重新設計右側操作按鈕,增大尺寸提升點擊體驗 - 修正tab高亮邏輯,避免多個tab同時亮起的問題 - 優化卡片交互邏輯,移除整卡點擊,只保留右側導航按鈕 - 修正例句圖片映射邏輯,確保所有詞卡都有對應圖片 - 添加完整的假資料展示六個CEFR等級效果 - 實現快速篩選按鈕,一鍵篩選常用條件 - 修正TypeScript類型錯誤,確保編譯正常 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- frontend/app/flashcards/page.tsx | 442 ++++++++++++++++++++++++++----- 1 file changed, 369 insertions(+), 73 deletions(-) diff --git a/frontend/app/flashcards/page.tsx b/frontend/app/flashcards/page.tsx index 73d2437..ea15842 100644 --- a/frontend/app/flashcards/page.tsx +++ b/frontend/app/flashcards/page.tsx @@ -11,6 +11,13 @@ function FlashcardsContent() { const [activeTab, setActiveTab] = useState('my-cards') const [selectedSet, setSelectedSet] = useState(null) const [searchTerm, setSearchTerm] = useState('') + const [showAdvancedSearch, setShowAdvancedSearch] = useState(false) + const [searchFilters, setSearchFilters] = useState({ + cefrLevel: '', + partOfSpeech: '', + masteryLevel: '', + onlyFavorites: false + }) // Real data from API const [cardSets, setCardSets] = useState([]) @@ -20,18 +27,34 @@ function FlashcardsContent() { // 臨時使用學習功能的例句圖片作為測試 const getExampleImage = (word: string): string => { + const availableImages = [ + '/images/examples/bring_up.png', + '/images/examples/instinct.png', + '/images/examples/warrant.png' + ] + const imageMap: {[key: string]: string} = { 'brought': '/images/examples/bring_up.png', 'instincts': '/images/examples/instinct.png', 'warrants': '/images/examples/warrant.png', - 'elaborate': '/images/examples/bring_up.png', // 作為預設 - 'hello': '/images/examples/instinct.png', - 'good': '/images/examples/warrant.png' + 'hello': '/images/examples/bring_up.png', + 'beautiful': '/images/examples/instinct.png', + 'understand': '/images/examples/warrant.png', + 'elaborate': '/images/examples/bring_up.png', + 'sophisticated': '/images/examples/instinct.png', + 'ubiquitous': '/images/examples/warrant.png' } - // 根據詞彙返回對應圖片,如果沒有則返回隨機圖片 - return imageMap[word?.toLowerCase()] || - imageMap[Object.keys(imageMap)[Math.floor(Math.random() * Object.keys(imageMap).length)]] + // 根據詞彙返回對應圖片,如果沒有則根據字母分配 + const mappedImage = imageMap[word?.toLowerCase()] + if (mappedImage) return mappedImage + + // 根據首字母分配圖片 + const firstChar = (word || 'a')[0].toLowerCase() + const charCode = firstChar.charCodeAt(0) - 97 // a=0, b=1, c=2... + const imageIndex = charCode % availableImages.length + + return availableImages[imageIndex] } // Form states @@ -40,12 +63,12 @@ function FlashcardsContent() { // 添加假資料用於展示CEFR效果 const mockFlashcards = [ - { id: 'mock1', word: 'hello', translation: '你好', partOfSpeech: 'interjection', pronunciation: '/həˈloʊ/', masteryLevel: 95, timesReviewed: 15, isFavorite: true, nextReviewDate: '2025-09-21', cardSet: { name: '基礎詞彙', color: 'bg-blue-500' }, difficultyLevel: 'A1' }, - { id: 'mock2', word: 'beautiful', translation: '美麗的', partOfSpeech: 'adjective', pronunciation: '/ˈbjuːtɪfəl/', masteryLevel: 78, timesReviewed: 8, isFavorite: false, nextReviewDate: '2025-09-22', cardSet: { name: '描述詞彙', color: 'bg-green-500' }, difficultyLevel: 'A2' }, - { id: 'mock3', word: 'understand', translation: '理解', partOfSpeech: 'verb', pronunciation: '/ˌʌndərˈstænd/', masteryLevel: 65, timesReviewed: 12, isFavorite: true, nextReviewDate: '2025-09-20', cardSet: { name: '常用動詞', color: 'bg-yellow-500' }, difficultyLevel: 'B1' }, - { id: 'mock4', word: 'elaborate', translation: '詳細說明', partOfSpeech: 'verb', pronunciation: '/ɪˈlæbərət/', masteryLevel: 45, timesReviewed: 5, isFavorite: false, nextReviewDate: '2025-09-19', cardSet: { name: '高級詞彙', color: 'bg-purple-500' }, difficultyLevel: 'B2' }, - { id: 'mock5', word: 'sophisticated', translation: '精密的', partOfSpeech: 'adjective', pronunciation: '/səˈfɪstɪkeɪtɪd/', masteryLevel: 30, timesReviewed: 3, isFavorite: true, nextReviewDate: '2025-09-18', cardSet: { name: '進階詞彙', color: 'bg-indigo-500' }, difficultyLevel: 'C1' }, - { id: 'mock6', word: 'ubiquitous', translation: '無處不在的', partOfSpeech: 'adjective', pronunciation: '/juːˈbɪkwɪtəs/', masteryLevel: 15, timesReviewed: 1, isFavorite: false, nextReviewDate: '2025-09-17', cardSet: { name: '學術詞彙', color: 'bg-red-500' }, difficultyLevel: 'C2' } + { id: 'mock1', word: 'hello', translation: '你好', partOfSpeech: 'interjection', pronunciation: '/həˈloʊ/', masteryLevel: 95, timesReviewed: 15, isFavorite: true, nextReviewDate: '2025-09-21', cardSet: { name: '基礎詞彙', color: 'bg-blue-500' }, difficultyLevel: 'A1', definition: 'A greeting word', example: 'Hello, how are you?', createdAt: '2025-09-17' }, + { id: 'mock2', word: 'beautiful', translation: '美麗的', partOfSpeech: 'adjective', pronunciation: '/ˈbjuːtɪfəl/', masteryLevel: 78, timesReviewed: 8, isFavorite: false, nextReviewDate: '2025-09-22', cardSet: { name: '描述詞彙', color: 'bg-green-500' }, difficultyLevel: 'A2', definition: 'Pleasing to look at', example: 'The beautiful sunset', createdAt: '2025-09-16' }, + { id: 'mock3', word: 'understand', translation: '理解', partOfSpeech: 'verb', pronunciation: '/ˌʌndərˈstænd/', masteryLevel: 65, timesReviewed: 12, isFavorite: true, nextReviewDate: '2025-09-20', cardSet: { name: '常用動詞', color: 'bg-yellow-500' }, difficultyLevel: 'B1', definition: 'To comprehend', example: 'I understand the concept', createdAt: '2025-09-15' }, + { id: 'mock4', word: 'elaborate', translation: '詳細說明', partOfSpeech: 'verb', pronunciation: '/ɪˈlæbərət/', masteryLevel: 45, timesReviewed: 5, isFavorite: false, nextReviewDate: '2025-09-19', cardSet: { name: '高級詞彙', color: 'bg-purple-500' }, difficultyLevel: 'B2', definition: 'To explain in detail', example: 'Please elaborate on your idea', createdAt: '2025-09-14' }, + { id: 'mock5', word: 'sophisticated', translation: '精密的', partOfSpeech: 'adjective', pronunciation: '/səˈfɪstɪkeɪtɪd/', masteryLevel: 30, timesReviewed: 3, isFavorite: true, nextReviewDate: '2025-09-18', cardSet: { name: '進階詞彙', color: 'bg-indigo-500' }, difficultyLevel: 'C1', definition: 'Highly developed', example: 'A sophisticated system', createdAt: '2025-09-13' }, + { id: 'mock6', word: 'ubiquitous', translation: '無處不在的', partOfSpeech: 'adjective', pronunciation: '/juːˈbɪkwɪtəs/', masteryLevel: 15, timesReviewed: 1, isFavorite: false, nextReviewDate: '2025-09-17', cardSet: { name: '學術詞彙', color: 'bg-red-500' }, difficultyLevel: 'C2', definition: 'Present everywhere', example: 'Smartphones are ubiquitous', createdAt: '2025-09-12' } ] // Load data from API @@ -182,14 +205,82 @@ function FlashcardsContent() { ) const allCards = [...flashcards, ...mockFlashcards] // 合併真實和假資料 + + // 進階搜尋邏輯 const filteredCards = allCards.filter(card => { + // 基本文字搜尋 if (searchTerm) { - return card.word?.toLowerCase().includes(searchTerm.toLowerCase()) || - card.translation?.toLowerCase().includes(searchTerm.toLowerCase()) + const searchLower = searchTerm.toLowerCase() + const matchesText = + card.word?.toLowerCase().includes(searchLower) || + card.translation?.toLowerCase().includes(searchLower) || + card.definition?.toLowerCase().includes(searchLower) + + if (!matchesText) return false } + + // CEFR等級篩選 + if (searchFilters.cefrLevel && (card as any).difficultyLevel !== searchFilters.cefrLevel) { + return false + } + + // 詞性篩選 + if (searchFilters.partOfSpeech && card.partOfSpeech !== searchFilters.partOfSpeech) { + return false + } + + // 掌握度篩選 + if (searchFilters.masteryLevel) { + const mastery = card.masteryLevel || 0 + if (searchFilters.masteryLevel === 'high' && mastery < 80) return false + if (searchFilters.masteryLevel === 'medium' && (mastery < 60 || mastery >= 80)) return false + if (searchFilters.masteryLevel === 'low' && mastery >= 60) return false + } + + // 收藏篩選 + if (searchFilters.onlyFavorites && !card.isFavorite) { + return false + } + return true }) + // 清除所有篩選 + const clearAllFilters = () => { + setSearchTerm('') + setSearchFilters({ + cefrLevel: '', + partOfSpeech: '', + masteryLevel: '', + onlyFavorites: false + }) + } + + // 檢查是否有活動篩選 + const hasActiveFilters = searchTerm || + searchFilters.cefrLevel || + searchFilters.partOfSpeech || + searchFilters.masteryLevel || + searchFilters.onlyFavorites + + // 搜尋結果高亮函數 + const highlightSearchTerm = (text: string, searchTerm: string) => { + if (!searchTerm || !text) return text + + const regex = new RegExp(`(${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi') + const parts = text.split(regex) + + return parts.map((part, index) => + regex.test(part) ? ( + + {part} + + ) : ( + part + ) + ) + } + // Add loading and error states if (loading) { return ( @@ -218,7 +309,6 @@ function FlashcardsContent() {

詞卡管理

-

管理你的詞卡集合

- {/* Search */} -
-
+ {/* 進階搜尋區域 */} +
+
+

搜尋詞卡

+ +
+ + {/* 主要搜尋框 */} +
setSearchTerm(e.target.value)} - placeholder="搜尋詞卡或卡組..." - className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" + placeholder="搜尋詞彙、翻譯或定義..." + className="w-full pl-12 pr-20 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent text-base" + onKeyDown={(e) => { + if (e.key === 'Escape') { + setSearchTerm('') + } + }} /> -
+
+ {(searchTerm || hasActiveFilters) && ( +
+ + {filteredCards.length} 結果 + + +
+ )}
+ + {/* 進階篩選選項 */} + {showAdvancedSearch && ( +
+
+ {/* CEFR等級篩選 */} +
+ + +
+ + {/* 詞性篩選 */} +
+ + +
+ + {/* 掌握度篩選 */} +
+ + +
+ + {/* 收藏篩選 */} +
+ + +
+
+ + {/* 快速篩選按鈕 */} +
+ 快速篩選: + + + + {hasActiveFilters && ( + + )} +
+
+ )} + + {/* 搜尋結果統計 */} + {(searchTerm || hasActiveFilters) && ( +
+
+ + + + + 找到 {filteredCards.length} 個詞卡 + {searchTerm && ( + ,包含 "{searchTerm}" + )} + +
+ {hasActiveFilters && ( + + )} +
+ )}
{/* Card Sets Tab */} @@ -376,18 +641,13 @@ function FlashcardsContent() { ) : (
{allCards.filter(card => card.isFavorite).map(card => ( -
-
{ - alert(`即將進入「${card.word}」的詳細頁面 (開發中)`) - }} - > +
+
{/* 收藏詞卡內容 - 與普通詞卡相同的佈局 */}
- - {card.difficultyLevel || 'A1'} + + {(card as any).difficultyLevel || 'A1'}
@@ -414,14 +674,18 @@ function FlashcardsContent() {
-

{card.word || '未設定'}

+

+ {searchTerm ? highlightSearchTerm(card.word || '未設定', searchTerm) : (card.word || '未設定')} +

{card.partOfSpeech || 'unknown'}
- {card.translation || '未設定'} + + {searchTerm ? highlightSearchTerm(card.translation || '未設定', searchTerm) : (card.translation || '未設定')} + {card.pronunciation && (
{card.pronunciation} @@ -478,11 +742,19 @@ function FlashcardsContent() {
-
+ {/* 進入詳細頁面箭頭 - 僅此處可點擊導航 */} +
+
@@ -541,19 +813,13 @@ function FlashcardsContent() { ) : (
{filteredCards.map(card => ( -
-
{ - // TODO: 導航到詞卡詳細頁面 - alert(`即將進入「${card.word}」的詳細頁面 (開發中)`) - }} - > +
+
{/* 詞卡右上角CEFR標註 */}
- - {card.difficultyLevel || 'A1'} + + {(card as any).difficultyLevel || 'A1'}
@@ -583,14 +849,18 @@ function FlashcardsContent() {
-

{card.word || '未設定'}

+

+ {searchTerm ? highlightSearchTerm(card.word || '未設定', searchTerm) : (card.word || '未設定')} +

{card.partOfSpeech || 'unknown'}
- {card.translation || '未設定'} + + {searchTerm ? highlightSearchTerm(card.translation || '未設定', searchTerm) : (card.translation || '未設定')} + {card.pronunciation && (
{card.pronunciation} @@ -621,46 +891,72 @@ function FlashcardsContent() { {/* 右側:操作按鈕 */}
- {/* 快速操作按鈕 */} -
e.stopPropagation()}> + {/* 重新設計的操作按鈕區 */} +
+ {/* 收藏按鈕 */} + + {/* 編輯按鈕 */} + + {/* 刪除按鈕 */} -
- {/* 進入詳細頁面箭頭 */} -
- - - + {/* 查看詳情按鈕 - 放大且更明顯 */} +