From 5167d910902dfa264c59e972d6b697139d43b2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=84=AD=E6=B2=9B=E8=BB=92?= Date: Thu, 2 Oct 2025 03:58:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E5=BE=A9=E5=9C=96=E7=89=87?= =?UTF-8?q?=E7=94=9F=E6=88=90=E6=9C=8D=E5=8B=99=20+=20=E7=B5=B1=E4=B8=80?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E6=8C=89=E9=88=95=E8=A8=AD=E8=A8=88=20+=20AP?= =?UTF-8?q?I=20=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 後端修復: • 修復圖片生成 DI Scope 問題 - 解決 ObjectDisposedException • FlashcardsController 統一 API 格式 - 添加圖片和複習屬性 • Repository 正確載入圖片關聯數據 前端優化: • 統一播放按鈕為藍底漸層設計 (w-10 h-10) • 修復圖片顯示邏輯 - 正確構建完整 URL • FlashcardDetailHeader 防護性編程 - 避免 NaN 錯誤 • 優化圖片顯示比例 - 正方形容器避免變形 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../Controllers/FlashcardsController.cs | 40 ++++++++++++- .../Repositories/FlashcardRepository.cs | 4 ++ .../AI/Generation/ImageGenerationWorkflow.cs | 7 ++- .../components/flashcards/FlashcardCard.tsx | 5 +- .../flashcards/FlashcardContentBlocks.tsx | 56 ++++++++++++++----- .../flashcards/FlashcardDetailHeader.tsx | 21 ++++--- frontend/lib/utils/flashcardUtils.ts | 12 +++- 7 files changed, 115 insertions(+), 30 deletions(-) diff --git a/backend/DramaLing.Api/Controllers/FlashcardsController.cs b/backend/DramaLing.Api/Controllers/FlashcardsController.cs index 97bb6a1..84045f1 100644 --- a/backend/DramaLing.Api/Controllers/FlashcardsController.cs +++ b/backend/DramaLing.Api/Controllers/FlashcardsController.cs @@ -45,7 +45,13 @@ public class FlashcardsController : BaseController DifficultyLevelNumeric = f.DifficultyLevelNumeric, CEFR = CEFRHelper.ToString(f.DifficultyLevelNumeric), f.CreatedAt, - f.UpdatedAt + f.UpdatedAt, + // 添加圖片相關屬性 + HasExampleImage = f.FlashcardExampleImages.Any(), + PrimaryImageUrl = f.FlashcardExampleImages + .Where(fei => fei.IsPrimary) + .Select(fei => $"/images/examples/{fei.ExampleImage.RelativePath}") + .FirstOrDefault() }), Count = flashcards.Count() }; @@ -121,7 +127,37 @@ public class FlashcardsController : BaseController return ErrorResponse("NOT_FOUND", "詞卡不存在", null, 404); } - return SuccessResponse(flashcard); + // 格式化返回數據,保持與列表 API 一致 + var flashcardData = new + { + flashcard.Id, + flashcard.Word, + flashcard.Translation, + flashcard.Definition, + flashcard.PartOfSpeech, + flashcard.Pronunciation, + flashcard.Example, + flashcard.ExampleTranslation, + flashcard.IsFavorite, + DifficultyLevelNumeric = flashcard.DifficultyLevelNumeric, + CEFR = CEFRHelper.ToString(flashcard.DifficultyLevelNumeric), + flashcard.CreatedAt, + flashcard.UpdatedAt, + // 添加圖片相關屬性 + HasExampleImage = flashcard.FlashcardExampleImages.Any(), + PrimaryImageUrl = flashcard.FlashcardExampleImages + .Where(fei => fei.IsPrimary) + .Select(fei => $"/images/examples/{fei.ExampleImage.RelativePath}") + .FirstOrDefault(), + // 添加複習相關屬性 (暫時預設值) + TimesReviewed = 0, + MasteryLevel = 0, + NextReviewDate = (DateTime?)null, + // 保留完整的圖片關聯數據供前端使用 + FlashcardExampleImages = flashcard.FlashcardExampleImages + }; + + return SuccessResponse(flashcardData); } catch (UnauthorizedAccessException) { diff --git a/backend/DramaLing.Api/Repositories/FlashcardRepository.cs b/backend/DramaLing.Api/Repositories/FlashcardRepository.cs index 68a2451..e714342 100644 --- a/backend/DramaLing.Api/Repositories/FlashcardRepository.cs +++ b/backend/DramaLing.Api/Repositories/FlashcardRepository.cs @@ -30,6 +30,8 @@ public class FlashcardRepository : BaseRepository, IFlashcardReposito } return await query + .Include(f => f.FlashcardExampleImages) + .ThenInclude(fei => fei.ExampleImage) .OrderByDescending(f => f.CreatedAt) .ToListAsync(); } @@ -37,6 +39,8 @@ public class FlashcardRepository : BaseRepository, IFlashcardReposito public async Task GetByUserIdAndFlashcardIdAsync(Guid userId, Guid flashcardId) { return await _context.Flashcards + .Include(f => f.FlashcardExampleImages) + .ThenInclude(fei => fei.ExampleImage) .FirstOrDefaultAsync(f => f.Id == flashcardId && f.UserId == userId && !f.IsArchived); } diff --git a/backend/DramaLing.Api/Services/AI/Generation/ImageGenerationWorkflow.cs b/backend/DramaLing.Api/Services/AI/Generation/ImageGenerationWorkflow.cs index a636c0b..e8868a4 100644 --- a/backend/DramaLing.Api/Services/AI/Generation/ImageGenerationWorkflow.cs +++ b/backend/DramaLing.Api/Services/AI/Generation/ImageGenerationWorkflow.cs @@ -56,12 +56,15 @@ public class ImageGenerationWorkflow : IImageGenerationWorkflow _logger.LogInformation("Created generation request {RequestId} for flashcard {FlashcardId}", generationRequest.Id, flashcardId); - // 後台執行生成流程 + // 後台執行生成流程 - 使用獨立的 Scope 避免 ObjectDisposedException _ = Task.Run(async () => { + using var backgroundScope = _serviceProvider.CreateScope(); + var backgroundPipelineService = backgroundScope.ServiceProvider.GetRequiredService(); + try { - await _pipelineService.ExecuteGenerationPipelineAsync(generationRequest.Id); + await backgroundPipelineService.ExecuteGenerationPipelineAsync(generationRequest.Id); } catch (Exception ex) { diff --git a/frontend/components/flashcards/FlashcardCard.tsx b/frontend/components/flashcards/FlashcardCard.tsx index 07c419b..ca052f2 100644 --- a/frontend/components/flashcards/FlashcardCard.tsx +++ b/frontend/components/flashcards/FlashcardCard.tsx @@ -46,14 +46,15 @@ export const FlashcardCard: React.FC = ({
- {/* 例句圖片區域 - 響應式設計 */} -
+ {/* 例句圖片區域 - 響應式設計,保持正方形比例 */} +
{hasExampleImage(flashcard) ? ( // 有例句圖片時顯示圖片 {`${flashcard.word} { const target = e.target as HTMLImageElement target.style.display = 'none' diff --git a/frontend/components/flashcards/FlashcardContentBlocks.tsx b/frontend/components/flashcards/FlashcardContentBlocks.tsx index 0980792..0f3144e 100644 --- a/frontend/components/flashcards/FlashcardContentBlocks.tsx +++ b/frontend/components/flashcards/FlashcardContentBlocks.tsx @@ -1,7 +1,6 @@ import React from 'react' import type { Flashcard } from '@/lib/services/flashcards' import { getFlashcardImageUrl } from '@/lib/utils/flashcardUtils' -import { TTSButton } from '@/components/shared/TTSButton' interface FlashcardContentBlocksProps { flashcard: Flashcard @@ -72,11 +71,14 @@ export const FlashcardContentBlocks: React.FC = ({ {/* 例句圖片 */}
{getFlashcardImageUrl(flashcard) ? ( - {`${flashcard.word} +
+ {`${flashcard.word} +
) : (
@@ -140,14 +142,40 @@ export const FlashcardContentBlocks: React.FC = ({ "{flashcard.example}"

- +

diff --git a/frontend/components/flashcards/FlashcardDetailHeader.tsx b/frontend/components/flashcards/FlashcardDetailHeader.tsx index 9aecd18..f8f5cbd 100644 --- a/frontend/components/flashcards/FlashcardDetailHeader.tsx +++ b/frontend/components/flashcards/FlashcardDetailHeader.tsx @@ -1,7 +1,6 @@ import React from 'react' import type { Flashcard } from '@/lib/services/flashcards' import { getPartOfSpeechDisplay, getCEFRColor } from '@/lib/utils/flashcardUtils' -import { TTSButton } from '@/components/shared/TTSButton' interface FlashcardDetailHeaderProps { flashcard: Flashcard @@ -26,13 +25,13 @@ export const FlashcardDetailHeader: React.FC = ({ {flashcard.pronunciation} - {/* TTS播放按鈕 - 使用新的TTSButton組件 */} + {/* TTS播放按鈕 - 藍底漸層設計 */}