# 前端詞卡管理資料流程圖 ## 📋 **文檔概覽** 本文檔詳細說明前端詞卡管理功能如何取得詞卡及其例句圖片,並顯示在用戶介面上的完整資料流程。 --- ## 🏗️ **整體架構圖** ```mermaid graph TB A[用戶訪問 /flashcards] --> B[FlashcardsContent 組件初始化] B --> C[useEffect 觸發資料載入] C --> D[flashcardsService.getFlashcards()] D --> E[HTTP GET /api/flashcards] E --> F[FlashcardsController.GetFlashcards()] F --> G[EF Core 查詢 + Include 圖片關聯] G --> H[資料庫查詢 flashcards + example_images] H --> I[IImageStorageService.GetImageUrlAsync()] I --> J[組裝回應資料] J --> K[前端接收 flashcards 陣列] K --> L[狀態更新 setFlashcards()] L --> M[UI 重新渲染] M --> N[FlashcardItem 組件渲染] N --> O[圖片顯示邏輯判斷] O --> P{有例句圖片?} P -->|Yes| Q[顯示圖片 ] P -->|No| R[顯示新增按鈕] Q --> S[響應式圖片縮放] R --> T[點擊觸發 handleGenerateExampleImage] ``` --- ## 🔄 **詳細資料流程** ### **第1階段:頁面初始化** #### **1.1 組件載入** ```typescript // /frontend/app/flashcards/page.tsx export default function FlashcardsPage() { return ( ) } function FlashcardsContent() { const [searchState, searchActions] = useFlashcardSearch(activeTab) useEffect(() => { loadTotalCounts() // 初始化資料載入 }, []) } ``` #### **1.2 資料載入觸發** ```mermaid sequenceDiagram participant UC as 用戶 participant FC as FlashcardsContent participant FS as flashcardsService participant API as Backend API UC->>FC: 訪問 /flashcards FC->>FC: useEffect 觸發 FC->>FS: searchActions.refresh() FS->>API: GET /api/flashcards ``` --- ### **第2階段:後端資料處理** #### **2.1 API 端點處理** ```csharp // FlashcardsController.GetFlashcards() var query = _context.Flashcards .Include(f => f.FlashcardExampleImages) // 載入圖片關聯 .ThenInclude(fei => fei.ExampleImage) // 載入圖片詳情 .Where(f => f.UserId == userId && !f.IsArchived) .AsQueryable(); ``` #### **2.2 資料庫查詢流程** ```mermaid graph LR A[Flashcards Table] --> B[FlashcardExampleImages Table] B --> C[ExampleImages Table] A --> D[User Filter] A --> E[Search Filter] A --> F[CEFR Filter] C --> G[Image URL Generation] G --> H[完整 JSON 回應] ``` #### **2.3 圖片資料組裝** ```csharp // 每個 flashcard 處理圖片關聯 foreach (var flashcardImage in flashcard.FlashcardExampleImages) { var imageUrl = await _imageStorageService.GetImageUrlAsync( flashcardImage.ExampleImage.RelativePath ); exampleImages.Add(new ExampleImageDto { Id = flashcardImage.ExampleImage.Id.ToString(), ImageUrl = imageUrl, // 完整的 HTTP URL IsPrimary = flashcardImage.IsPrimary, QualityScore = flashcardImage.ExampleImage.QualityScore }); } ``` --- ### **第3階段:前端資料接收與處理** #### **3.1 API 回應結構** ```json { "success": true, "data": { "flashcards": [ { "id": "94c32b17-53a7-4de5-9bfc-f6d4f2dc1368", "word": "up", "translation": "出", "example": "He brought the issue up in the meeting.", // 新增的圖片相關欄位 "exampleImages": [ { "id": "d96d3330-7814-45e1-9ac6-801c8ca32ee7", "imageUrl": "https://localhost:5008/images/examples/xxx.png", "isPrimary": true, "qualityScore": 0.95, "fileSize": 190000 } ], "hasExampleImage": true, "primaryImageUrl": "https://localhost:5008/images/examples/xxx.png" } ] } } ``` #### **3.2 前端狀態更新流程** ```mermaid graph TD A[API 回應接收] --> B[解構 flashcards 陣列] B --> C[更新 React 狀態] C --> D[觸發組件重新渲染] D --> E[FlashcardItem 組件 map 渲染] E --> F[個別詞卡資料傳入] F --> G[圖片顯示邏輯判斷] ``` --- ### **第4階段:UI 渲染與圖片顯示** #### **4.1 詞卡項目渲染** ```typescript // FlashcardItem 組件 function FlashcardItem({ card, ... }) { return (
{/* 圖片區域 - 響應式設計 */}
{hasExampleImage(card) ? ( // 顯示圖片 {`${card.word} ) : ( // 顯示新增按鈕
onGenerateExampleImage(card)}> 新增例句圖
)}
{/* 詞卡資訊 */}

