220 lines
6.9 KiB
Markdown
220 lines
6.9 KiB
Markdown
# 前端圖片 URL 處理機制詳解
|
||
|
||
## 📋 概述
|
||
|
||
本文檔詳細解釋 DramaLing 前端如何處理詞卡圖片 URL,以及為什麼不同格式的 URL 都能正常運作。
|
||
|
||
## 🎯 核心工具:`flashcardUtils.ts`
|
||
|
||
### 檔案位置
|
||
```
|
||
frontend/lib/utils/flashcardUtils.ts
|
||
```
|
||
|
||
### 檔案目的
|
||
**統一管理詞卡相關的顯示和處理邏輯** - 避免在各個元件中重複寫相同的處理代碼
|
||
|
||
---
|
||
|
||
## 📝 核心函數詳細解析
|
||
|
||
### 1. **圖片 URL 處理 - `getFlashcardImageUrl()`**
|
||
|
||
**位置**: 第 77-99 行
|
||
**用途**: 智能處理詞卡圖片 URL,支援多種格式和來源
|
||
|
||
#### 處理邏輯流程:
|
||
|
||
```typescript
|
||
export const getFlashcardImageUrl = (flashcard: any): string | null => {
|
||
// 第一優先:檢查 primaryImageUrl
|
||
if (flashcard.primaryImageUrl) {
|
||
// 判斷是相對路徑還是完整 URL
|
||
if (flashcard.primaryImageUrl.startsWith('/')) {
|
||
// 相對路徑:拼接後端基礎 URL
|
||
return `${API_CONFIG.BASE_URL}${flashcard.primaryImageUrl}`
|
||
}
|
||
// 完整 URL:直接使用
|
||
return flashcard.primaryImageUrl
|
||
}
|
||
|
||
// 第二優先:檢查 exampleImages 陣列
|
||
if (flashcard.exampleImages && flashcard.exampleImages.length > 0) {
|
||
// 尋找標記為主要的圖片
|
||
const primaryImage = flashcard.exampleImages.find((img: any) => img.isPrimary)
|
||
if (primaryImage) {
|
||
const imageUrl = primaryImage.imageUrl
|
||
return imageUrl?.startsWith('/') ? `${API_CONFIG.BASE_URL}${imageUrl}` : imageUrl
|
||
}
|
||
// 沒有主要圖片,使用第一張
|
||
const firstImageUrl = flashcard.exampleImages[0].imageUrl
|
||
return firstImageUrl?.startsWith('/') ? `${API_CONFIG.BASE_URL}${firstImageUrl}` : firstImageUrl
|
||
}
|
||
|
||
// 都沒有圖片
|
||
return null
|
||
}
|
||
```
|
||
|
||
#### 支援的 URL 格式:
|
||
|
||
| 格式類型 | 範例 | 處理方式 |
|
||
|---------|------|----------|
|
||
| **Google Cloud Storage** | `https://storage.googleapis.com/dramaling-images/examples/file.png` | 直接使用完整 URL |
|
||
| **本地服務** | `http://localhost:5008/images/examples/file.png` | 直接使用完整 URL |
|
||
| **相對路徑** | `/images/examples/file.png` | 拼接為 `http://localhost:5008/images/examples/file.png` |
|
||
|
||
---
|
||
|
||
### 2. **其他工具函數**
|
||
|
||
#### **詞性顯示 - `getPartOfSpeechDisplay()`**
|
||
```typescript
|
||
// 輸入:"noun" → 輸出:"n."
|
||
// 輸入:"adjective" → 輸出:"adj."
|
||
// 輸入:"preposition/adverb" → 輸出:"prep./adv." (支援複合詞性)
|
||
```
|
||
|
||
#### **CEFR 等級顏色 - `getCEFRColor()`**
|
||
```typescript
|
||
// A1 → "bg-green-100 text-green-700 border-green-200" (綠色)
|
||
// B1 → "bg-yellow-100 text-yellow-700 border-yellow-200" (黃色)
|
||
// C2 → "bg-purple-100 text-purple-700 border-purple-200" (紫色)
|
||
```
|
||
|
||
#### **熟練度處理**
|
||
- **`getMasteryColor()`**: 根據數字返回顏色 (90+→綠色, <50→紅色)
|
||
- **`getMasteryText()`**: 轉換為中文 (90+→"精通", <50→"學習中")
|
||
|
||
#### **日期格式化**
|
||
- **`formatNextReviewDate()`**: 複習時間 (過期→"需要複習", 明天→"明天")
|
||
- **`formatCreatedDate()`**: 台灣日期格式 ("2024/1/15")
|
||
|
||
#### **統計計算 - `calculateFlashcardStats()`**
|
||
```typescript
|
||
{
|
||
total: 總詞卡數,
|
||
mastered: 精通詞卡數 (熟練度 ≥ 80),
|
||
learning: 學習中詞卡數 (熟練度 40-79),
|
||
new: 新詞卡數 (熟練度 < 40),
|
||
favorites: 收藏詞卡數,
|
||
masteryPercentage: 精通百分比
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔍 實際運作流程
|
||
|
||
### API 回應格式
|
||
|
||
**目前狀態(修復後):**
|
||
- `/api/flashcards`: 回傳完整 Google Cloud Storage URL
|
||
- `/api/flashcards/{id}`: 回傳完整 Google Cloud Storage URL
|
||
|
||
```json
|
||
{
|
||
"primaryImageUrl": "https://storage.googleapis.com/dramaling-images/examples/b2bb23b8-16dd-44b2-bf64-34c468f2d362_e6498ba6-742b-473f-93b6-f4b58c3dd3e9.png"
|
||
}
|
||
```
|
||
|
||
### 前端處理流程
|
||
|
||
1. **接收 API 回應** → 取得 `primaryImageUrl`
|
||
2. **呼叫 `getFlashcardImageUrl()`** → 檢查 URL 格式
|
||
3. **格式判斷**:
|
||
- ✅ 完整 URL (`https://storage.googleapis.com/...`) → 直接使用
|
||
- ❌ 相對路徑 (`/images/...`) → 拼接後端域名(目前不會發生)
|
||
4. **元件渲染** → `<img src={imageUrl} />`
|
||
|
||
---
|
||
|
||
## 🎯 設計優勢
|
||
|
||
### **1. 兼容性設計**
|
||
- 支援多種 URL 格式(相對路徑/完整 URL)
|
||
- 可以無縫切換本地/雲端儲存
|
||
- 向後兼容舊版 API
|
||
|
||
### **2. 防禦性編程**
|
||
- 多重備用方案(primaryImageUrl → exampleImages → null)
|
||
- 自動處理路徑拼接邏輯
|
||
- 避免因 API 格式變更導致圖片顯示失敗
|
||
|
||
### **3. 集中化管理**
|
||
- 所有圖片 URL 處理邏輯集中在一個函數
|
||
- 修改邏輯時只需要改一個地方
|
||
- 多個元件可以重用相同邏輯
|
||
|
||
### **4. 模組化架構**
|
||
- 每個功能都有專門的工具函數
|
||
- 易於測試和維護
|
||
- 符合 DRY (Don't Repeat Yourself) 原則
|
||
|
||
---
|
||
|
||
## 🔧 使用範例
|
||
|
||
### 在元件中使用
|
||
|
||
```typescript
|
||
// FlashcardCard.tsx
|
||
import { getFlashcardImageUrl, getCEFRColor, getPartOfSpeechDisplay } from '@/lib/utils/flashcardUtils'
|
||
|
||
// 取得圖片 URL
|
||
const imageUrl = getFlashcardImageUrl(flashcard)
|
||
// 結果:https://storage.googleapis.com/dramaling-images/examples/file.png
|
||
|
||
// 取得 CEFR 顏色
|
||
const cefrClasses = getCEFRColor(flashcard.cefr)
|
||
// 結果:"bg-blue-100 text-blue-700 border-blue-200"
|
||
|
||
// 取得詞性簡寫
|
||
const partOfSpeech = getPartOfSpeechDisplay(flashcard.partOfSpeech)
|
||
// 結果:"n."
|
||
|
||
// 在 JSX 中使用
|
||
<img src={imageUrl} alt="example" />
|
||
<span className={cefrClasses}>{flashcard.cefr}</span>
|
||
<span>{partOfSpeech}</span>
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 問題解答
|
||
|
||
### Q: 為什麼不同格式的 URL 都能正常運作?
|
||
|
||
**A**: 前端設計了智能兼容性處理:
|
||
|
||
1. **完整 URL** → 直接使用(目前的情況)
|
||
2. **相對路徝** → 自動拼接後端域名(向後兼容)
|
||
3. **多重備用** → primaryImageUrl 失敗時使用 exampleImages
|
||
|
||
### Q: 這樣的設計有什麼好處?
|
||
|
||
**A**:
|
||
- ✅ **彈性切換**:可以在本地開發和雲端部署間切換
|
||
- ✅ **向後兼容**:支援舊版 API 格式
|
||
- ✅ **錯誤處理**:多重備用方案確保圖片顯示
|
||
- ✅ **維護性**:集中化管理,易於修改
|
||
|
||
### Q: 目前系統的實際運作狀況?
|
||
|
||
**A**:
|
||
- 後端統一回傳完整的 Google Cloud Storage URLs
|
||
- 前端接收到完整 URL,直接使用(不進入相對路徑處理邏輯)
|
||
- 圖片正常顯示,系統運作正常
|
||
|
||
---
|
||
|
||
## 📈 總結
|
||
|
||
`flashcardUtils.ts` 是一個設計良好的工具函數庫,實現了:
|
||
|
||
- **統一化**:所有詞卡相關的顯示邏輯集中管理
|
||
- **兼容性**:支援多種 URL 格式和資料來源
|
||
- **可維護性**:模組化設計,易於擴展和修改
|
||
- **可靠性**:防禦性編程,確保系統穩定運作
|
||
|
||
這種設計確保了前端系統的健壯性和可維護性,是現代前端架構的最佳實務範例。 |