diff --git a/frontend/app/flashcards/page.tsx b/frontend/app/flashcards/page.tsx
index b12a451..b2e30ce 100644
--- a/frontend/app/flashcards/page.tsx
+++ b/frontend/app/flashcards/page.tsx
@@ -10,28 +10,8 @@ import { useToast } from '@/components/shared/Toast'
import { flashcardsService, type Flashcard } from '@/lib/services/flashcards'
import { imageGenerationService } from '@/lib/services/imageGeneration'
import { useFlashcardSearch, type SearchActions } from '@/hooks/flashcards/useFlashcardSearch'
+import { getPartOfSpeechDisplay } from '@/lib/utils/flashcardUtils'
-// 詞性簡寫轉換 (全域函數)
-const getPartOfSpeechDisplay = (partOfSpeech: string): string => {
- const shortMap: {[key: string]: string} = {
- 'noun': 'n.',
- 'verb': 'v.',
- 'adjective': 'adj.',
- 'adverb': 'adv.',
- 'pronoun': 'pron.',
- 'conjunction': 'conj.',
- 'preposition': 'prep.',
- 'interjection': 'int.',
- 'idiom': 'idiom'
- }
-
- // 處理複合詞性 (如 "preposition/adverb")
- if (partOfSpeech?.includes('/')) {
- return partOfSpeech.split('/').map(p => shortMap[p.trim()] || p.trim()).join('/')
- }
-
- return shortMap[partOfSpeech] || partOfSpeech || ''
-}
// 重構後的FlashcardsContent組件
function FlashcardsContent({ showForm, setShowForm }: { showForm: boolean; setShowForm: (show: boolean) => void }) {
diff --git a/frontend/lib/services/imageGeneration.ts b/frontend/lib/services/imageGeneration.ts
index b67f4e0..fbe4423 100644
--- a/frontend/lib/services/imageGeneration.ts
+++ b/frontend/lib/services/imageGeneration.ts
@@ -105,7 +105,7 @@ class ImageGenerationService {
...request
}
- return this.makeRequest(`/api/imagegeneration/flashcards/${flashcardId}/generate`, {
+ return this.makeRequest(`/api/ImageGeneration/flashcards/${flashcardId}/generate`, {
method: 'POST',
body: JSON.stringify(defaultRequest)
})
@@ -113,12 +113,12 @@ class ImageGenerationService {
// 查詢生成狀態
async getGenerationStatus(requestId: string): Promise
> {
- return this.makeRequest(`/api/imagegeneration/requests/${requestId}/status`)
+ return this.makeRequest(`/api/ImageGeneration/requests/${requestId}/status`)
}
// 取消生成
async cancelGeneration(requestId: string): Promise> {
- return this.makeRequest(`/api/imagegeneration/requests/${requestId}/cancel`, {
+ return this.makeRequest(`/api/ImageGeneration/requests/${requestId}/cancel`, {
method: 'POST'
})
}
diff --git a/frontend/lib/utils/flashcardUtils.ts b/frontend/lib/utils/flashcardUtils.ts
new file mode 100644
index 0000000..c76a42f
--- /dev/null
+++ b/frontend/lib/utils/flashcardUtils.ts
@@ -0,0 +1,107 @@
+/**
+ * Flashcard 相關工具函數
+ * 統一管理詞卡相關的顯示和處理邏輯
+ */
+
+// 詞性簡寫轉換
+export const getPartOfSpeechDisplay = (partOfSpeech: string): string => {
+ const shortMap: {[key: string]: string} = {
+ 'noun': 'n.',
+ 'verb': 'v.',
+ 'adjective': 'adj.',
+ 'adverb': 'adv.',
+ 'pronoun': 'pron.',
+ 'conjunction': 'conj.',
+ 'preposition': 'prep.',
+ 'interjection': 'int.',
+ 'idiom': 'idiom'
+ }
+
+ // 處理複合詞性 (如 "preposition/adverb")
+ if (partOfSpeech?.includes('/')) {
+ return partOfSpeech.split('/').map(p => shortMap[p.trim()] || p.trim()).join('/')
+ }
+
+ return shortMap[partOfSpeech] || partOfSpeech || ''
+}
+
+// CEFR等級顏色獲取
+export const getCEFRColor = (level: string): 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'
+ }
+}
+
+// 熟練度等級顏色獲取
+export const getMasteryColor = (level: number): string => {
+ if (level >= 90) return 'bg-green-100 text-green-800'
+ if (level >= 70) return 'bg-yellow-100 text-yellow-800'
+ if (level >= 50) return 'bg-orange-100 text-orange-800'
+ return 'bg-red-100 text-red-800'
+}
+
+// 熟練度等級文字
+export const getMasteryText = (level: number): string => {
+ if (level >= 90) return '精通'
+ if (level >= 70) return '熟悉'
+ if (level >= 50) return '理解'
+ return '學習中'
+}
+
+// 下次複習時間格式化
+export const formatNextReviewDate = (dateString: string): string => {
+ const reviewDate = new Date(dateString)
+ const now = new Date()
+ const diffInDays = Math.ceil((reviewDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
+
+ if (diffInDays < 0) return '需要複習'
+ if (diffInDays === 0) return '今天'
+ if (diffInDays === 1) return '明天'
+ return `${diffInDays}天後`
+}
+
+// 詞卡創建時間格式化
+export const formatCreatedDate = (dateString: string): string => {
+ return new Date(dateString).toLocaleDateString('zh-TW')
+}
+
+// 獲取例句圖片URL (統一邏輯)
+export const getFlashcardImageUrl = (flashcard: any): string | null => {
+ // 優先使用 primaryImageUrl
+ if (flashcard.primaryImageUrl) {
+ return flashcard.primaryImageUrl
+ }
+
+ // 然後檢查 exampleImages 陣列
+ if (flashcard.exampleImages && flashcard.exampleImages.length > 0) {
+ const primaryImage = flashcard.exampleImages.find((img: any) => img.isPrimary)
+ if (primaryImage) return primaryImage.imageUrl
+ return flashcard.exampleImages[0].imageUrl
+ }
+
+ return null
+}
+
+// 詞卡統計計算
+export const calculateFlashcardStats = (flashcards: any[]) => {
+ const total = flashcards.length
+ const mastered = flashcards.filter(card => card.masteryLevel >= 80).length
+ const learning = flashcards.filter(card => card.masteryLevel >= 40 && card.masteryLevel < 80).length
+ const new_cards = flashcards.filter(card => card.masteryLevel < 40).length
+ const favorites = flashcards.filter(card => card.isFavorite).length
+
+ return {
+ total,
+ mastered,
+ learning,
+ new: new_cards,
+ favorites,
+ masteryPercentage: total > 0 ? Math.round((mastered / total) * 100) : 0
+ }
+}
\ No newline at end of file