# DramaLing 前端架構詳細說明

## 1. 技術棧概覽

### 1.1 核心技術
- **框架**: Next.js 15 (App Router)
- **語言**: TypeScript 5.x
- **樣式框架**: Tailwind CSS 3.x
- **UI 組件**: React 18.x + 自定義組件
- **狀態管理**: React useState/useEffect hooks
- **API 通信**: Fetch API + 自定義 Service 層

### 1.2 開發工具
- **套件管理**: npm
- **建置工具**: Next.js 內建 (Webpack + SWC)
- **型別檢查**: TypeScript Compiler
- **代碼格式化**: Prettier (如果配置)
- **代碼檢查**: ESLint (如果配置)

## 2. 專案結構

### 2.1 目錄架構

```
frontend/
├── app/                    # Next.js App Router 頁面
│   ├── flashcards/        # 詞卡管理頁面
│   │   ├── page.tsx       # 詞卡列表頁面
│   │   └── [id]/          # 動態詞卡詳細頁面
│   │       └── page.tsx
│   ├── generate/          # AI 生成詞卡頁面
│   │   └── page.tsx
│   ├── settings/          # 設定頁面
│   │   └── page.tsx
│   ├── layout.tsx         # 根佈局
│   ├── page.tsx           # 首頁
│   └── globals.css        # 全域樣式
├── components/            # 可重用組件
│   ├── ClickableTextV2.tsx      # 可點擊文字組件
│   ├── FlashcardForm.tsx        # 詞卡表單組件
│   ├── Navigation.tsx           # 導航組件
│   ├── ProtectedRoute.tsx       # 路由保護組件
│   └── AudioPlayer.tsx          # 音頻播放組件
├── lib/                   # 工具函數和服務
│   ├── services/          # API 服務層
│   │   ├── flashcards.ts  # 詞卡 API 服務
│   │   └── auth.ts        # 認證 API 服務
│   └── utils/             # 工具函數
├── public/                # 靜態資源
│   └── images/            # 圖片資源
├── package.json           # 專案配置
├── tailwind.config.js     # Tailwind 配置
├── tsconfig.json          # TypeScript 配置
└── next.config.js         # Next.js 配置
```

## 3. 頁面架構設計

### 3.1 詞卡管理頁面 (app/flashcards/page.tsx)

#### 組件層級結構
```
FlashcardsPage (Protected Route Wrapper)
└── FlashcardsContent (Main Logic Component)
    ├── Navigation (Top Navigation Bar)
    ├── Page Header (Title + Action Buttons)
    ├── Tab System (All Cards / Favorites)
    ├── Search & Filter Section
    │   ├── Main Search Input
    │   ├── Advanced Filters (Collapsible)
    │   └── Quick Filter Buttons
    ├── Flashcard List Display
    │   └── Flashcard Card Components (Repeated)
    └── FlashcardForm Modal (When Editing)
```

#### 狀態管理架構
```typescript
// 主要狀態
const [activeTab, setActiveTab] = useState<'all-cards' | 'favorites'>('all-cards')
const [searchTerm, setSearchTerm] = useState<string>('')
const [searchFilters, setSearchFilters] = useState<SearchFilters>({
  cefrLevel: '',
  partOfSpeech: '',
  masteryLevel: '',
  onlyFavorites: false
})

// 資料狀態
const [flashcards, setFlashcards] = useState<Flashcard[]>([])
const [loading, setLoading] = useState<boolean>(true)
const [error, setError] = useState<string | null>(null)

// 表單狀態
const [showForm, setShowForm] = useState<boolean>(false)
const [editingCard, setEditingCard] = useState<Flashcard | null>(null)
```

### 3.2 AI 分析頁面 (app/generate/page.tsx)

#### 組件結構
```
GeneratePage (Protected Route Wrapper)
└── GenerateContent (Main Logic Component)
    ├── Navigation
    ├── Input Section (Conditional Rendering)
    │   ├── Text Input Area
    │   ├── Character Counter
    │   └── Analysis Button
    └── Analysis Results Section (Conditional Rendering)
        ├── Star Explanation Banner
        ├── Grammar Correction Panel (If Needed)
        ├── Main Sentence Display
        │   ├── Vocabulary Statistics Cards
        │   ├── ClickableTextV2 Component
        │   ├── Translation Section
        │   └── Idioms Display Section
        └── Action Buttons
```