{card.word}

{card.translation}
) } ``` #### **4.2 圖片顯示判斷邏輯** ```mermaid flowchart TD A[FlashcardItem 渲染] --> B{檢查 card.hasExampleImage} B -->|true| C[取得 card.primaryImageUrl] B -->|false| D[顯示新增例句圖按鈕] C --> E[設定 img src 屬性] E --> F[瀏覽器載入圖片] F --> G{圖片載入成功?} G -->|成功| H[顯示 512x512 圖片] G -->|失敗| I[顯示錯誤提示] D --> J[用戶點擊生成按鈕] J --> K[觸發 handleGenerateExampleImage] H --> L[CSS 響應式縮放顯示] ``` --- ## 🔧 **技術實現細節** ### **前端服務層** ```typescript // /frontend/lib/services/flashcards.ts export const flashcardsService = { async getFlashcards(): Promise { const response = await fetch(`${API_URL}/api/flashcards`, { headers: { 'Authorization': `Bearer ${getToken()}`, 'Content-Type': 'application/json' } }) return response.json() } } // 回應介面定義 interface Flashcard { id: string word: string translation: string example: string // 圖片相關欄位 exampleImages: ExampleImage[] hasExampleImage: boolean primaryImageUrl?: string } interface ExampleImage { id: string imageUrl: string isPrimary: boolean qualityScore?: number fileSize?: number } ``` ### **圖片顯示邏輯** ```typescript // 當前實現 (將被取代) const getExampleImage = (card: Flashcard): string | null => { // 硬編碼映射 (舊方式) const imageMap: {[key: string]: string} = { 'evidence': '/images/examples/bring_up.png', } return imageMap[card.word?.toLowerCase()] || null } // 新實現 (基於 API 資料) const getExampleImage = (card: Flashcard): string | null => { return card.primaryImageUrl || null } const hasExampleImage = (card: Flashcard): boolean => { return card.hasExampleImage } ``` --- ## 🖼️ **圖片載入和顯示流程** ### **圖片 URL 生成過程** ```mermaid sequenceDiagram participant FE as 前端 participant BE as 後端 API participant DB as 資料庫 participant FS as 檔案系統 FE->>BE: GET /api/flashcards BE->>DB: 查詢 flashcards + images DB-->>BE: 返回關聯資料 BE->>FS: 檢查圖片檔案存在 FS-->>BE: 確認檔案路徑 BE->>BE: 生成完整 HTTP URL BE-->>FE: 回應包含 imageUrl FE->>FS: 瀏覽器請求圖片 FS-->>FE: 返回 512x512 PNG 圖片 ``` ### **響應式圖片顯示** ```css /* 圖片容器響應式尺寸 */ .example-image-container { /* 手機 */ width: 128px; /* w-32 */ height: 80px; /* h-20 */ } @media (min-width: 640px) { .example-image-container { /* 平板 */ width: 160px; /* sm:w-40 */ height: 96px; /* sm:h-24 */ } } @media (min-width: 768px) { .example-image-container { /* 桌面 */ width: 192px; /* md:w-48 */ height: 128px; /* md:h-32 */ } } /* 圖片本身處理 */ .example-image { width: 100%; height: 100%; object-fit: cover; /* 保持比例,裁切適應容器 */ border-radius: 8px; } ``` --- ## ⚡ **效能優化策略** ### **前端優化** ```typescript // 圖片懶載入 {`${card.word} // 錯誤處理 { e.target.style.display = 'none' // 顯示備用內容 }} /> ``` ### **後端優化** ```csharp // 查詢優化 var flashcards = await query .AsNoTracking() // 只讀查詢優化 .OrderByDescending(f => f.CreatedAt) .ToListAsync(); // 圖片 URL 快取 (未來實現) private readonly IMemoryCache _urlCache; ``` --- ## 🎮 **用戶互動流程** ### **圖片生成流程** ```mermaid flowchart TD A[用戶看到詞卡] --> B{是否有圖片?} B -->|有| C[顯示 512x512 圖片] B -->|無| D[顯示新增例句圖按鈕] D --> E[用戶點擊按鈕] E --> F[觸發 handleGenerateExampleImage] F --> G[調用圖片生成 API] G --> H[顯示生成進度] H --> I[等待 2-3 分鐘] I --> J[生成完成] J --> K[自動刷新詞卡列表] K --> L[新圖片顯示在詞卡中] ``` ### **生成進度顯示** ```typescript // 生成狀態管理 const [generatingCards, setGeneratingCards] = useState>(new Set()) const handleGenerateExampleImage = async (card: Flashcard) => { // 1. 標記為生成中 setGeneratingCards(prev => new Set([...prev, card.id])) try { // 2. 調用生成 API const result = await imageGenerationService.generateImage(card.id) // 3. 輪詢進度 await imageGenerationService.pollUntilComplete(result.requestId) // 4. 刷新資料 await searchActions.refresh() // 5. 顯示成功訊息 toast.success(`「${card.word}」的例句圖片生成完成!`) } catch (error) { toast.error(`圖片生成失敗: ${error.message}`) } finally { // 6. 移除生成中狀態 setGeneratingCards(prev => { const newSet = new Set(prev) newSet.delete(card.id) return newSet }) } } ``` --- ## 📊 **資料流轉換表** | 階段 | 資料格式 | 位置 | 範例 | |------|----------|------|------| | **資料庫** | 關聯表格 | `flashcard_example_images` | `{flashcard_id, example_image_id, is_primary}` | | **EF Core** | 實體物件 | `Flashcard.FlashcardExampleImages` | `List` | | **後端 API** | JSON 回應 | HTTP Response | `{hasExampleImage: true, primaryImageUrl: "https://..."}` | | **前端狀態** | TypeScript 物件 | React State | `flashcards: Flashcard[]` | | **UI 組件** | JSX 元素 | React Component | `` | | **瀏覽器** | 實際圖片 | DOM | `512x512 PNG 圖片顯示` | --- ## 🔍 **錯誤處理流程** ### **API 層級錯誤** ```mermaid graph TD A[API 調用] --> B{網路狀態} B -->|成功| C[解析 JSON] B -->|失敗| D[顯示網路錯誤] C --> E{success: true?} E -->|Yes| F[正常資料流程] E -->|No| G[顯示 API 錯誤訊息] D --> H[重試機制] G --> H H --> I[用戶手動重新整理] ``` ### **圖片載入錯誤** ```typescript // 圖片載入失敗處理 const handleImageError = (e: React.SyntheticEvent) => { const target = e.target as HTMLImageElement target.style.display = 'none' // 顯示備用內容 target.parentElement!.innerHTML = `
圖片載入失敗
` } ``` --- ## 🎯 **實際運作範例** ### **情境1:有圖片的詞卡 (deal)** ``` 1. 用戶訪問詞卡頁面 2. API 返回: hasExampleImage: true, primaryImageUrl: "https://localhost:5008/..." 3. React 渲染: 4. 瀏覽器載入: 512x512 PNG 圖片 (約190KB) 5. CSS 處理: 響應式縮放顯示在詞卡中 ``` ### **情境2:無圖片的詞卡 (up)** ``` 1. 用戶訪問詞卡頁面 2. API 返回: hasExampleImage: false, primaryImageUrl: null 3. React 渲染: 新增例句圖按鈕 4. 用戶點擊: 觸發圖片生成流程 5. 生成完成: 自動刷新並顯示新圖片 ``` --- ## 🔮 **未來擴展規劃** ### **前端增強功能** - **圖片預覽**: 點擊圖片查看大圖 - **多圖片支援**: 輪播顯示多張例句圖 - **圖片編輯**: 刪除、重新生成功能 - **批量生成**: 一次為多個詞卡生成圖片 ### **效能優化** - **圖片 CDN**: 雲端加速分發 - **WebP 格式**: 更小的檔案大小 - **預載入**: 預先載入即將顯示的圖片 - **虛擬化**: 大量詞卡的效能優化 --- ## 📈 **監控指標** ### **前端效能** - 頁面載入時間: 目標 < 2 秒 - 圖片載入時間: 目標 < 1 秒 - API 回應時間: 目標 < 500ms ### **用戶體驗** - 圖片顯示成功率: 目標 > 95% - 生成成功率: 目標 > 90% - 用戶滿意度: 目標 > 4.5/5 --- **文檔版本**: v1.0 **建立日期**: 2025-09-24 **最後更新**: 2025-09-24 **相關文檔**: [前後端整合計劃](./EXAMPLE_IMAGE_FRONTEND_BACKEND_INTEGRATION_PLAN.md)