fix: resolve 7 frontend-backend API inconsistencies and add comprehensive documentation
- Fixed LoginResponse: added expires_in field - Renamed format to file_format across FileInfo interface - Updated ProcessRequest: replaced confidence_threshold with detect_layout - Modified ProcessResponse: added message and total_files, removed task_id - Removed non-existent getTaskStatus API call - Fixed getOCRResult parameter from taskId to fileId - Commented out translation config APIs pending backend implementation Documentation: - Added API_REFERENCE.md: Complete API endpoint inventory - Added API_FIX_SUMMARY.md: Detailed before/after comparison of all fixes - Added FRONTEND_API.md: Frontend integration documentation - Added FRONTEND_QUICK_START.md: Quick start guide - Added FRONTEND_UPGRADE_SUMMARY.md: Upgrade summary 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
893
FRONTEND_API.md
Normal file
893
FRONTEND_API.md
Normal file
@@ -0,0 +1,893 @@
|
||||
# Tool_OCR Frontend API Documentation
|
||||
|
||||
> **Version**: 0.1.0
|
||||
> **Last Updated**: 2025-01-13
|
||||
> **Purpose**: Complete documentation of frontend architecture, component structure, API integration, and dependencies
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Project Overview](#project-overview)
|
||||
2. [Technology Stack](#technology-stack)
|
||||
3. [Component Architecture](#component-architecture)
|
||||
4. [Page → API Dependency Matrix](#page--api-dependency-matrix)
|
||||
5. [Component Tree Structure](#component-tree-structure)
|
||||
6. [State Management Strategy](#state-management-strategy)
|
||||
7. [Route Configuration](#route-configuration)
|
||||
8. [API Integration Patterns](#api-integration-patterns)
|
||||
9. [UI/UX Design System](#uiux-design-system)
|
||||
10. [Error Handling Patterns](#error-handling-patterns)
|
||||
11. [Deployment Configuration](#deployment-configuration)
|
||||
|
||||
---
|
||||
|
||||
## Project Overview
|
||||
|
||||
Tool_OCR 前端是一個基於 React 18 + Vite 的現代化 OCR 文件處理系統,提供企業級的使用者介面和體驗。
|
||||
|
||||
### Key Features
|
||||
|
||||
- **批次檔案上傳**: 支援拖放上傳,多檔案批次處理
|
||||
- **即時進度追蹤**: 使用輪詢機制顯示 OCR 處理進度
|
||||
- **結果預覽**: Markdown 和 JSON 雙格式預覽
|
||||
- **靈活匯出**: 支援 TXT、JSON、Excel、Markdown、PDF、ZIP 多種格式
|
||||
- **規則管理**: 可自訂匯出規則和 CSS 模板
|
||||
- **響應式設計**: 適配桌面和平板裝置
|
||||
|
||||
---
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Core Dependencies
|
||||
|
||||
```json
|
||||
{
|
||||
"@tanstack/react-query": "^5.90.7", // Server state management
|
||||
"react": "^19.2.0", // UI framework
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.9.5", // Routing
|
||||
"vite": "^7.2.2", // Build tool
|
||||
"typescript": "~5.9.3" // Type safety
|
||||
}
|
||||
```
|
||||
|
||||
### UI & Styling
|
||||
|
||||
```json
|
||||
{
|
||||
"tailwindcss": "^4.1.17", // CSS framework
|
||||
"class-variance-authority": "^0.7.0", // Component variants
|
||||
"clsx": "^2.1.1", // Class name utility
|
||||
"tailwind-merge": "^3.4.0", // Tailwind class merge
|
||||
"lucide-react": "^0.553.0" // Icon library
|
||||
}
|
||||
```
|
||||
|
||||
### State & Data
|
||||
|
||||
```json
|
||||
{
|
||||
"zustand": "^5.0.8", // Client state
|
||||
"axios": "^1.13.2", // HTTP client
|
||||
"react-dropzone": "^14.3.8", // File upload
|
||||
"react-markdown": "^9.0.1" // Markdown rendering
|
||||
}
|
||||
```
|
||||
|
||||
### Internationalization
|
||||
|
||||
```json
|
||||
{
|
||||
"i18next": "^25.6.2",
|
||||
"react-i18next": "^16.3.0"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Architecture
|
||||
|
||||
### Atomic Design Structure
|
||||
|
||||
```
|
||||
frontend/src/
|
||||
├── components/
|
||||
│ ├── ui/ # Atomic components (shadcn/ui)
|
||||
│ │ ├── button.tsx
|
||||
│ │ ├── card.tsx
|
||||
│ │ ├── input.tsx
|
||||
│ │ ├── label.tsx
|
||||
│ │ ├── select.tsx
|
||||
│ │ ├── badge.tsx
|
||||
│ │ ├── progress.tsx
|
||||
│ │ ├── alert.tsx
|
||||
│ │ ├── dialog.tsx
|
||||
│ │ ├── tabs.tsx
|
||||
│ │ ├── table.tsx
|
||||
│ │ └── toast.tsx
|
||||
│ ├── FileUpload.tsx # Drag-and-drop upload component
|
||||
│ ├── ResultsTable.tsx # OCR results display table
|
||||
│ ├── MarkdownPreview.tsx # Markdown content renderer
|
||||
│ └── Layout.tsx # Main app layout with sidebar
|
||||
├── pages/
|
||||
│ ├── LoginPage.tsx # Authentication
|
||||
│ ├── UploadPage.tsx # File upload and selection
|
||||
│ ├── ProcessingPage.tsx # OCR processing status
|
||||
│ ├── ResultsPage.tsx # Results viewing and preview
|
||||
│ ├── ExportPage.tsx # Export configuration and download
|
||||
│ └── SettingsPage.tsx # User settings and rules management
|
||||
├── store/
|
||||
│ ├── authStore.ts # Authentication state (Zustand)
|
||||
│ └── uploadStore.ts # Upload batch state (Zustand)
|
||||
├── services/
|
||||
│ └── api.ts # API client (Axios)
|
||||
├── types/
|
||||
│ └── api.ts # TypeScript type definitions
|
||||
├── lib/
|
||||
│ └── utils.ts # Utility functions
|
||||
├── i18n/
|
||||
│ └── index.ts # i18n configuration
|
||||
└── styles/
|
||||
└── index.css # Global styles and CSS variables
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Page → API Dependency Matrix
|
||||
|
||||
| Page/Component | API Endpoints Used | HTTP Method | Purpose | Polling |
|
||||
|----------------|-------------------|-------------|---------|---------|
|
||||
| **LoginPage** | `/api/v1/auth/login` | POST | User authentication | No |
|
||||
| **UploadPage** | `/api/v1/upload` | POST | Upload files for OCR | No |
|
||||
| **ProcessingPage** | `/api/v1/ocr/process` | POST | Start OCR processing | No |
|
||||
| | `/api/v1/batch/{batch_id}/status` | GET | Poll batch status | Yes (2s) |
|
||||
| **ResultsPage** | `/api/v1/batch/{batch_id}/status` | GET | Load completed files | No |
|
||||
| | `/api/v1/ocr/result/{file_id}` | GET | Get OCR result details | No |
|
||||
| | `/api/v1/export/pdf/{file_id}` | GET | Download PDF export | No |
|
||||
| **ExportPage** | `/api/v1/export` | POST | Export batch results | No |
|
||||
| | `/api/v1/export/rules` | GET | List export rules | No |
|
||||
| | `/api/v1/export/rules` | POST | Create new rule | No |
|
||||
| | `/api/v1/export/rules/{rule_id}` | PUT | Update existing rule | No |
|
||||
| | `/api/v1/export/rules/{rule_id}` | DELETE | Delete rule | No |
|
||||
| | `/api/v1/export/css-templates` | GET | List CSS templates | No |
|
||||
| **SettingsPage** | `/api/v1/export/rules` | GET | Manage export rules | No |
|
||||
|
||||
---
|
||||
|
||||
## Component Tree Structure
|
||||
|
||||
```
|
||||
App
|
||||
├── Router (React Router)
|
||||
│ ├── PublicRoute
|
||||
│ │ └── LoginPage
|
||||
│ │ ├── Form (username + password)
|
||||
│ │ ├── Button (submit)
|
||||
│ │ └── Alert (error display)
|
||||
│ └── ProtectedRoute (requires authentication)
|
||||
│ └── Layout
|
||||
│ ├── Sidebar
|
||||
│ │ ├── Logo
|
||||
│ │ ├── Navigation Links
|
||||
│ │ │ ├── UploadPage link
|
||||
│ │ │ ├── ProcessingPage link
|
||||
│ │ │ ├── ResultsPage link
|
||||
│ │ │ ├── ExportPage link
|
||||
│ │ │ └── SettingsPage link
|
||||
│ │ └── User Section + Logout
|
||||
│ ├── TopBar
|
||||
│ │ ├── SearchInput
|
||||
│ │ └── NotificationBell
|
||||
│ └── MainContent (Outlet)
|
||||
│ ├── UploadPage
|
||||
│ │ ├── FileUpload (react-dropzone)
|
||||
│ │ ├── FileList (selected files)
|
||||
│ │ └── UploadButton
|
||||
│ ├── ProcessingPage
|
||||
│ │ ├── ProgressBar
|
||||
│ │ ├── StatsCards (completed/processing/failed)
|
||||
│ │ ├── FileStatusList
|
||||
│ │ └── ActionButtons
|
||||
│ ├── ResultsPage
|
||||
│ │ ├── FileList (left sidebar)
|
||||
│ │ │ ├── SearchInput
|
||||
│ │ │ └── FileItems
|
||||
│ │ └── PreviewPanel (right)
|
||||
│ │ ├── StatsCards
|
||||
│ │ ├── Tabs (Markdown/JSON)
|
||||
│ │ ├── MarkdownPreview
|
||||
│ │ └── JSONViewer
|
||||
│ ├── ExportPage
|
||||
│ │ ├── FormatSelector
|
||||
│ │ ├── RuleSelector
|
||||
│ │ ├── CSSTemplateSelector
|
||||
│ │ ├── OptionsForm
|
||||
│ │ └── ExportButton
|
||||
│ └── SettingsPage
|
||||
│ ├── UserInfo
|
||||
│ ├── ExportRulesManager
|
||||
│ │ ├── RuleList
|
||||
│ │ ├── CreateRuleDialog
|
||||
│ │ ├── EditRuleDialog
|
||||
│ │ └── DeleteConfirmDialog
|
||||
│ └── SystemSettings
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Management Strategy
|
||||
|
||||
### Client State (Zustand)
|
||||
|
||||
**authStore.ts** - Authentication State
|
||||
```typescript
|
||||
interface AuthState {
|
||||
user: User | null
|
||||
isAuthenticated: boolean
|
||||
setUser: (user: User | null) => void
|
||||
logout: () => void
|
||||
}
|
||||
```
|
||||
|
||||
**uploadStore.ts** - Upload Batch State
|
||||
```typescript
|
||||
interface UploadState {
|
||||
batchId: number | null
|
||||
files: FileInfo[]
|
||||
uploadProgress: number
|
||||
setBatchId: (id: number) => void
|
||||
setFiles: (files: FileInfo[]) => void
|
||||
setUploadProgress: (progress: number) => void
|
||||
reset: () => void
|
||||
}
|
||||
```
|
||||
|
||||
### Server State (React Query)
|
||||
|
||||
- **Caching**: Automatic caching with stale-while-revalidate strategy
|
||||
- **Polling**: Automatic refetch for batch status every 2 seconds during processing
|
||||
- **Error Handling**: Built-in error retry and error state management
|
||||
- **Optimistic Updates**: For export rules CRUD operations
|
||||
|
||||
### Query Keys
|
||||
|
||||
```typescript
|
||||
// Batch status polling
|
||||
['batchStatus', batchId]
|
||||
|
||||
// OCR result for specific file
|
||||
['ocrResult', fileId]
|
||||
|
||||
// Export rules list
|
||||
['exportRules']
|
||||
|
||||
// CSS templates list
|
||||
['cssTemplates']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Route Configuration
|
||||
|
||||
| Route | Component | Access Level | Description | Protected |
|
||||
|-------|-----------|--------------|-------------|-----------|
|
||||
| `/login` | LoginPage | Public | User authentication | No |
|
||||
| `/` | Layout (redirect to /upload) | Private | Main layout wrapper | Yes |
|
||||
| `/upload` | UploadPage | Private | File upload interface | Yes |
|
||||
| `/processing` | ProcessingPage | Private | OCR processing status | Yes |
|
||||
| `/results` | ResultsPage | Private | View OCR results | Yes |
|
||||
| `/export` | ExportPage | Private | Export configuration | Yes |
|
||||
| `/settings` | SettingsPage | Private | User settings | Yes |
|
||||
|
||||
### Protected Route Implementation
|
||||
|
||||
```typescript
|
||||
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
||||
const isAuthenticated = useAuthStore((state) => state.isAuthenticated)
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Navigate to="/login" replace />
|
||||
}
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Integration Patterns
|
||||
|
||||
### API Client Configuration
|
||||
|
||||
**Base URL**: `http://localhost:12010/api/v1`
|
||||
|
||||
**Request Interceptor**: Adds JWT token to Authorization header
|
||||
|
||||
```typescript
|
||||
this.client.interceptors.request.use((config) => {
|
||||
if (this.token) {
|
||||
config.headers.Authorization = `Bearer ${this.token}`
|
||||
}
|
||||
return config
|
||||
})
|
||||
```
|
||||
|
||||
**Response Interceptor**: Handles 401 errors and redirects to login
|
||||
|
||||
```typescript
|
||||
this.client.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error: AxiosError<ApiError>) => {
|
||||
if (error.response?.status === 401) {
|
||||
this.clearToken()
|
||||
window.location.href = '/login'
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
```typescript
|
||||
// 1. Login
|
||||
const response = await apiClient.login({ username, password })
|
||||
// Response: { access_token, token_type, expires_in }
|
||||
|
||||
// 2. Store token
|
||||
localStorage.setItem('auth_token', response.access_token)
|
||||
|
||||
// 3. Set user in store
|
||||
setUser({ id: 1, username })
|
||||
|
||||
// 4. Navigate to /upload
|
||||
navigate('/upload')
|
||||
```
|
||||
|
||||
### File Upload Flow
|
||||
|
||||
```typescript
|
||||
// 1. Prepare FormData
|
||||
const formData = new FormData()
|
||||
files.forEach((file) => formData.append('files', file))
|
||||
|
||||
// 2. Upload files
|
||||
const response = await apiClient.uploadFiles(files)
|
||||
// Response: { batch_id, files: FileInfo[] }
|
||||
|
||||
// 3. Store batch info
|
||||
setBatchId(response.batch_id)
|
||||
setFiles(response.files)
|
||||
|
||||
// 4. Navigate to /processing
|
||||
navigate('/processing')
|
||||
```
|
||||
|
||||
### OCR Processing Flow
|
||||
|
||||
```typescript
|
||||
// 1. Start OCR processing
|
||||
await apiClient.processOCR({ batch_id, lang: 'ch', detect_layout: true })
|
||||
// Response: { message, batch_id, total_files, status }
|
||||
|
||||
// 2. Poll batch status every 2 seconds
|
||||
const { data: batchStatus } = useQuery({
|
||||
queryKey: ['batchStatus', batchId],
|
||||
queryFn: () => apiClient.getBatchStatus(batchId),
|
||||
refetchInterval: (query) => {
|
||||
const status = query.state.data?.batch.status
|
||||
if (status === 'completed' || status === 'failed') return false
|
||||
return 2000 // Poll every 2 seconds
|
||||
},
|
||||
})
|
||||
|
||||
// 3. Auto-redirect when completed
|
||||
useEffect(() => {
|
||||
if (batchStatus?.batch.status === 'completed') {
|
||||
navigate('/results')
|
||||
}
|
||||
}, [batchStatus?.batch.status])
|
||||
```
|
||||
|
||||
### Results Viewing Flow
|
||||
|
||||
```typescript
|
||||
// 1. Load batch status
|
||||
const { data: batchStatus } = useQuery({
|
||||
queryKey: ['batchStatus', batchId],
|
||||
queryFn: () => apiClient.getBatchStatus(batchId),
|
||||
})
|
||||
|
||||
// 2. Select a file
|
||||
setSelectedFileId(fileId)
|
||||
|
||||
// 3. Load OCR result for selected file
|
||||
const { data: ocrResult } = useQuery({
|
||||
queryKey: ['ocrResult', selectedFileId],
|
||||
queryFn: () => apiClient.getOCRResult(selectedFileId),
|
||||
enabled: !!selectedFileId,
|
||||
})
|
||||
|
||||
// 4. Display in Markdown or JSON format
|
||||
<Tabs>
|
||||
<TabsContent value="markdown">
|
||||
<ReactMarkdown>{ocrResult.markdown_content}</ReactMarkdown>
|
||||
</TabsContent>
|
||||
<TabsContent value="json">
|
||||
<pre>{JSON.stringify(ocrResult.json_data, null, 2)}</pre>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
### Export Flow
|
||||
|
||||
```typescript
|
||||
// 1. Select export format and options
|
||||
const exportData = {
|
||||
batch_id: batchId,
|
||||
format: 'pdf',
|
||||
rule_id: selectedRuleId,
|
||||
css_template: 'academic',
|
||||
options: { include_metadata: true }
|
||||
}
|
||||
|
||||
// 2. Request export
|
||||
const blob = await apiClient.exportResults(exportData)
|
||||
|
||||
// 3. Trigger download
|
||||
downloadBlob(blob, `ocr-results-${batchId}.pdf`)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UI/UX Design System
|
||||
|
||||
### Color Palette (CSS Variables)
|
||||
|
||||
```css
|
||||
/* Primary - Professional Blue */
|
||||
--primary: 217 91% 60%; /* #3b82f6 */
|
||||
--primary-foreground: 0 0% 100%;
|
||||
|
||||
/* Secondary - Gray-Blue */
|
||||
--secondary: 220 15% 95%;
|
||||
--secondary-foreground: 220 15% 25%;
|
||||
|
||||
/* Accent - Vibrant Teal */
|
||||
--accent: 173 80% 50%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
|
||||
/* Success */
|
||||
--success: 142 72% 45%; /* #16a34a */
|
||||
--success-foreground: 0 0% 100%;
|
||||
|
||||
/* Destructive */
|
||||
--destructive: 0 85% 60%; /* #ef4444 */
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
|
||||
/* Warning */
|
||||
--warning: 38 92% 50%;
|
||||
--warning-foreground: 0 0% 100%;
|
||||
|
||||
/* Background */
|
||||
--background: 220 15% 97%; /* #fafafa */
|
||||
--card: 0 0% 100%; /* #ffffff */
|
||||
--sidebar: 220 25% 12%; /* Dark blue-gray */
|
||||
|
||||
/* Borders */
|
||||
--border: 220 13% 88%;
|
||||
--radius: 0.5rem;
|
||||
```
|
||||
|
||||
### Typography
|
||||
|
||||
- **Font Family**: System font stack (native)
|
||||
- **Page Title**: 1.875rem (30px), font-weight: 700
|
||||
- **Section Title**: 1.125rem (18px), font-weight: 600
|
||||
- **Body Text**: 0.875rem (14px), font-weight: 400
|
||||
- **Small Text**: 0.75rem (12px)
|
||||
|
||||
### Spacing Scale
|
||||
|
||||
```css
|
||||
--spacing-xs: 0.25rem; /* 4px */
|
||||
--spacing-sm: 0.5rem; /* 8px */
|
||||
--spacing-md: 1rem; /* 16px */
|
||||
--spacing-lg: 1.5rem; /* 24px */
|
||||
--spacing-xl: 2rem; /* 32px */
|
||||
```
|
||||
|
||||
### Component Variants
|
||||
|
||||
**Button Variants**:
|
||||
- `default`: Primary blue background
|
||||
- `outline`: Border only
|
||||
- `secondary`: Muted background
|
||||
- `destructive`: Red for delete actions
|
||||
- `ghost`: No background, hover effect
|
||||
|
||||
**Alert Variants**:
|
||||
- `default`: Neutral gray
|
||||
- `info`: Blue
|
||||
- `success`: Green
|
||||
- `warning`: Yellow
|
||||
- `destructive`: Red
|
||||
|
||||
**Badge Variants**:
|
||||
- `default`: Gray
|
||||
- `success`: Green
|
||||
- `warning`: Yellow
|
||||
- `destructive`: Red
|
||||
- `secondary`: Muted
|
||||
|
||||
### Responsive Breakpoints
|
||||
|
||||
```typescript
|
||||
// Tailwind breakpoints
|
||||
sm: '640px', // Mobile landscape
|
||||
md: '768px', // Tablet
|
||||
lg: '1024px', // Desktop (primary support)
|
||||
xl: '1280px', // Large desktop
|
||||
2xl: '1536px' // Extra large
|
||||
```
|
||||
|
||||
**Primary Support**: Desktop (>= 1024px)
|
||||
**Secondary Support**: Tablet (768px - 1023px)
|
||||
**Optional**: Mobile (< 768px)
|
||||
|
||||
---
|
||||
|
||||
## Error Handling Patterns
|
||||
|
||||
### Global Error Boundary
|
||||
|
||||
```typescript
|
||||
class ErrorBoundary extends Component<Props, State> {
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { hasError: true, error }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
console.error('Uncaught error:', error, errorInfo)
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return <ErrorFallbackUI error={this.state.error} />
|
||||
}
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API Error Handling
|
||||
|
||||
```typescript
|
||||
try {
|
||||
await apiClient.uploadFiles(files)
|
||||
} catch (err: any) {
|
||||
const errorDetail = err.response?.data?.detail
|
||||
|
||||
toast({
|
||||
title: t('upload.uploadError'),
|
||||
description: Array.isArray(errorDetail)
|
||||
? errorDetail.map(e => e.msg || e.message).join(', ')
|
||||
: errorDetail || t('errors.networkError'),
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Form Validation
|
||||
|
||||
```typescript
|
||||
// Client-side validation
|
||||
if (selectedFiles.length === 0) {
|
||||
toast({
|
||||
title: t('errors.validationError'),
|
||||
description: '請選擇至少一個檔案',
|
||||
variant: 'destructive',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Backend validation errors
|
||||
if (err.response?.status === 422) {
|
||||
const errors = err.response.data.detail
|
||||
// Display validation errors to user
|
||||
}
|
||||
```
|
||||
|
||||
### Loading States
|
||||
|
||||
```typescript
|
||||
// Query loading state
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['batchStatus', batchId],
|
||||
queryFn: () => apiClient.getBatchStatus(batchId),
|
||||
})
|
||||
|
||||
if (isLoading) return <LoadingSpinner />
|
||||
if (error) return <ErrorAlert error={error} />
|
||||
if (!data) return <EmptyState />
|
||||
|
||||
// Mutation loading state
|
||||
const mutation = useMutation({
|
||||
mutationFn: apiClient.uploadFiles,
|
||||
onSuccess: () => { /* success */ },
|
||||
onError: () => { /* error */ },
|
||||
})
|
||||
|
||||
<Button disabled={mutation.isPending}>
|
||||
{mutation.isPending ? <Loader2 className="animate-spin" /> : '上傳'}
|
||||
</Button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.production
|
||||
VITE_API_BASE_URL=http://localhost:12010
|
||||
VITE_APP_NAME=Tool_OCR
|
||||
VITE_APP_VERSION=0.1.0
|
||||
```
|
||||
|
||||
### Build Configuration
|
||||
|
||||
**vite.config.ts**:
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 12011,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:12010',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
sourcemap: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor: ['react', 'react-dom', 'react-router-dom'],
|
||||
ui: ['@tanstack/react-query', 'zustand', 'lucide-react'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Build Commands
|
||||
|
||||
```bash
|
||||
# Development
|
||||
npm run dev
|
||||
|
||||
# Production build
|
||||
npm run build
|
||||
|
||||
# Preview production build
|
||||
npm run preview
|
||||
```
|
||||
|
||||
### Nginx Configuration
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name tool-ocr.example.com;
|
||||
root /path/to/Tool_OCR/frontend/dist;
|
||||
|
||||
# Frontend static files
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# API reverse proxy
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:12010;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
# Static assets caching
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Code Splitting
|
||||
|
||||
- **Vendor Bundle**: React, React Router, React Query (separate chunk)
|
||||
- **UI Bundle**: Zustand, Lucide React, UI components
|
||||
- **Route-based Splitting**: Lazy load pages with `React.lazy()`
|
||||
|
||||
### Caching Strategy
|
||||
|
||||
- **React Query Cache**: 5 minutes stale time for most queries
|
||||
- **Polling Interval**: 2 seconds during OCR processing
|
||||
- **Infinite Cache**: Export rules (rarely change)
|
||||
|
||||
### Asset Optimization
|
||||
|
||||
- **Images**: Convert to WebP format, use appropriate sizes
|
||||
- **Fonts**: System font stack (no custom fonts)
|
||||
- **Icons**: Lucide React (tree-shakeable)
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Component Testing (Planned)
|
||||
|
||||
```typescript
|
||||
// Example: UploadPage.test.tsx
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import { UploadPage } from '@/pages/UploadPage'
|
||||
|
||||
describe('UploadPage', () => {
|
||||
it('should display file upload area', () => {
|
||||
render(<UploadPage />)
|
||||
expect(screen.getByText(/拖放檔案/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should allow file selection', async () => {
|
||||
render(<UploadPage />)
|
||||
const file = new File(['content'], 'test.pdf', { type: 'application/pdf' })
|
||||
// Test file upload
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### API Integration Testing
|
||||
|
||||
- **Mock API Responses**: Use MSW (Mock Service Worker)
|
||||
- **Error Scenarios**: Test 401, 404, 500 responses
|
||||
- **Loading States**: Test skeleton/spinner display
|
||||
|
||||
---
|
||||
|
||||
## Accessibility Standards
|
||||
|
||||
### WCAG 2.1 AA Compliance
|
||||
|
||||
- **Keyboard Navigation**: All interactive elements accessible via keyboard
|
||||
- **Focus Indicators**: Visible focus states on all inputs and buttons
|
||||
- **ARIA Labels**: Proper labels for screen readers
|
||||
- **Color Contrast**: Minimum 4.5:1 ratio for text
|
||||
- **Alt Text**: All images have descriptive alt attributes
|
||||
|
||||
### Semantic HTML
|
||||
|
||||
```typescript
|
||||
// Use semantic elements
|
||||
<nav> // Navigation
|
||||
<main> // Main content
|
||||
<aside> // Sidebar
|
||||
<article> // Independent content
|
||||
<section> // Grouped content
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
### Minimum Supported Versions
|
||||
|
||||
- **Chrome**: 90+
|
||||
- **Firefox**: 88+
|
||||
- **Edge**: 90+
|
||||
- **Safari**: 14+
|
||||
|
||||
### Polyfills Required
|
||||
|
||||
- None (modern build target: ES2020)
|
||||
|
||||
---
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
# 1. Install dependencies
|
||||
npm install
|
||||
|
||||
# 2. Start dev server
|
||||
npm run dev
|
||||
# Frontend: http://localhost:12011
|
||||
# API Proxy: http://localhost:12011/api -> http://localhost:12010/api
|
||||
|
||||
# 3. Build for production
|
||||
npm run build
|
||||
|
||||
# 4. Preview production build
|
||||
npm run preview
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
- **Formatter**: Prettier (automatic on save)
|
||||
- **Linter**: ESLint
|
||||
- **Type Checking**: TypeScript strict mode
|
||||
|
||||
---
|
||||
|
||||
## Known Issues & Limitations
|
||||
|
||||
### Current Limitations
|
||||
|
||||
1. **No Real-time WebSocket**: Uses HTTP polling for progress updates
|
||||
2. **No Offline Support**: Requires active internet connection
|
||||
3. **No Mobile Optimization**: Primarily designed for desktop/tablet
|
||||
4. **Translation Feature Stub**: Planned for Phase 5
|
||||
5. **File Size Limit**: Frontend validates 50MB per file, backend may differ
|
||||
|
||||
### Future Improvements
|
||||
|
||||
- [ ] Implement WebSocket for real-time updates
|
||||
- [ ] Add dark mode toggle
|
||||
- [ ] Mobile responsive design
|
||||
- [ ] Implement translation feature
|
||||
- [ ] Add E2E tests with Playwright
|
||||
- [ ] PWA support for offline capability
|
||||
|
||||
---
|
||||
|
||||
## Maintenance & Updates
|
||||
|
||||
### Update Checklist
|
||||
|
||||
When updating API contracts:
|
||||
1. Update TypeScript types in `@/types/api.ts`
|
||||
2. Update API client methods in `@/services/api.ts`
|
||||
3. Update this documentation (FRONTEND_API.md)
|
||||
4. Update corresponding page components
|
||||
5. Test integration thoroughly
|
||||
|
||||
### Dependency Updates
|
||||
|
||||
```bash
|
||||
# Check for updates
|
||||
npm outdated
|
||||
|
||||
# Update dependencies
|
||||
npm update
|
||||
|
||||
# Update to latest (breaking changes possible)
|
||||
npm install <package>@latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contact & Support
|
||||
|
||||
**Frontend Developer**: Claude Code
|
||||
**Documentation Version**: 0.1.0
|
||||
**Last Updated**: 2025-01-13
|
||||
|
||||
For API questions, refer to:
|
||||
- `API_REFERENCE.md` - Complete API documentation
|
||||
- `backend_api.md` - Backend implementation details
|
||||
- FastAPI Swagger UI: `http://localhost:12010/docs`
|
||||
|
||||
---
|
||||
|
||||
**End of Documentation**
|
||||
Reference in New Issue
Block a user