#### 分析結果資料流
```typescript
// AI 分析請求
handleAnalyzeSentence()
  → fetch('/api/ai/analyze-sentence')
  → setSentenceAnalysis(apiData)
  → setShowAnalysisView(true)

// 詞卡保存流程
handleSaveWord()
  → flashcardsService.createFlashcard()
  → alert(success/failure message)
```

## 4. 組件設計模式

### 4.1 可重用組件設計

#### ClickableTextV2 組件
```typescript
interface ClickableTextProps {
  text: string;                                    // 顯示文字
  analysis?: Record<string, WordAnalysis>;         // 詞彙分析資料
  onWordClick?: (word: string, analysis: WordAnalysis) => void;
  onSaveWord?: (word: string, analysis: WordAnalysis) => Promise<{success: boolean}>;
  remainingUsage?: number;                         // 剩餘使用次數
  showIdiomsInline?: boolean;                      // 是否內嵌顯示慣用語
}

// 設計特色：
// - 詞彙點擊互動
// - CEFR 等級顏色編碼
// - 星星標記 (高頻詞彙)
// - 彈出式詞彙詳情
// - Portal 渲染優化
```

#### FlashcardForm 組件
```typescript
interface FlashcardFormProps {
  initialData?: Partial<Flashcard>;  // 編輯時的初始資料
  isEdit?: boolean;                  // 是否為編輯模式
  onSuccess: () => void;             // 成功回調
  onCancel: () => void;              // 取消回調
}

// 表單欄位：
// - word (必填)
// - translation (必填)
// - definition (必填)
// - pronunciation (選填)
// - partOfSpeech (選填，下拉選單)
// - example (必填)
// - exampleTranslation (選填)
```

### 4.2 路由保護模式

#### ProtectedRoute 組件
```typescript
// 用途：保護需要認證的頁面
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
  // 檢查認證狀態
  // 未登入時導向登入頁面
  // 已登入時顯示子組件
  return (
    <div>
      {/* 認證檢查邏輯 */}
      {children}
    </div>
  )
}

// 使用方式：
<ProtectedRoute>
  <FlashcardsContent />
</ProtectedRoute>
```

## 5. API 服務層設計

### 5.1 服務類別架構

#### FlashcardsService 類別
```typescript
class FlashcardsService {
  private readonly baseURL = `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5008'}/api`;

  // 統一的請求處理方法
  private async makeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T>

  // CRUD 操作方法
  async getFlashcards(search?: string, favoritesOnly?: boolean): Promise<ApiResponse<{flashcards: Flashcard[], count: number}>>
  async getFlashcard(id: string): Promise<ApiResponse<Flashcard>>
  async createFlashcard(data: CreateFlashcardRequest): Promise<ApiResponse<Flashcard>>
  async updateFlashcard(id: string, data: CreateFlashcardRequest): Promise<ApiResponse<Flashcard>>
  async deleteFlashcard(id: string): Promise<ApiResponse<void>>
  async toggleFavorite(id: string): Promise<ApiResponse<void>>
}

// 單例模式匯出
export const flashcardsService = new FlashcardsService();
```

### 5.2 型別定義標準化

#### 核心型別定義
```typescript
// 詞卡介面定義
export interface Flashcard {
  id: string;
  word: string;
  translation: string;
  definition: string;
  partOfSpeech: string;
  pronunciation: string;
  example: string;
  exampleTranslation?: string;
  masteryLevel: number;
  timesReviewed: number;
  isFavorite: boolean;
  nextReviewDate: string;
  difficultyLevel: string;
  createdAt: string;
  updatedAt?: string;
}

// API 回應格式
export interface ApiResponse<T> {
  success: boolean;
  data?: T;
  error?: string;
  message?: string;
}

// 創建請求格式
export interface CreateFlashcardRequest {
  word: string;
  translation: string;
  definition: string;
  pronunciation: string;
  partOfSpeech: string;
  example: string;
  exampleTranslation?: string;
}
```

## 6. 樣式系統架構

### 6.1 Tailwind CSS 配置

#### 主要設計系統
```javascript
// tailwind.config.js
module.exports = {
  content: ['./app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {
      colors: {
        primary: {
          DEFAULT: '#3B82F6',  // 主色調藍色
          hover: '#2563EB'     // 懸停狀態
        }
      }
    }
  }
}
```

#### CEFR 等級顏色系統
```typescript
const getCEFRColor = (level: 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'
  }
}
```

### 6.2 響應式設計模式

#### 斷點策略
```css
/* 手機版 */
@media (max-width: 767px) {
  .flashcard-grid { grid-template-columns: 1fr; }
  .search-filters { flex-direction: column; }
}

