# Tool_OCR 前端代碼示例和最佳實踐 ## 1. Tailwind CSS 樣式使用示例 ### 佈局組件 (Layout.tsx 提取) ```typescript // 導航欄樣式示例

{t('app.title')}

{/* 導航選項卡 */} ``` ### 按鈕變體示例 (button.tsx) ```typescript // 默認按鈕 // 危險操作 // 輪廓樣式 // 幽靈按鈕 // 鏈接樣式 // 不同尺寸 ``` ### 響應式卡片示例 (UploadPage.tsx 提取) ```typescript
{t('upload.selectedFiles')} ({selectedFiles.length})
{selectedFiles.map((file, index) => (

{file.name}

{formatFileSize(file.size)}

))}
``` ### 文件上傳拖放區域 (FileUpload.tsx 提取) ```typescript
{isDragActive ? (

{isDragReject ? t('upload.invalidFiles') : t('upload.dropFilesHere')}

) : ( <>

{t('upload.dragAndDrop')}

{t('upload.supportedFormats')}

)}
``` --- ## 2. React Query 使用示例 ### 批次狀態查詢 (ResultsPage.tsx 提取) ```typescript import { useQuery } from '@tanstack/react-query' export default function ResultsPage() { const { batchId } = useUploadStore() const [selectedFileId, setSelectedFileId] = useState(null) // 獲取批次狀態 const { data: batchStatus, isLoading } = useQuery({ queryKey: ['batchStatus', batchId], queryFn: () => apiClient.getBatchStatus(batchId!), enabled: !!batchId, // 只在有 batchId 時查詢 }) // 獲取 OCR 結果 const { data: ocrResult, isLoading: isLoadingResult } = useQuery({ queryKey: ['ocrResult', selectedFileId], queryFn: () => apiClient.getOCRResult(selectedFileId!.toString()), enabled: !!selectedFileId, }) if (!batchId) { return
Please upload files first
} return (
{isLoading ? (

Loading batch status...

) : ( )}
) } ``` --- ## 3. Zustand 狀態管理示例 ### 認證存儲 (authStore.ts) ```typescript import { create } from 'zustand' import { persist } from 'zustand/middleware' import type { User } from '@/types/api' interface AuthState { user: User | null isAuthenticated: boolean setUser: (user: User | null) => void logout: () => void } export const useAuthStore = create()( persist( (set) => ({ user: null, isAuthenticated: false, setUser: (user) => set({ user, isAuthenticated: user !== null, }), logout: () => set({ user: null, isAuthenticated: false, }), }), { name: 'auth-storage', // localStorage 鍵名 } ) ) // 使用示例 function LoginPage() { const setUser = useAuthStore((state) => state.setUser) const handleLogin = async (username: string, password: string) => { const response = await apiClient.login({ username, password }) setUser({ id: 1, username }) } } ``` ### 上傳狀態存儲 (uploadStore.ts) ```typescript export const useUploadStore = create()( persist( (set) => ({ batchId: null, files: [], uploadProgress: 0, setBatchId: (id) => { set({ batchId: id }) }, setFiles: (files) => set({ files }), setUploadProgress: (progress) => set({ uploadProgress: progress }), updateFileStatus: (fileId, status) => set((state) => ({ files: state.files.map((file) => file.id === fileId ? { ...file, status } : file ), })), clearUpload: () => set({ batchId: null, files: [], uploadProgress: 0, }), }), { name: 'tool-ocr-upload-store', // 只持久化 batchId 和 files,不持久化進度 partialize: (state) => ({ batchId: state.batchId, files: state.files, }), } ) ) // 使用示例 function UploadPage() { const { setBatchId, setFiles } = useUploadStore() const handleUploadSuccess = (data: UploadResponse) => { setBatchId(data.batch_id) setFiles(data.files) } } ``` --- ## 4. API 客戶端使用示例 ### API 認證流程 (api.ts) ```typescript // 登錄 const response = await apiClient.login({ username: 'admin', password: 'password' }) // Token 自動保存到 localStorage // 後續請求自動附帶 token const status = await apiClient.getBatchStatus(123) // 請求頭自動包含: Authorization: Bearer // 登出 apiClient.logout() // Token 自動從 localStorage 清除 ``` ### 文件上傳示例 ```typescript const handleUpload = async (files: File[]) => { try { const response = await apiClient.uploadFiles(files) // response: { batch_id: 1, files: [...] } setBatchId(response.batch_id) setFiles(response.files) } catch (error) { // 自動處理 401 錯誤並重定向到登錄頁 showError(error.response?.data?.detail) } } ``` ### 導出功能示例 ```typescript // 導出 PDF const handleDownloadPDF = async (fileId: number) => { const blob = await apiClient.exportPDF(fileId) const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `ocr-result-${fileId}.pdf` a.click() window.URL.revokeObjectURL(url) } // 導出規則管理 const rules = await apiClient.getExportRules() const newRule = await apiClient.createExportRule({ rule_name: 'My Rule', config_json: { /* ... */ } }) await apiClient.updateExportRule(rule.id, { rule_name: 'Updated' }) await apiClient.deleteExportRule(rule.id) ``` --- ## 5. 國際化使用示例 ### 在組件中使用翻譯 ```typescript import { useTranslation } from 'react-i18next' export default function UploadPage() { const { t } = useTranslation() return (

{t('upload.title')}

{t('upload.dragAndDrop')}

{/* 帶插值的翻譯 */}

{t('upload.fileCount', { count: 5 })}

{/* 會渲染: "已選擇 5 個檔案" */}
) } ``` ### i18n 初始化 (i18n/index.ts) ```typescript import i18n from 'i18next' import { initReactI18next } from 'react-i18next' import zhTW from './locales/zh-TW.json' i18n.use(initReactI18next).init({ resources: { 'zh-TW': { translation: zhTW, }, }, lng: 'zh-TW', fallbackLng: 'zh-TW', interpolation: { escapeValue: false, }, }) export default i18n ``` --- ## 6. 路由和保護示例 ### 受保護的路由 (App.tsx) ```typescript function ProtectedRoute({ children }: { children: React.ReactNode }) { const isAuthenticated = useAuthStore((state) => state.isAuthenticated) if (!isAuthenticated) { return } return <>{children} } function App() { return ( {/* 公開路由 */} } /> {/* 受保護的路由 */} } > } /> } /> } /> } /> } /> } /> {/* 全部匹配 */} } /> ) } ``` --- ## 7. 類型定義示例 ### API 類型 (types/api.ts) ```typescript // 認證 export interface LoginRequest { username: string password: string } export interface LoginResponse { access_token: string token_type: string } // 文件上傳 export interface UploadResponse { batch_id: number files: FileInfo[] } export interface FileInfo { id: number filename: string file_size: number format: string status: 'pending' | 'processing' | 'completed' | 'failed' } // OCR 結果 export interface OCRResult { file_id: number filename: string status: string markdown_content: string json_data: OCRJsonData confidence: number processing_time: number } export interface TextBlock { text: string confidence: number bbox: [number, number, number, number] position: number } // 導出規則 export interface ExportRule { id: number rule_name: string config_json: Record css_template?: string created_at: string } ``` --- ## 8. 最佳實踐 ### 1. 組件結構 ```typescript // 導入順序 import { useState } from 'react' import { useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { useQuery } from '@tanstack/react-query' // 內部導入 import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { useAuthStore } from '@/store/authStore' import { apiClient } from '@/services/api' // 組件定義 export default function MyPage() { // Hooks const { t } = useTranslation() const navigate = useNavigate() const [state, setState] = useState(null) // 查詢 const { data, isLoading } = useQuery({ queryKey: ['key'], queryFn: () => apiClient.getData(), }) // 狀態更新 const handleClick = () => { // ... } // 渲染 return (
{/* JSX */}
) } ``` ### 2. 錯誤處理 ```typescript const { toast } = useToast() const handleAction = async () => { try { const result = await apiClient.doSomething() toast({ title: t('success.title'), description: t('success.message'), variant: 'success', }) } catch (error: any) { const errorMessage = error.response?.data?.detail || t('errors.generic') toast({ title: t('error.title'), description: errorMessage, variant: 'destructive', }) } } ``` ### 3. 類名合併 ```typescript import { cn } from '@/lib/utils' // 條件類名 const buttonClass = cn( 'base-classes', { 'conditional-classes': isActive, 'other-classes': isDisabled, }, customClassName ) // 動態樣式 const cardClass = cn( 'p-4 rounded-lg', variant === 'outlined' && 'border border-input', variant === 'elevated' && 'shadow-lg' ) ``` ### 4. React Query 配置 ```typescript // 主入口 (main.tsx) const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 1, refetchOnWindowFocus: false, staleTime: 1000 * 60 * 5, // 5 分鐘 }, }, }) // 使用示例 const { data, isLoading, error } = useQuery({ queryKey: ['items', id], queryFn: () => apiClient.getItem(id), enabled: !!id, // 條件查詢 staleTime: 1000 * 60 * 10, // 10 分鐘不新鮮 }) ``` --- ## 9. 環境變數 ### .env 文件 ```env VITE_API_BASE_URL=http://localhost:12010 ``` ### 使用環境變數 ```typescript const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:12010' ``` --- ## 10. 常見開發流程 ### 創建新頁面 1. 在 `/src/pages` 創建 `NewPage.tsx` 2. 在 `App.tsx` 添加路由 3. 使用 Zustand 管理狀態 4. 使用 React Query 獲取數據 5. 使用 Tailwind CSS 樣式 6. 使用 i18next 添加文字 ### 添加新 API 接口 1. 在 `types/api.ts` 定義類型 2. 在 `services/api.ts` 添加方法 3. 在需要的地方使用 `apiClient.method()` 4. 使用 React Query 或直接調用 ### 修改樣式 1. 優先使用 Tailwind CSS 工具類 2. 使用 cn() 合併類名 3. 修改 `tailwind.config.js` 自定義主題 4. 在 `index.css` 添加全局樣式