# 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('') const [searchFilters, setSearchFilters] = useState({ cefrLevel: '', partOfSpeech: '', masteryLevel: '', onlyFavorites: false }) // 資料狀態 const [flashcards, setFlashcards] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // 表單狀態 const [showForm, setShowForm] = useState(false) const [editingCard, setEditingCard] = useState(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; // 詞彙分析資料 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; // 編輯時的初始資料 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 (
{/* 認證檢查邏輯 */} {children}
) } // 使用方式: ``` ## 5. API 服務層設計 ### 5.1 服務類別架構 #### FlashcardsService 類別 ```typescript class FlashcardsService { private readonly baseURL = `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5008'}/api`; // 統一的請求處理方法 private async makeRequest(endpoint: string, options: RequestInit = {}): Promise // CRUD 操作方法 async getFlashcards(search?: string, favoritesOnly?: boolean): Promise> async getFlashcard(id: string): Promise> async createFlashcard(data: CreateFlashcardRequest): Promise> async updateFlashcard(id: string, data: CreateFlashcardRequest): Promise> async deleteFlashcard(id: string): Promise> async toggleFavorite(id: string): Promise> } // 單例模式匯出 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 { 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: () =>
載入中...
, ssr: false }) ``` ### 7.2 資料載入優化 #### 條件式資料載入 ```typescript useEffect(() => { // 只在需要時載入資料 if (activeTab === 'favorites') { loadFavoriteFlashcards(); } else { loadAllFlashcards(); } }, [activeTab]) ``` #### 錯誤邊界處理 ```typescript // API 服務層錯誤處理 private async makeRequest(endpoint: string, options: RequestInit = {}): Promise { 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([]) const [loading, setLoading] = useState(true) // 搜尋狀態 const [searchTerm, setSearchTerm] = useState('') const [searchFilters, setSearchFilters] = useState(defaultFilters) } ``` #### 跨組件狀態同步 ```typescript // 通過 props 和 callback 實現父子組件通信 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 (
載入中...
) } ``` #### 按鈕載入狀態 ```typescript ``` ### 9.2 錯誤狀態處理 #### 頁面級錯誤 ```typescript if (error) { return (
{error}
) } ``` #### 表單錯誤 ```typescript {error && (
{error}
)} ``` ### 9.3 空狀態設計 #### 友善的空狀態 ```typescript {filteredCards.length === 0 ? (

沒有找到詞卡

創建新詞卡
) : ( // 詞卡列表 )} ``` ## 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) ? ( {part} ) : part ) } ``` ### 10.2 彈窗互動設計 #### Portal 渲染模式 ```typescript import { createPortal } from 'react-dom' const VocabPopup = () => { if (!selectedWord || !mounted) return null return createPortal( <> {/* 遮罩層 */}
{/* 彈窗內容 */}
{/* 詞彙詳細資訊 */}
, document.body ) } ``` ### 10.3 鍵盤操作支援 #### ESC 鍵清除搜尋 ```typescript { 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( {}} />) // 填寫表單 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)