/* 平板版 */
@media (min-width: 768px) and (max-width: 1023px) {
  .flashcard-grid { grid-template-columns: repeat(2, 1fr); }
}

/* 桌面版 */
@media (min-width: 1024px) {
  .flashcard-grid { grid-template-columns: repeat(3, 1fr); }
}
```

## 7. 效能優化策略

### 7.1 React 效能優化

#### useMemo 和 useCallback 使用
```typescript
// 快取詞彙統計計算
const vocabularyStats = useMemo(() => {
  if (!sentenceAnalysis?.vocabularyAnalysis) return defaultStats;

  // 複雜計算邏輯
  return calculateStats(sentenceAnalysis.vocabularyAnalysis);
}, [sentenceAnalysis])

// 快取事件處理函數
const handleWordClick = useCallback(async (word: string, event: React.MouseEvent) => {
  // 事件處理邏輯
}, [findWordAnalysis, onWordClick, calculatePopupPosition])
```

#### 組件懶載入
```typescript
// 動態導入大型組件
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
  loading: () => <div>載入中...</div>,
  ssr: false
})
```

### 7.2 資料載入優化

#### 條件式資料載入
```typescript
useEffect(() => {
  // 只在需要時載入資料
  if (activeTab === 'favorites') {
    loadFavoriteFlashcards();
  } else {
    loadAllFlashcards();
  }
}, [activeTab])
```

#### 錯誤邊界處理
```typescript
// API 服務層錯誤處理
private async makeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
  try {
    const response = await fetch(`${this.baseURL}${endpoint}`, {
      headers: {
        'Content-Type': 'application/json',
        ...options.headers,
      },
      ...options,
    });

    if (!response.ok) {
      const errorData = await response.json().catch(() => ({ error: 'Network error' }));
      throw new Error(errorData.error || `HTTP ${response.status}`);
    }

    return response.json();
  } catch (error) {
    console.error('API request failed:', error);
    throw error;
  }
}
```

## 8. 狀態管理模式

### 8.1 本地狀態管理

#### 頁面級狀態
```typescript
// 每個頁面管理自己的狀態
function FlashcardsContent() {
  // UI 狀態
  const [activeTab, setActiveTab] = useState('all-cards')
  const [showForm, setShowForm] = useState(false)

  // 資料狀態
  const [flashcards, setFlashcards] = useState<Flashcard[]>([])
  const [loading, setLoading] = useState(true)

  // 搜尋狀態
  const [searchTerm, setSearchTerm] = useState('')
  const [searchFilters, setSearchFilters] = useState(defaultFilters)
}
```

#### 跨組件狀態同步
```typescript
// 通過 props 和 callback 實現父子組件通信
<FlashcardForm
  initialData={editingCard}
  isEdit={!!editingCard}
  onSuccess={handleFormSuccess}  // 成功後重新載入資料
  onCancel={() => setShowForm(false)}
/>
```

### 8.2 持久化狀態

#### localStorage 使用
```typescript
// 用戶偏好設定
const userLevel = localStorage.getItem('userEnglishLevel') || 'A2'

// 搜尋歷史 (未來功能)
const searchHistory = JSON.parse(localStorage.getItem('searchHistory') || '[]')
```

## 9. 用戶體驗設計

### 9.1 載入狀態處理

#### 統一載入狀態
```typescript
if (loading) {
  return (
    <div className="min-h-screen bg-gray-50 flex items-center justify-center">
      <div className="text-lg">載入中...</div>
    </div>
  )
}
```

#### 按鈕載入狀態
```typescript
<button
  disabled={loading}
  className="disabled:opacity-50 disabled:cursor-not-allowed"
>
  {loading ? '載入中...' : '提交'}
</button>
```

### 9.2 錯誤狀態處理

#### 頁面級錯誤
```typescript
if (error) {
  return (
    <div className="min-h-screen bg-gray-50 flex items-center justify-center">
      <div className="text-red-600">{error}</div>
    </div>
  )
}
```

#### 表單錯誤
```typescript
{error && (
  <div className="bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-lg">
    {error}
  </div>
)}
```

### 9.3 空狀態設計

#### 友善的空狀態
```typescript
{filteredCards.length === 0 ? (
  <div className="text-center py-12">
    <p className="text-gray-500 mb-4">沒有找到詞卡</p>
    <Link href="/generate" className="bg-primary text-white px-4 py-2 rounded-lg">
      創建新詞卡
    </Link>
  </div>
) : (
  // 詞卡列表
)}
```

## 10. 互動設計模式

### 10.1 搜尋互動

#### 即時搜尋
```typescript
// 輸入時即時過濾，無防抖延遲
const filteredCards = allCards.filter(card => {
  if (searchTerm) {
    const searchLower = searchTerm.toLowerCase()
    return card.word?.toLowerCase().includes(searchLower) ||
           card.translation?.toLowerCase().includes(searchLower) ||
           card.definition?.toLowerCase().includes(searchLower)
  }
  return true
})
```

#### 搜尋結果高亮
```typescript
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) ? (
      <mark key={index} className="bg-yellow-200 text-yellow-900 px-1 rounded">
        {part}
      </mark>
    ) : part
  )
}
```

### 10.2 彈窗互動設計

#### Portal 渲染模式
```typescript
import { createPortal } from 'react-dom'

const VocabPopup = () => {
  if (!selectedWord || !mounted) return null

  return createPortal(
    <>
      {/* 遮罩層 */}
      <div className="fixed inset-0 bg-black bg-opacity-50 z-40" onClick={closePopup} />

      {/* 彈窗內容 */}
      <div className="fixed z-50 bg-white rounded-xl shadow-lg" style={{...}}>
        {/* 詞彙詳細資訊 */}
      </div>
    </>,
    document.body
  )
}
```

### 10.3 鍵盤操作支援

#### ESC 鍵清除搜尋
```typescript
<input
  onKeyDown={(e) => {
    if (e.key === 'Escape') {
      setSearchTerm('')
    }
  }}
/>
```

## 11. 開發與建置

### 11.1 開發環境

#### 開發伺服器啟動
```bash
cd frontend
npm run dev

# 開發伺服器運行於: http://localhost:3000
# Hot Reload 啟用
# Fast Refresh 啟用
```

#### 環境變數配置
```bash
# .env.local
NEXT_PUBLIC_API_URL=http://localhost:5008
```

### 11.2 建置優化

#### Next.js 配置 (next.config.js)
```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    appDir: true,  // 啟用 App Router
  },
  images: {
    domains: ['localhost'],  // 圖片域名白名單
  }
}

module.exports = nextConfig
```

#### TypeScript 配置 (tsconfig.json)
```json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "es6"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "baseUrl": ".",
    "paths": {
      "@/*": ["./*"]  // 路徑別名
    }
  }
}
```

## 12. 測試策略

### 12.1 組件測試
```typescript
// 未來實作：Jest + React Testing Library
import { render, screen, fireEvent } from '@testing-library/react'
import { FlashcardForm } from './FlashcardForm'

test('should submit form with valid data', async () => {
  const onSuccess = jest.fn()
  render(<FlashcardForm onSuccess={onSuccess} onCancel={() => {}} />)

  // 填寫表單
  fireEvent.change(screen.getByLabelText('單字'), { target: { value: 'test' } })

  // 提交表單
  fireEvent.click(screen.getByText('創建詞卡'))

  // 驗證結果
  expect(onSuccess).toHaveBeenCalled()
})
```

### 12.2 API 服務測試
```typescript
// Mock fetch 進行單元測試
global.fetch = jest.fn()

test('should create flashcard successfully', async () => {
  const mockResponse = { success: true, data: mockFlashcard }
  ;(fetch as jest.Mock).mockResolvedValueOnce({
    ok: true,
    json: async () => mockResponse,
  })

  const result = await flashcardsService.createFlashcard(mockData)
  expect(result.success).toBe(true)
})
```

---

**文檔版本**: v1.0
**建立日期**: 2025-09-24
**維護負責**: 前端開發團隊
**下次審核**: 架構變更時

> 📋 相關文檔：
> - [系統架構總覽](./system-architecture.md)
> - [後端架構詳細說明](./backend-architecture.md)
> - [詞卡 API 規格](./flashcard-api-specification.md)