feat: Docker化部署 - 單容器架構轉換
將 Tool_OCR 從 macOS conda 環境轉換為 Docker 單容器部署方案。 前後端整合於同一容器,通過 Nginx 反向代理,僅對外暴露單一端口。 ## 新增功能 - Docker 單容器架構(Frontend + Backend + Nginx) - 多階段構建優化鏡像大小 - Supervisor 進程管理 - 健康檢查機制 - 完整部署文檔 ## 技術細節 - 對外端口:12015(原 12010 已被佔用) - 內部架構:Nginx(12015) → FastAPI(8000) - 前端靜態文件由 Nginx 直接服務 - API 請求通過 Nginx 反向代理 ## 系統依賴完善 - libmagic1:文件類型檢測 - LibreOffice:Office 文檔轉換 - paddlex[ocr]:PP-StructureV3 版面分析 - 中日韓字體支援 ## 配置調整 - 環境變數路徑:macOS 路徑 → 容器絕對路徑 - 前端 API URL:修正為統一端口 12015 - Pip 安裝:延長超時至 600 秒,重試 5 次 - CRLF 轉換:自動處理 Windows 換行符 ## 清理 - 移除臨時文檔(API_FIX_SUMMARY.md 等 7 個文檔) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
87
.dockerignore
Normal file
87
.dockerignore
Normal file
@@ -0,0 +1,87 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
*.egg
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# Node
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.npm
|
||||
.yarn
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# Data directories (will be mounted as volumes)
|
||||
data/
|
||||
uploads/
|
||||
storage/
|
||||
models/
|
||||
|
||||
# Backend specific
|
||||
backend/uploads/
|
||||
backend/storage/
|
||||
backend/models/
|
||||
backend/logs/
|
||||
backend/__pycache__/
|
||||
backend/*.egg-info/
|
||||
|
||||
# Frontend specific
|
||||
frontend/node_modules/
|
||||
frontend/dist/
|
||||
frontend/.vite/
|
||||
frontend/.cache/
|
||||
|
||||
# Documentation (not needed in container)
|
||||
*.md
|
||||
!README.md
|
||||
docs/
|
||||
demo_docs/
|
||||
|
||||
# Claude and OpenSpec
|
||||
.claude/
|
||||
openspec/
|
||||
|
||||
# OS
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
temp/
|
||||
tmp/
|
||||
52
.env
52
.env
@@ -1,5 +1,5 @@
|
||||
# Tool_OCR - Environment Configuration Template
|
||||
# Copy this file to .env and fill in your actual values
|
||||
# Tool_OCR - Docker Environment Configuration
|
||||
# Copy this file to .env when deploying with Docker
|
||||
|
||||
# ===== Database Configuration =====
|
||||
MYSQL_HOST=mysql.theaken.com
|
||||
@@ -9,18 +9,17 @@ MYSQL_PASSWORD=WLeSCi0yhtc7
|
||||
MYSQL_DATABASE=db_A060
|
||||
|
||||
# ===== Application Configuration =====
|
||||
# Server ports
|
||||
BACKEND_PORT=12010
|
||||
FRONTEND_PORT=12011
|
||||
# External port (exposed to host)
|
||||
FRONTEND_PORT=12010
|
||||
|
||||
# Security
|
||||
# Security (IMPORTANT: Change SECRET_KEY in production!)
|
||||
SECRET_KEY=your-secret-key-here-please-change-this-to-random-string
|
||||
ALGORITHM=HS256
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES=1440
|
||||
|
||||
# ===== OCR Configuration =====
|
||||
# PaddleOCR model directory
|
||||
PADDLEOCR_MODEL_DIR=./models/paddleocr
|
||||
# PaddleOCR model directory (inside container)
|
||||
PADDLEOCR_MODEL_DIR=/app/backend/models/paddleocr
|
||||
# Supported languages (comma-separated)
|
||||
OCR_LANGUAGES=ch,en,japan,korean
|
||||
# Default confidence threshold
|
||||
@@ -33,24 +32,24 @@ MAX_OCR_WORKERS=4
|
||||
MAX_UPLOAD_SIZE=52428800
|
||||
# Allowed file extensions (comma-separated)
|
||||
ALLOWED_EXTENSIONS=png,jpg,jpeg,pdf,bmp,tiff,doc,docx,ppt,pptx
|
||||
# Upload directories
|
||||
UPLOAD_DIR=./uploads
|
||||
TEMP_DIR=./uploads/temp
|
||||
PROCESSED_DIR=./uploads/processed
|
||||
IMAGES_DIR=./uploads/images
|
||||
# Upload directories (inside container)
|
||||
UPLOAD_DIR=/app/backend/uploads
|
||||
TEMP_DIR=/app/backend/uploads/temp
|
||||
PROCESSED_DIR=/app/backend/uploads/processed
|
||||
IMAGES_DIR=/app/backend/uploads/images
|
||||
|
||||
# ===== Export Configuration =====
|
||||
# Storage directories
|
||||
STORAGE_DIR=./storage
|
||||
MARKDOWN_DIR=./storage/markdown
|
||||
JSON_DIR=./storage/json
|
||||
EXPORTS_DIR=./storage/exports
|
||||
# Storage directories (inside container)
|
||||
STORAGE_DIR=/app/backend/storage
|
||||
MARKDOWN_DIR=/app/backend/storage/markdown
|
||||
JSON_DIR=/app/backend/storage/json
|
||||
EXPORTS_DIR=/app/backend/storage/exports
|
||||
|
||||
# ===== PDF Generation Configuration =====
|
||||
# Pandoc path (auto-detected if installed via brew)
|
||||
PANDOC_PATH=/opt/homebrew/bin/pandoc
|
||||
# WeasyPrint font directory
|
||||
FONT_DIR=/System/Library/Fonts
|
||||
# Pandoc path (inside container)
|
||||
PANDOC_PATH=/usr/bin/pandoc
|
||||
# Font directory (inside container)
|
||||
FONT_DIR=/usr/share/fonts
|
||||
# Default PDF page size
|
||||
PDF_PAGE_SIZE=A4
|
||||
# Default PDF margins (mm)
|
||||
@@ -64,8 +63,8 @@ PDF_MARGIN_RIGHT=20
|
||||
ENABLE_TRANSLATION=false
|
||||
# Translation engine: offline (argostranslate) or api (future)
|
||||
TRANSLATION_ENGINE=offline
|
||||
# Argostranslate models directory
|
||||
ARGOSTRANSLATE_MODELS_DIR=./models/argostranslate
|
||||
# Argostranslate models directory (inside container)
|
||||
ARGOSTRANSLATE_MODELS_DIR=/app/backend/models/argostranslate
|
||||
|
||||
# ===== Background Tasks Configuration =====
|
||||
# Task queue type: memory (default) or redis (future)
|
||||
@@ -75,8 +74,9 @@ TASK_QUEUE_TYPE=memory
|
||||
|
||||
# ===== CORS Configuration =====
|
||||
# Allowed origins (comma-separated, * for all)
|
||||
CORS_ORIGINS=http://localhost:12011,http://127.0.0.1:12011
|
||||
# For Docker, use the external URL
|
||||
CORS_ORIGINS=http://localhost:12010,http://127.0.0.1:12010
|
||||
|
||||
# ===== Logging Configuration =====
|
||||
LOG_LEVEL=INFO
|
||||
LOG_FILE=./logs/app.log
|
||||
LOG_FILE=/app/backend/logs/app.log
|
||||
|
||||
82
.env.docker
Normal file
82
.env.docker
Normal file
@@ -0,0 +1,82 @@
|
||||
# Tool_OCR - Docker Environment Configuration
|
||||
# Copy this file to .env when deploying with Docker
|
||||
|
||||
# ===== Database Configuration =====
|
||||
MYSQL_HOST=mysql.theaken.com
|
||||
MYSQL_PORT=33306
|
||||
MYSQL_USER=A060
|
||||
MYSQL_PASSWORD=WLeSCi0yhtc7
|
||||
MYSQL_DATABASE=db_A060
|
||||
|
||||
# ===== Application Configuration =====
|
||||
# External port (exposed to host)
|
||||
FRONTEND_PORT=12015
|
||||
|
||||
# Security (IMPORTANT: Change SECRET_KEY in production!)
|
||||
SECRET_KEY=your-secret-key-here-please-change-this-to-random-string
|
||||
ALGORITHM=HS256
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES=1440
|
||||
|
||||
# ===== OCR Configuration =====
|
||||
# PaddleOCR model directory (inside container)
|
||||
PADDLEOCR_MODEL_DIR=/app/backend/models/paddleocr
|
||||
# Supported languages (comma-separated)
|
||||
OCR_LANGUAGES=ch,en,japan,korean
|
||||
# Default confidence threshold
|
||||
OCR_CONFIDENCE_THRESHOLD=0.5
|
||||
# Maximum concurrent OCR workers
|
||||
MAX_OCR_WORKERS=4
|
||||
|
||||
# ===== File Upload Configuration =====
|
||||
# Maximum file size in bytes (50MB default)
|
||||
MAX_UPLOAD_SIZE=52428800
|
||||
# Allowed file extensions (comma-separated)
|
||||
ALLOWED_EXTENSIONS=png,jpg,jpeg,pdf,bmp,tiff,doc,docx,ppt,pptx
|
||||
# Upload directories (inside container)
|
||||
UPLOAD_DIR=/app/backend/uploads
|
||||
TEMP_DIR=/app/backend/uploads/temp
|
||||
PROCESSED_DIR=/app/backend/uploads/processed
|
||||
IMAGES_DIR=/app/backend/uploads/images
|
||||
|
||||
# ===== Export Configuration =====
|
||||
# Storage directories (inside container)
|
||||
STORAGE_DIR=/app/backend/storage
|
||||
MARKDOWN_DIR=/app/backend/storage/markdown
|
||||
JSON_DIR=/app/backend/storage/json
|
||||
EXPORTS_DIR=/app/backend/storage/exports
|
||||
|
||||
# ===== PDF Generation Configuration =====
|
||||
# Pandoc path (inside container)
|
||||
PANDOC_PATH=/usr/bin/pandoc
|
||||
# Font directory (inside container)
|
||||
FONT_DIR=/usr/share/fonts
|
||||
# Default PDF page size
|
||||
PDF_PAGE_SIZE=A4
|
||||
# Default PDF margins (mm)
|
||||
PDF_MARGIN_TOP=20
|
||||
PDF_MARGIN_BOTTOM=20
|
||||
PDF_MARGIN_LEFT=20
|
||||
PDF_MARGIN_RIGHT=20
|
||||
|
||||
# ===== Translation Configuration (Reserved) =====
|
||||
# Enable translation feature (reserved for future)
|
||||
ENABLE_TRANSLATION=false
|
||||
# Translation engine: offline (argostranslate) or api (future)
|
||||
TRANSLATION_ENGINE=offline
|
||||
# Argostranslate models directory (inside container)
|
||||
ARGOSTRANSLATE_MODELS_DIR=/app/backend/models/argostranslate
|
||||
|
||||
# ===== Background Tasks Configuration =====
|
||||
# Task queue type: memory (default) or redis (future)
|
||||
TASK_QUEUE_TYPE=memory
|
||||
# Redis URL (if using redis)
|
||||
# REDIS_URL=redis://localhost:6379/0
|
||||
|
||||
# ===== CORS Configuration =====
|
||||
# Allowed origins (comma-separated, * for all)
|
||||
# For Docker, use the external URL
|
||||
CORS_ORIGINS=http://localhost:12015,http://127.0.0.1:12015
|
||||
|
||||
# ===== Logging Configuration =====
|
||||
LOG_LEVEL=INFO
|
||||
LOG_FILE=/app/backend/logs/app.log
|
||||
@@ -1,368 +0,0 @@
|
||||
# API 前後端不一致問題修正總結
|
||||
|
||||
## 修正日期
|
||||
2025-01-13
|
||||
|
||||
## 修正概覽
|
||||
|
||||
本次修正針對前後端 API 整合的 6 個主要問題進行了全面修復,確保前後端資料結構完全一致。
|
||||
|
||||
---
|
||||
|
||||
## 已修正的問題
|
||||
|
||||
### ✅ 問題 1: 登入回應結構不一致
|
||||
|
||||
**修正內容**:
|
||||
- 在前端 `LoginResponse` 型別新增 `expires_in` 欄位
|
||||
|
||||
**修改檔案**:
|
||||
- [frontend/src/types/api.ts:12-16](frontend/src/types/api.ts#L12-L16)
|
||||
|
||||
**變更**:
|
||||
```typescript
|
||||
// 修正前
|
||||
export interface LoginResponse {
|
||||
access_token: string
|
||||
token_type: string
|
||||
}
|
||||
|
||||
// 修正後
|
||||
export interface LoginResponse {
|
||||
access_token: string
|
||||
token_type: string
|
||||
expires_in: number // Token expiration time in seconds
|
||||
}
|
||||
```
|
||||
|
||||
**影響**:
|
||||
- ✅ 前端現在可以接收 Token 過期時間
|
||||
- ✅ 可實作 Token 自動續期功能
|
||||
- ✅ 可提前提醒使用者 Token 即將過期
|
||||
|
||||
---
|
||||
|
||||
### ✅ 問題 2: OCR 任務狀態 API 不存在
|
||||
|
||||
**修正內容**:
|
||||
- 移除前端的 `getTaskStatus()` 方法
|
||||
- 統一使用 `getBatchStatus()` 進行批次狀態追蹤
|
||||
- 從 import 中移除 `TaskStatus` 型別
|
||||
|
||||
**修改檔案**:
|
||||
- [frontend/src/services/api.ts:3-18](frontend/src/services/api.ts#L3-L18) - 移除 TaskStatus import
|
||||
- [frontend/src/services/api.ts:153-160](frontend/src/services/api.ts#L153-L160) - 移除 getTaskStatus 方法
|
||||
|
||||
**變更**:
|
||||
```typescript
|
||||
// 修正前
|
||||
import type {
|
||||
// ... 其他型別
|
||||
TaskStatus,
|
||||
// ...
|
||||
}
|
||||
|
||||
async getTaskStatus(taskId: string): Promise<TaskStatus> {
|
||||
const response = await this.client.get<TaskStatus>(`/ocr/status/${taskId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
// 修正後
|
||||
// TaskStatus 已從 import 移除
|
||||
// getTaskStatus 方法已刪除
|
||||
// 統一使用 getBatchStatus() 追蹤狀態
|
||||
```
|
||||
|
||||
**影響**:
|
||||
- ✅ 避免呼叫不存在的 API 端點 (404 錯誤)
|
||||
- ✅ 統一使用批次狀態管理
|
||||
- ✅ 簡化狀態追蹤邏輯
|
||||
|
||||
---
|
||||
|
||||
### ✅ 問題 3: OCR 處理請求/回應模型不符
|
||||
|
||||
**修正內容**:
|
||||
- 修改 `ProcessRequest` 型別,改用 `detect_layout` 取代 `confidence_threshold`
|
||||
- 修改 `ProcessResponse` 型別,新增 `message` 和 `total_files` 欄位,移除 `task_id`
|
||||
|
||||
**修改檔案**:
|
||||
- [frontend/src/types/api.ts:37-50](frontend/src/types/api.ts#L37-L50)
|
||||
|
||||
**變更**:
|
||||
```typescript
|
||||
// 修正前
|
||||
export interface ProcessRequest {
|
||||
batch_id: number
|
||||
lang?: string
|
||||
confidence_threshold?: number // ❌ 後端不支援此欄位
|
||||
}
|
||||
|
||||
export interface ProcessResponse {
|
||||
task_id: string // ❌ 後端不回傳此欄位
|
||||
batch_id: number
|
||||
status: string
|
||||
}
|
||||
|
||||
// 修正後
|
||||
export interface ProcessRequest {
|
||||
batch_id: number
|
||||
lang?: string
|
||||
detect_layout?: boolean // ✅ 與後端一致
|
||||
}
|
||||
|
||||
export interface ProcessResponse {
|
||||
message: string // ✅ 新增
|
||||
batch_id: number
|
||||
total_files: number // ✅ 新增
|
||||
status: string
|
||||
// task_id 已移除
|
||||
}
|
||||
```
|
||||
|
||||
**影響**:
|
||||
- ✅ 前端可正確傳遞版面偵測參數給後端
|
||||
- ✅ 前端可接收處理訊息和檔案總數
|
||||
- ✅ 避免型別檢查錯誤
|
||||
- ✅ 避免驗證失敗
|
||||
|
||||
---
|
||||
|
||||
### ✅ 問題 4: 上傳檔案欄位命名不一致
|
||||
|
||||
**修正內容**:
|
||||
- 將 `FileInfo.format` 改名為 `FileInfo.file_format`
|
||||
|
||||
**修改檔案**:
|
||||
- [frontend/src/types/api.ts:28-35](frontend/src/types/api.ts#L28-L35)
|
||||
|
||||
**變更**:
|
||||
```typescript
|
||||
// 修正前
|
||||
export interface FileInfo {
|
||||
id: number
|
||||
filename: string
|
||||
file_size: number
|
||||
format: string // ❌ 與後端欄位名稱不同
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||
}
|
||||
|
||||
// 修正後
|
||||
export interface FileInfo {
|
||||
id: number
|
||||
filename: string
|
||||
file_size: number
|
||||
file_format: string // ✅ 與後端一致
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||
}
|
||||
```
|
||||
|
||||
**影響**:
|
||||
- ✅ 前端可直接使用後端回傳的 `file_format` 欄位
|
||||
- ✅ 無需額外的欄位映射或轉換
|
||||
- ✅ UI 可正確顯示檔案格式
|
||||
|
||||
---
|
||||
|
||||
### ✅ 問題 5: CSS 模板清單缺少 filename
|
||||
|
||||
**修正內容**:
|
||||
- 移除 `CSSTemplate.filename` 欄位定義
|
||||
- 改用 `name` 作為模板識別碼
|
||||
|
||||
**修改檔案**:
|
||||
- [frontend/src/types/api.ts:135-139](frontend/src/types/api.ts#L135-L139)
|
||||
|
||||
**變更**:
|
||||
```typescript
|
||||
// 修正前
|
||||
export interface CSSTemplate {
|
||||
name: string
|
||||
description: string
|
||||
filename: string // ❌ 後端實際不回傳此欄位
|
||||
}
|
||||
|
||||
// 修正後
|
||||
export interface CSSTemplate {
|
||||
name: string
|
||||
description: string
|
||||
// filename 已移除,使用 name 作為識別碼
|
||||
}
|
||||
```
|
||||
|
||||
**影響**:
|
||||
- ✅ 避免 `filename` 為 undefined 的問題
|
||||
- ✅ 使用 `name` 作為 `<option>` 的 key/value
|
||||
- ✅ 與後端實際回傳結構一致
|
||||
|
||||
---
|
||||
|
||||
### ✅ 問題 6: 翻譯設定端點未實作
|
||||
|
||||
**修正內容**:
|
||||
- 註解 `getTranslationConfigs()` 方法
|
||||
- 註解 `createTranslationConfig()` 方法
|
||||
- 新增 `@deprecated` 標記說明
|
||||
|
||||
**修改檔案**:
|
||||
- [frontend/src/services/api.ts:246-266](frontend/src/services/api.ts#L246-L266)
|
||||
|
||||
**變更**:
|
||||
```typescript
|
||||
// 修正前
|
||||
async getTranslationConfigs(): Promise<TranslationConfig[]> {
|
||||
const response = await this.client.get<TranslationConfig[]>('/translate/configs')
|
||||
return response.data
|
||||
}
|
||||
|
||||
async createTranslationConfig(
|
||||
config: Omit<TranslationConfig, 'id' | 'created_at'>
|
||||
): Promise<TranslationConfig> {
|
||||
const response = await this.client.post<TranslationConfig>('/translate/configs', config)
|
||||
return response.data
|
||||
}
|
||||
|
||||
// 修正後
|
||||
/**
|
||||
* Get translation configs (NOT IMPLEMENTED)
|
||||
* @deprecated Backend endpoint does not exist - will return 404
|
||||
*/
|
||||
// async getTranslationConfigs(): Promise<TranslationConfig[]> { ... }
|
||||
|
||||
/**
|
||||
* Create translation config (NOT IMPLEMENTED)
|
||||
* @deprecated Backend endpoint does not exist - will return 404
|
||||
*/
|
||||
// async createTranslationConfig(...): Promise<TranslationConfig> { ... }
|
||||
```
|
||||
|
||||
**影響**:
|
||||
- ✅ 避免呼叫不存在的 API 端點 (404 錯誤)
|
||||
- ✅ 明確標示這些方法尚未實作
|
||||
- ✅ 保留型別定義供 Phase 5 使用
|
||||
|
||||
---
|
||||
|
||||
### ✅ 問題 7: OCR 結果查詢參數名稱誤導
|
||||
|
||||
**修正內容**:
|
||||
- 修改 `getOCRResult()` 方法參數名稱從 `taskId` 改為 `fileId`
|
||||
- 更新註解說明使用檔案級別追蹤
|
||||
|
||||
**修改檔案**:
|
||||
- [frontend/src/services/api.ts:153-160](frontend/src/services/api.ts#L153-L160)
|
||||
|
||||
**變更**:
|
||||
```typescript
|
||||
// 修正前
|
||||
async getOCRResult(taskId: string): Promise<OCRResult> {
|
||||
const response = await this.client.get<OCRResult>(`/ocr/result/${taskId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
// 修正後
|
||||
/**
|
||||
* Get OCR result by file ID
|
||||
* Note: Backend uses file-level tracking, not task-level
|
||||
*/
|
||||
async getOCRResult(fileId: number): Promise<OCRResult> {
|
||||
const response = await this.client.get<OCRResult>(`/ocr/result/${fileId}`)
|
||||
return response.data
|
||||
}
|
||||
```
|
||||
|
||||
**影響**:
|
||||
- ✅ 參數名稱更準確反映實際用途
|
||||
- ✅ 型別從 `string` 改為 `number` 更符合後端
|
||||
- ✅ 減少開發者混淆
|
||||
|
||||
---
|
||||
|
||||
## 修正檔案清單
|
||||
|
||||
### 前端修改
|
||||
1. **frontend/src/types/api.ts**
|
||||
- LoginResponse 新增 `expires_in`
|
||||
- FileInfo `format` → `file_format`
|
||||
- ProcessRequest `confidence_threshold` → `detect_layout`
|
||||
- ProcessResponse 新增 `message`, `total_files`,移除 `task_id`
|
||||
- CSSTemplate 移除 `filename`
|
||||
|
||||
2. **frontend/src/services/api.ts**
|
||||
- 移除 `TaskStatus` import
|
||||
- 移除 `getTaskStatus()` 方法
|
||||
- 修正 `getOCRResult()` 參數名稱和型別
|
||||
- 註解 `getTranslationConfigs()` 和 `createTranslationConfig()`
|
||||
|
||||
### 後端修改
|
||||
- **無需修改** - 所有修正都在前端配合後端
|
||||
|
||||
---
|
||||
|
||||
## 驗證建議
|
||||
|
||||
### 1. 型別檢查
|
||||
```bash
|
||||
cd frontend
|
||||
npm run type-check
|
||||
```
|
||||
|
||||
### 2. 編譯檢查
|
||||
```bash
|
||||
cd frontend
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 3. 測試建議
|
||||
- ✅ 測試登入功能,確認可接收 `expires_in`
|
||||
- ✅ 測試檔案上傳,確認 `file_format` 欄位正確顯示
|
||||
- ✅ 測試 OCR 處理,確認 `detect_layout` 參數正確傳遞
|
||||
- ✅ 測試批次狀態追蹤,確認 `getBatchStatus()` 正常運作
|
||||
- ✅ 測試 OCR 結果查詢,確認 `fileId` 參數正確使用
|
||||
- ✅ 確認不再呼叫不存在的翻譯設定端點
|
||||
|
||||
---
|
||||
|
||||
## 後續工作建議
|
||||
|
||||
### 立即工作
|
||||
1. ✅ 更新前端 UI 使用新的欄位名稱 (`file_format` 而非 `format`)
|
||||
2. ✅ 移除任何使用 `getTaskStatus()` 的程式碼
|
||||
3. ✅ 更新使用 `getOCRResult()` 的程式碼,改用 `fileId` 參數
|
||||
|
||||
### Phase 5 (翻譯功能) 工作
|
||||
1. ⏸️ 實作後端 `/api/v1/translate/configs` 端點 (GET/POST)
|
||||
2. ⏸️ 取消註解前端的 `getTranslationConfigs()` 和 `createTranslationConfig()`
|
||||
3. ⏸️ 移除 `@deprecated` 標記
|
||||
|
||||
---
|
||||
|
||||
## API 文件
|
||||
|
||||
完整的 API 端點清單和說明請參考:
|
||||
- 📄 [API_REFERENCE.md](./API_REFERENCE.md) - 完整 API 端點文件
|
||||
|
||||
---
|
||||
|
||||
## 修正統計
|
||||
|
||||
- **總問題數**: 7
|
||||
- **已修正**: 7
|
||||
- **修正率**: 100%
|
||||
- **修改檔案數**: 2
|
||||
- **新增文件數**: 2
|
||||
|
||||
---
|
||||
|
||||
## 簽核
|
||||
|
||||
- **修正人員**: Claude AI
|
||||
- **審核狀態**: ✅ 待使用者驗證
|
||||
- **版本**: v0.1.0-fix1
|
||||
- **修正日期**: 2025-01-13
|
||||
|
||||
---
|
||||
|
||||
## 附註
|
||||
|
||||
所有修正都遵循「前端配合後端」的原則,避免破壞性變更。後端 API 保持穩定,前端型別定義完全對齊後端實作。
|
||||
524
DOCKER_DEPLOYMENT.md
Normal file
524
DOCKER_DEPLOYMENT.md
Normal file
@@ -0,0 +1,524 @@
|
||||
# Tool_OCR Docker 部署指南
|
||||
|
||||
## 架構說明
|
||||
|
||||
Tool_OCR 使用統一容器架構,將前端和後端封裝在同一個容器中:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Container (tool_ocr) │
|
||||
│ │
|
||||
│ ┌──────────────────────────────┐ │
|
||||
│ │ Nginx :12010 (External) │ │
|
||||
│ │ - Frontend Static Files │ │
|
||||
│ │ - Reverse Proxy for API │ │
|
||||
│ └─────────┬────────────────────┘ │
|
||||
│ │ proxy_pass │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────┐ │
|
||||
│ │ FastAPI :8000 (Internal) │ │
|
||||
│ │ - OCR Processing │ │
|
||||
│ │ - File Management │ │
|
||||
│ │ - Export Services │ │
|
||||
│ └──────────────────────────────┘ │
|
||||
│ │
|
||||
│ Supervisor manages both services │
|
||||
└─────────────────────────────────────┘
|
||||
│
|
||||
│ Port 12010 only!
|
||||
▼
|
||||
External Access
|
||||
```
|
||||
|
||||
### 優勢
|
||||
|
||||
1. **單一端口**: 只需要暴露一個端口 (12010)
|
||||
2. **簡化部署**: 一個容器包含完整應用
|
||||
3. **統一管理**: Supervisor 管理所有服務
|
||||
4. **生產就緒**: Nginx 提供高性能靜態文件服務和反向代理
|
||||
|
||||
## 快速開始
|
||||
|
||||
### 前置要求
|
||||
|
||||
- Docker Engine 20.10+
|
||||
- Docker Compose 2.0+
|
||||
- 至少 4GB 可用內存
|
||||
- 至少 10GB 可用磁碟空間
|
||||
|
||||
### 1. 準備環境配置
|
||||
|
||||
**複製環境配置範本:**
|
||||
|
||||
Linux/Mac:
|
||||
```bash
|
||||
cp .env.docker .env
|
||||
```
|
||||
|
||||
Windows (PowerShell):
|
||||
```powershell
|
||||
Copy-Item .env.docker .env
|
||||
```
|
||||
|
||||
**編輯 `.env` 文件,至少修改以下重要配置:**
|
||||
|
||||
```bash
|
||||
# 修改為安全的密鑰
|
||||
SECRET_KEY=your-very-secure-random-key-here
|
||||
|
||||
# 根據需要調整端口
|
||||
FRONTEND_PORT=12010
|
||||
|
||||
# 根據實際情況配置 CORS
|
||||
CORS_ORIGINS=http://your-domain.com:12010,http://localhost:12010
|
||||
```
|
||||
|
||||
### 2. 創建數據目錄
|
||||
|
||||
Linux/Mac:
|
||||
```bash
|
||||
mkdir -p data/{uploads,storage,models,logs}
|
||||
```
|
||||
|
||||
Windows (PowerShell):
|
||||
```powershell
|
||||
mkdir -p data/uploads, data/storage, data/models, data/logs
|
||||
```
|
||||
|
||||
或使用跨平台命令:
|
||||
```bash
|
||||
mkdir -p data/uploads data/storage data/models data/logs
|
||||
```
|
||||
|
||||
### 3. 構建並啟動容器
|
||||
|
||||
```bash
|
||||
# 構建映像
|
||||
docker compose build
|
||||
|
||||
# 啟動服務
|
||||
docker compose up -d
|
||||
|
||||
# 查看日誌
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
> 注意:舊版本 Docker 使用 `docker-compose`(帶連字符),新版本使用 `docker compose`(無連字符)。兩者都支持。
|
||||
|
||||
### 4. 驗證部署
|
||||
|
||||
Linux/Mac:
|
||||
```bash
|
||||
# 檢查健康狀態
|
||||
curl http://localhost:12010/health
|
||||
|
||||
# 訪問 API 文檔
|
||||
open http://localhost:12010/docs
|
||||
|
||||
# 訪問前端界面
|
||||
open http://localhost:12010
|
||||
```
|
||||
|
||||
Windows (PowerShell):
|
||||
```powershell
|
||||
# 檢查健康狀態
|
||||
curl http://localhost:12010/health
|
||||
|
||||
# 在瀏覽器中打開
|
||||
Start-Process "http://localhost:12010"
|
||||
Start-Process "http://localhost:12010/docs"
|
||||
```
|
||||
|
||||
## 管理命令
|
||||
|
||||
> 提示:以下命令在 Windows、Linux 和 Mac 上通用。如果您使用舊版 Docker,將 `docker compose` 替換為 `docker-compose`。
|
||||
|
||||
### 查看狀態
|
||||
|
||||
```bash
|
||||
# 查看容器狀態
|
||||
docker compose ps
|
||||
|
||||
# 查看實時日誌
|
||||
docker compose logs -f
|
||||
|
||||
# 查看特定服務日誌
|
||||
docker compose exec tool_ocr tail -f /var/log/nginx/tool_ocr_access.log
|
||||
docker compose exec tool_ocr tail -f /app/backend/logs/app.log
|
||||
```
|
||||
|
||||
### 重啟服務
|
||||
|
||||
```bash
|
||||
# 重啟容器
|
||||
docker compose restart
|
||||
|
||||
# 重啟 Nginx (容器內)
|
||||
docker compose exec tool_ocr supervisorctl restart nginx
|
||||
|
||||
# 重啟 Backend (容器內)
|
||||
docker compose exec tool_ocr supervisorctl restart backend
|
||||
```
|
||||
|
||||
### 停止和清理
|
||||
|
||||
```bash
|
||||
# 停止服務
|
||||
docker compose stop
|
||||
|
||||
# 停止並移除容器
|
||||
docker compose down
|
||||
|
||||
# 完全清理(包括數據卷)⚠️ 慎用
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
### 進入容器調試
|
||||
|
||||
```bash
|
||||
# 進入容器 shell
|
||||
docker compose exec tool_ocr bash
|
||||
|
||||
# 查看 Supervisor 狀態
|
||||
docker compose exec tool_ocr supervisorctl status
|
||||
|
||||
# 查看進程
|
||||
docker compose exec tool_ocr ps aux
|
||||
```
|
||||
|
||||
## 數據持久化
|
||||
|
||||
以下目錄會持久化到主機的 `./data/` 目錄:
|
||||
|
||||
| 容器內路徑 | 主機路徑 | 說明 |
|
||||
|-----------|---------|------|
|
||||
| `/app/backend/uploads` | `./data/uploads` | 上傳文件 |
|
||||
| `/app/backend/storage` | `./data/storage` | 處理結果 |
|
||||
| `/app/backend/models` | `./data/models` | OCR 模型 |
|
||||
| `/app/backend/logs` | `./data/logs` | 應用日誌 |
|
||||
|
||||
### 備份數據
|
||||
|
||||
Linux/Mac:
|
||||
```bash
|
||||
# 備份所有數據
|
||||
tar -czf tool_ocr_backup_$(date +%Y%m%d).tar.gz data/
|
||||
|
||||
# 只備份重要數據
|
||||
tar -czf tool_ocr_data_$(date +%Y%m%d).tar.gz data/uploads data/storage
|
||||
```
|
||||
|
||||
Windows (PowerShell):
|
||||
```powershell
|
||||
# 備份所有數據(需要安裝 7-Zip 或使用 Compress-Archive)
|
||||
$date = Get-Date -Format "yyyyMMdd"
|
||||
Compress-Archive -Path data -DestinationPath "tool_ocr_backup_$date.zip"
|
||||
|
||||
# 只備份重要數據
|
||||
Compress-Archive -Path data/uploads, data/storage -DestinationPath "tool_ocr_data_$date.zip"
|
||||
```
|
||||
|
||||
### 恢復數據
|
||||
|
||||
Linux/Mac:
|
||||
```bash
|
||||
# 停止容器
|
||||
docker compose stop
|
||||
|
||||
# 恢復數據
|
||||
tar -xzf tool_ocr_backup_20250113.tar.gz
|
||||
|
||||
# 啟動容器
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Windows (PowerShell):
|
||||
```powershell
|
||||
# 停止容器
|
||||
docker compose stop
|
||||
|
||||
# 恢復數據
|
||||
Expand-Archive -Path tool_ocr_backup_20250113.zip -DestinationPath . -Force
|
||||
|
||||
# 啟動容器
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## 1Panel 部署指南
|
||||
|
||||
### 1. 準備項目文件
|
||||
|
||||
在 1Panel 的應用目錄中創建項目:
|
||||
|
||||
```bash
|
||||
cd /opt/1panel/apps
|
||||
mkdir -p tool_ocr
|
||||
cd tool_ocr
|
||||
|
||||
# 上傳項目文件
|
||||
# - Dockerfile
|
||||
# - docker-compose.yml
|
||||
# - docker/ 目錄
|
||||
# - backend/ 目錄
|
||||
# - frontend/ 目錄
|
||||
# - requirements.txt
|
||||
# - .env
|
||||
```
|
||||
|
||||
### 2. 在 1Panel 中創建應用
|
||||
|
||||
1. 登入 1Panel 管理面板
|
||||
2. 進入「應用商店」→「自定義應用」
|
||||
3. 選擇「Docker Compose」
|
||||
4. 上傳或粘貼 `docker-compose.yml` 內容
|
||||
5. 配置環境變量
|
||||
6. 點擊「創建」
|
||||
|
||||
### 3. 配置反向代理(可選)
|
||||
|
||||
如果需要通過域名訪問:
|
||||
|
||||
1. 在 1Panel 中創建網站
|
||||
2. 配置反向代理:
|
||||
- 目標地址: `http://127.0.0.1:12010`
|
||||
- 啟用 WebSocket 支援(如需要)
|
||||
|
||||
### 4. 配置 SSL 證書(可選)
|
||||
|
||||
1. 在 1Panel 網站設置中
|
||||
2. 申請或上傳 SSL 證書
|
||||
3. 啟用 HTTPS
|
||||
|
||||
## 更新部署
|
||||
|
||||
### 更新代碼
|
||||
|
||||
```bash
|
||||
# 停止容器
|
||||
docker compose stop
|
||||
|
||||
# 拉取最新代碼
|
||||
git pull
|
||||
|
||||
# 重新構建映像
|
||||
docker compose build --no-cache
|
||||
|
||||
# 啟動容器
|
||||
docker compose up -d
|
||||
|
||||
# 查看日誌確認啟動成功
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
### 數據庫遷移
|
||||
|
||||
如果有數據庫結構變更:
|
||||
|
||||
```bash
|
||||
# 進入容器
|
||||
docker compose exec tool_ocr bash
|
||||
|
||||
# 運行遷移
|
||||
cd /app/backend
|
||||
alembic upgrade head
|
||||
|
||||
# 退出容器
|
||||
exit
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 1. 容器無法啟動
|
||||
|
||||
```bash
|
||||
# 查看詳細錯誤
|
||||
docker compose logs
|
||||
```
|
||||
|
||||
檢查端口占用:
|
||||
|
||||
Linux/Mac:
|
||||
```bash
|
||||
netstat -tuln | grep 12010
|
||||
# 或
|
||||
lsof -i :12010
|
||||
```
|
||||
|
||||
Windows (PowerShell):
|
||||
```powershell
|
||||
netstat -ano | findstr 12010
|
||||
# 或
|
||||
Get-NetTCPConnection -LocalPort 12010
|
||||
```
|
||||
|
||||
檢查磁碟空間:
|
||||
|
||||
Linux/Mac:
|
||||
```bash
|
||||
df -h
|
||||
```
|
||||
|
||||
Windows (PowerShell):
|
||||
```powershell
|
||||
Get-PSDrive
|
||||
```
|
||||
|
||||
### 2. Nginx 無法啟動
|
||||
|
||||
```bash
|
||||
# 檢查 Nginx 配置語法
|
||||
docker compose exec tool_ocr nginx -t
|
||||
|
||||
# 查看 Nginx 錯誤日誌
|
||||
docker compose exec tool_ocr cat /var/log/nginx/error.log
|
||||
```
|
||||
|
||||
### 3. Backend API 無法訪問
|
||||
|
||||
```bash
|
||||
# 檢查 Backend 是否運行
|
||||
docker compose exec tool_ocr supervisorctl status backend
|
||||
|
||||
# 查看 Backend 日誌
|
||||
docker compose exec tool_ocr cat /app/backend/logs/app.log
|
||||
|
||||
# 重啟 Backend
|
||||
docker compose exec tool_ocr supervisorctl restart backend
|
||||
```
|
||||
|
||||
### 4. 數據庫連接失敗
|
||||
|
||||
```bash
|
||||
# 測試數據庫連接
|
||||
docker compose exec tool_ocr python -c "
|
||||
from app.core.database import engine
|
||||
try:
|
||||
with engine.connect() as conn:
|
||||
print('Database connection successful!')
|
||||
except Exception as e:
|
||||
print(f'Database connection failed: {e}')
|
||||
"
|
||||
```
|
||||
|
||||
### 5. OCR 處理失敗
|
||||
|
||||
```bash
|
||||
# 檢查 PaddleOCR 模型
|
||||
docker compose exec tool_ocr ls -la /app/backend/models/paddleocr/
|
||||
|
||||
# 測試 OCR 功能
|
||||
docker compose exec tool_ocr python -c "
|
||||
from paddleocr import PaddleOCR
|
||||
ocr = PaddleOCR(lang='ch')
|
||||
print('PaddleOCR initialized successfully!')
|
||||
"
|
||||
```
|
||||
|
||||
### 6. 前端頁面無法訪問
|
||||
|
||||
```bash
|
||||
# 檢查前端文件是否存在
|
||||
docker compose exec tool_ocr ls -la /app/frontend/dist/
|
||||
|
||||
# 檢查 Nginx 配置
|
||||
docker compose exec tool_ocr cat /etc/nginx/conf.d/default.conf
|
||||
```
|
||||
|
||||
## 性能優化
|
||||
|
||||
### 1. 調整 OCR 工作進程數
|
||||
|
||||
根據 CPU 核心數調整:
|
||||
|
||||
```bash
|
||||
# 在 .env 中設置
|
||||
MAX_OCR_WORKERS=8 # 建議設置為 CPU 核心數
|
||||
```
|
||||
|
||||
### 2. 調整 Nginx Worker 進程數
|
||||
|
||||
編輯 `docker/nginx.conf`:
|
||||
|
||||
```nginx
|
||||
worker_processes auto; # 自動根據 CPU 核心數
|
||||
```
|
||||
|
||||
### 3. 優化 Upload 大小限制
|
||||
|
||||
根據實際需求調整:
|
||||
|
||||
```bash
|
||||
# 在 .env 中設置(以字節為單位)
|
||||
MAX_UPLOAD_SIZE=104857600 # 100MB
|
||||
```
|
||||
|
||||
同時修改 `docker/nginx.conf`:
|
||||
|
||||
```nginx
|
||||
client_max_body_size 100M;
|
||||
```
|
||||
|
||||
## 監控和日誌
|
||||
|
||||
### 日誌位置
|
||||
|
||||
| 服務 | 容器內路徑 | 主機路徑 |
|
||||
|------|-----------|---------|
|
||||
| Nginx Access | `/var/log/nginx/tool_ocr_access.log` | - |
|
||||
| Nginx Error | `/var/log/nginx/tool_ocr_error.log` | - |
|
||||
| Backend | `/app/backend/logs/app.log` | `./data/logs/app.log` |
|
||||
| Supervisor | `/var/log/supervisor/supervisord.log` | - |
|
||||
|
||||
### 日誌輪轉
|
||||
|
||||
建議配置日誌輪轉以防止日誌文件過大:
|
||||
|
||||
```bash
|
||||
# 創建 logrotate 配置(主機上)
|
||||
cat > /etc/logrotate.d/tool_ocr << 'EOF'
|
||||
/path/to/tool_ocr/data/logs/*.log {
|
||||
daily
|
||||
rotate 7
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
create 0644 root root
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
## 安全建議
|
||||
|
||||
1. **修改默認密鑰**: 務必修改 `.env` 中的 `SECRET_KEY`
|
||||
2. **使用 HTTPS**: 在生產環境中啟用 SSL/TLS
|
||||
3. **限制 CORS**: 只允許可信的來源
|
||||
4. **定期更新**: 及時更新 Docker 映像和依賴
|
||||
5. **備份數據**: 定期備份重要數據
|
||||
6. **監控日誌**: 定期檢查日誌中的異常活動
|
||||
|
||||
## 常見問題
|
||||
|
||||
### Q: 如何修改對外端口?
|
||||
|
||||
A: 修改 `.env` 中的 `FRONTEND_PORT` 和 `docker-compose.yml` 中的端口映射。
|
||||
|
||||
### Q: 如何增加上傳文件大小限制?
|
||||
|
||||
A: 修改 `.env` 中的 `MAX_UPLOAD_SIZE` 和 `docker/nginx.conf` 中的 `client_max_body_size`。
|
||||
|
||||
### Q: 如何連接外部 MySQL 數據庫?
|
||||
|
||||
A: 在 `.env` 中配置正確的數據庫連接信息。
|
||||
|
||||
### Q: 如何查看詳細的錯誤信息?
|
||||
|
||||
A: 設置 `.env` 中的 `LOG_LEVEL=DEBUG` 並重啟容器。
|
||||
|
||||
## 聯繫支援
|
||||
|
||||
如果遇到問題,請:
|
||||
|
||||
1. 查看日誌: `docker-compose logs -f`
|
||||
2. 檢查配置: 確認 `.env` 文件正確
|
||||
3. 查看文檔: 參考本文檔的故障排除部分
|
||||
4. 提交 Issue: 在項目倉庫提交問題報告
|
||||
131
Dockerfile
Normal file
131
Dockerfile
Normal file
@@ -0,0 +1,131 @@
|
||||
# ============================================
|
||||
# Tool_OCR - Unified Docker Image
|
||||
# Frontend (React + Vite) + Backend (FastAPI)
|
||||
# Served by Nginx with reverse proxy
|
||||
# ============================================
|
||||
|
||||
# ============================================
|
||||
# Stage 1: Build Frontend
|
||||
# ============================================
|
||||
FROM node:20-alpine AS frontend-builder
|
||||
|
||||
WORKDIR /app/frontend
|
||||
|
||||
# Copy package files
|
||||
COPY frontend/package*.json ./
|
||||
|
||||
# Install all dependencies (including devDependencies for build)
|
||||
RUN npm ci
|
||||
|
||||
# Copy frontend source
|
||||
COPY frontend/ ./
|
||||
|
||||
# Create production environment file
|
||||
RUN echo "VITE_API_BASE_URL=" > .env.production
|
||||
|
||||
# Build frontend for production
|
||||
RUN npm run build
|
||||
|
||||
|
||||
# ============================================
|
||||
# Stage 2: Build Backend + Final Image
|
||||
# ============================================
|
||||
FROM python:3.10-slim-bookworm
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PIP_NO_CACHE_DIR=1 \
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
||||
DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install system dependencies
|
||||
# - nginx: web server and reverse proxy
|
||||
# - supervisor: process manager for nginx + uvicorn
|
||||
# - curl: for health checks
|
||||
# - pandoc: for markdown to PDF conversion
|
||||
# - poppler-utils: for pdf2image (PDF processing)
|
||||
# - libpango-1.0-0, libpangocairo-1.0-0: for WeasyPrint
|
||||
# - libgdk-pixbuf2.0-0: for WeasyPrint image handling
|
||||
# - libffi-dev: for cryptography
|
||||
# - fonts-noto-cjk: Chinese/Japanese/Korean font support
|
||||
# - libgomp1, libgl1-mesa-glx, libglib2.0-0: for OpenCV and PaddleOCR
|
||||
# - libmagic1: for python-magic file type detection
|
||||
# - libreoffice-writer, libreoffice-impress: for Office document conversion (doc/docx/ppt/pptx)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
nginx \
|
||||
supervisor \
|
||||
curl \
|
||||
pandoc \
|
||||
poppler-utils \
|
||||
libpango-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
libgdk-pixbuf2.0-0 \
|
||||
libffi-dev \
|
||||
fonts-noto-cjk \
|
||||
fonts-noto-cjk-extra \
|
||||
libgomp1 \
|
||||
libgl1-mesa-glx \
|
||||
libglib2.0-0 \
|
||||
libmagic1 \
|
||||
libreoffice-writer \
|
||||
libreoffice-impress \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy Python requirements
|
||||
COPY requirements.txt .
|
||||
|
||||
# Install Python dependencies with extended timeout
|
||||
# PaddlePaddle is 189MB and may take time to download
|
||||
# Timeout: 600 seconds (10 minutes), Retries: 5
|
||||
RUN pip install --timeout 600 --retries 5 -r requirements.txt
|
||||
|
||||
# Copy backend application
|
||||
COPY backend/ ./backend/
|
||||
|
||||
# Copy frontend build from frontend-builder stage
|
||||
COPY --from=frontend-builder /app/frontend/dist /app/frontend/dist
|
||||
|
||||
# Copy Nginx configuration
|
||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY docker/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Copy supervisor configuration
|
||||
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
# Copy startup script and fix line endings (Windows CRLF -> Linux LF)
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN sed -i 's/\r$//' /entrypoint.sh && chmod +x /entrypoint.sh
|
||||
|
||||
# Create necessary directories with proper permissions
|
||||
RUN mkdir -p \
|
||||
/app/backend/uploads/temp \
|
||||
/app/backend/uploads/processed \
|
||||
/app/backend/uploads/images \
|
||||
/app/backend/storage/markdown \
|
||||
/app/backend/storage/json \
|
||||
/app/backend/storage/exports \
|
||||
/app/backend/models/paddleocr \
|
||||
/app/backend/logs \
|
||||
/var/log/supervisor \
|
||||
/var/log/nginx \
|
||||
/var/cache/nginx \
|
||||
/var/run \
|
||||
&& chmod -R 755 /app \
|
||||
&& chown -R www-data:www-data /var/log/nginx /var/cache/nginx
|
||||
|
||||
# Expose port (only one port needed!)
|
||||
EXPOSE 12015
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD curl -f http://localhost:12015/health || exit 1
|
||||
|
||||
# Set working directory to backend for Python app
|
||||
WORKDIR /app/backend
|
||||
|
||||
# Use entrypoint script to start supervisor
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -1,511 +0,0 @@
|
||||
# Tool_OCR 前端項目結構分析
|
||||
|
||||
## 項目概況
|
||||
|
||||
- **項目名稱**: Tool_OCR Frontend
|
||||
- **框架**: React 19.2.0 (TypeScript)
|
||||
- **構建工具**: Vite 7.2.2
|
||||
- **樣式方案**: Tailwind CSS 4.1.17
|
||||
- **路由管理**: React Router v7.9.5
|
||||
- **狀態管理**: Zustand 5.0.8
|
||||
- **數據獲取**: TanStack React Query v5.90.7
|
||||
- **國際化**: i18next 25.6.2
|
||||
- **源碼行數**: ~2,700 行
|
||||
- **開發語言**: TypeScript
|
||||
- **運行端口**: 12011
|
||||
|
||||
---
|
||||
|
||||
## 前端技術棧詳解
|
||||
|
||||
### 核心框架
|
||||
- **React 19.2.0**: 最新版 React,用於構建用戶界面
|
||||
- 使用函數式組件
|
||||
- React Hooks 進行狀態和副作用管理
|
||||
- 嚴格模式(StrictMode)開啟
|
||||
|
||||
### 構建與開發
|
||||
- **Vite 7.2.2**: 快速的前端構建工具
|
||||
- 配置路徑別名 `@` → `./src`
|
||||
- 開發服務器代理設定(API 轉發到 localhost:12010)
|
||||
- TypeScript 支持
|
||||
|
||||
- **TypeScript ~5.9.3**: 類型檢查和增強開發體驗
|
||||
|
||||
- **ESLint + TypeScript ESLint**: 代碼質量檢查
|
||||
- React Hooks 規則檢查
|
||||
- React Refresh 支持
|
||||
|
||||
### 樣式設計
|
||||
|
||||
#### Tailwind CSS 4.1.17
|
||||
- **CSS 框架**: 完全採用 Tailwind CSS
|
||||
- **配置文件**: `/frontend/tailwind.config.js`
|
||||
|
||||
**主題配置 (HSL 色彩系統)**:
|
||||
```
|
||||
顏色變數 (CSS 自定義屬性):
|
||||
- --background / --foreground (背景/前景)
|
||||
- --primary / --primary-foreground (主色/主色前景)
|
||||
- --secondary / --secondary-foreground (次色)
|
||||
- --muted / --muted-foreground (灰色系)
|
||||
- --accent / --accent-foreground (強調色)
|
||||
- --destructive / --destructive-foreground (危險操作)
|
||||
- --card / --card-foreground (卡片)
|
||||
- --popover / --popover-foreground (彈出框)
|
||||
- --border / --input (邊框/輸入框)
|
||||
- --ring (焦點環)
|
||||
```
|
||||
|
||||
**亮色模式**:
|
||||
- 白色背景 (hsl(0 0% 100%))
|
||||
- 深色文字 (hsl(222.2 84% 4.9%))
|
||||
- 藍色主題 (hsl(221.2 83.2% 53.3%))
|
||||
|
||||
**暗色模式** (.dark class):
|
||||
- 深色背景 (hsl(222.2 84% 4.9%))
|
||||
- 淺色文字 (hsl(210 40% 98%))
|
||||
|
||||
#### PostCSS
|
||||
- **配置文件**: `/frontend/postcss.config.js`
|
||||
- **插件**: @tailwindcss/postcss (新版本原生支持)
|
||||
|
||||
#### 全局樣式
|
||||
- **入口**: `/frontend/src/index.css`
|
||||
- @tailwind 指令導入
|
||||
- CSS 變數定義
|
||||
- 亮色/暗色主題切換
|
||||
|
||||
---
|
||||
|
||||
## 項目目錄結構
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── assets/ # 靜態資源
|
||||
│ ├── components/ # React 組件
|
||||
│ │ ├── ui/ # UI 基礎組件庫
|
||||
│ │ │ ├── button.tsx # 按鈕組件 (多種變體)
|
||||
│ │ │ ├── card.tsx # 卡片容器
|
||||
│ │ │ ├── badge.tsx # 標籤組件
|
||||
│ │ │ ├── table.tsx # 表格組件
|
||||
│ │ │ ├── progress.tsx # 進度條
|
||||
│ │ │ └── toast.tsx # 提示通知
|
||||
│ │ ├── FileUpload.tsx # 文件上傳 (拖放支持)
|
||||
│ │ ├── ResultsTable.tsx # 結果表格
|
||||
│ │ ├── MarkdownPreview.tsx # Markdown 預覽
|
||||
│ │ └── Layout.tsx # 主佈局組件
|
||||
│ │
|
||||
│ ├── pages/ # 頁面組件
|
||||
│ │ ├── LoginPage.tsx # 登錄頁面
|
||||
│ │ ├── UploadPage.tsx # 文件上傳頁面
|
||||
│ │ ├── ProcessingPage.tsx # OCR 處理頁面
|
||||
│ │ ├── ResultsPage.tsx # 結果查看頁面
|
||||
│ │ ├── ExportPage.tsx # 導出配置頁面
|
||||
│ │ └── SettingsPage.tsx # 系統設置頁面
|
||||
│ │
|
||||
│ ├── store/ # 狀態管理 (Zustand)
|
||||
│ │ ├── authStore.ts # 認證狀態
|
||||
│ │ └── uploadStore.ts # 上傳狀態
|
||||
│ │
|
||||
│ ├── services/ # API 服務層
|
||||
│ │ └── api.ts # API 客戶端 (Axios)
|
||||
│ │
|
||||
│ ├── types/ # TypeScript 類型定義
|
||||
│ │ └── api.ts # API 接口類型
|
||||
│ │
|
||||
│ ├── lib/ # 工具函數
|
||||
│ │ └── utils.ts # cn() 函數 (Tailwind 類名合併)
|
||||
│ │
|
||||
│ ├── hooks/ # 自定義 React Hooks
|
||||
│ │ └── (待擴展)
|
||||
│ │
|
||||
│ ├── i18n/ # 國際化配置
|
||||
│ │ ├── index.ts # i18n 初始化
|
||||
│ │ └── locales/
|
||||
│ │ └── zh-TW.json # 繁體中文翻譯
|
||||
│ │
|
||||
│ ├── App.tsx # 應用根組件 (路由定義)
|
||||
│ ├── main.tsx # 應用入口
|
||||
│ ├── index.css # 全局樣式
|
||||
│ └── App.css # 應用樣式 (可棄用)
|
||||
│
|
||||
├── public/ # 公開靜態資源
|
||||
├── vite.config.ts # Vite 配置
|
||||
├── tailwind.config.js # Tailwind 配置
|
||||
├── postcss.config.js # PostCSS 配置
|
||||
├── tsconfig.json # TypeScript 配置
|
||||
├── tsconfig.app.json # 應用 TS 配置
|
||||
├── tsconfig.node.json # Node TS 配置
|
||||
├── eslint.config.js # ESLint 配置
|
||||
├── index.html # HTML 入口
|
||||
├── package.json # 依賴管理
|
||||
├── package-lock.json # 依賴鎖定
|
||||
└── .env # 環境變數
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心頁面和功能
|
||||
|
||||
### 頁面概述
|
||||
|
||||
| 頁面 | 路由 | 功能描述 | 行數 |
|
||||
|------|------|--------|------|
|
||||
| LoginPage | /login | 用戶認證 | 97 |
|
||||
| UploadPage | /upload | 文件上傳,支持拖放 | 140 |
|
||||
| ProcessingPage | /processing | 實時監控 OCR 處理進度 | 200 |
|
||||
| ResultsPage | /results | 查看和下載 OCR 結果 | 157 |
|
||||
| ExportPage | /export | 導出規則管理和結果導出 | 321 |
|
||||
| SettingsPage | /settings | 系統配置和用戶偏好 | 325 |
|
||||
|
||||
### 總 UI 頁面代碼: ~1,240 行
|
||||
|
||||
---
|
||||
|
||||
## 組件架構
|
||||
|
||||
### UI 基礎組件庫 (/src/components/ui/)
|
||||
|
||||
採用現代化的無頭 UI 組件設計,配合 Tailwind CSS:
|
||||
|
||||
1. **Button**
|
||||
- 變體: default, destructive, outline, secondary, ghost, link
|
||||
- 尺寸: default, sm, lg, icon
|
||||
- 使用 cn() 實現動態類名合併
|
||||
|
||||
2. **Card**
|
||||
- CardRoot, CardHeader, CardTitle, CardContent, CardFooter
|
||||
- 容器組件,使用 Tailwind 樣式
|
||||
|
||||
3. **Table**
|
||||
- 表格組件家族
|
||||
- Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell
|
||||
|
||||
4. **Badge**
|
||||
- 標籤/徽章組件
|
||||
- 用於狀態指示
|
||||
|
||||
5. **Progress**
|
||||
- 進度條組件
|
||||
- 百分比顯示
|
||||
|
||||
6. **Toast**
|
||||
- 通知系統
|
||||
- useToast() Hook
|
||||
- 支持多個變體
|
||||
|
||||
### 業務組件
|
||||
|
||||
1. **FileUpload**
|
||||
- react-dropzone 整合
|
||||
- 支持拖放和點擊選擇
|
||||
- 文件驗證 (類型、大小、數量)
|
||||
- 自定義 accept 配置
|
||||
|
||||
2. **ResultsTable**
|
||||
- 結果列表展示
|
||||
|
||||
3. **MarkdownPreview**
|
||||
- Markdown 內容預覽
|
||||
|
||||
4. **Layout**
|
||||
- 主體佈局
|
||||
- 導航欄
|
||||
- 頁頭
|
||||
- 頁腳
|
||||
|
||||
---
|
||||
|
||||
## 狀態管理方案
|
||||
|
||||
### Zustand (v5.0.8)
|
||||
|
||||
#### 1. authStore.ts
|
||||
```typescript
|
||||
interface AuthState {
|
||||
user: User | null
|
||||
isAuthenticated: boolean
|
||||
setUser: (user: User | null) => void
|
||||
logout: () => void
|
||||
}
|
||||
```
|
||||
- **持久化**: localStorage (auth-storage)
|
||||
- **用途**: 管理登錄用戶信息和認證狀態
|
||||
|
||||
#### 2. uploadStore.ts
|
||||
```typescript
|
||||
interface UploadState {
|
||||
batchId: number | null
|
||||
files: FileInfo[]
|
||||
uploadProgress: number
|
||||
setBatchId: (id: number) => void
|
||||
setFiles: (files: FileInfo[]) => void
|
||||
setUploadProgress: (progress: number) => void
|
||||
updateFileStatus: (fileId: number, status: string) => void
|
||||
clearUpload: () => void
|
||||
}
|
||||
```
|
||||
- **持久化**: localStorage (tool-ocr-upload-store)
|
||||
- 只持久化: batchId, files (不持久化進度)
|
||||
- **用途**: 管理當前上傳批次和文件信息
|
||||
|
||||
---
|
||||
|
||||
## API 服務層
|
||||
|
||||
### API 客戶端 (services/api.ts)
|
||||
|
||||
基於 Axios 的單例模式實現:
|
||||
|
||||
**配置**:
|
||||
- Base URL: http://localhost:12010 (可通過 VITE_API_BASE_URL 環境變數覆蓋)
|
||||
- API 版本: /api/v1
|
||||
- 超時時間: 30 秒
|
||||
- Content-Type: application/json
|
||||
|
||||
**功能模塊**:
|
||||
|
||||
1. **認證模塊**
|
||||
- login(username, password)
|
||||
- logout()
|
||||
- Token 管理 (localStorage)
|
||||
|
||||
2. **文件上傳模塊**
|
||||
- uploadFiles(files: File[])
|
||||
- FormData multipart 上傳
|
||||
|
||||
3. **OCR 處理模塊**
|
||||
- processOCR(batchId, lang, confidenceThreshold)
|
||||
- getTaskStatus(taskId)
|
||||
- getOCRResult(taskId)
|
||||
- getBatchStatus(batchId)
|
||||
|
||||
4. **導出模塊**
|
||||
- exportResults(batchId, format, options)
|
||||
- exportPDF(fileId, cssTemplate)
|
||||
- getExportRules()
|
||||
- createExportRule(rule)
|
||||
- updateExportRule(ruleId, rule)
|
||||
- deleteExportRule(ruleId)
|
||||
- getCSSTemplates()
|
||||
|
||||
5. **翻譯模塊** (計劃功能)
|
||||
- translateDocument() [501 Not Implemented]
|
||||
- getTranslationConfigs()
|
||||
- createTranslationConfig()
|
||||
|
||||
**攔截器**:
|
||||
- 請求攔截: 自動添加 Authorization Bearer token
|
||||
- 響應攔截: 401 時清除 token 並重定向到登錄頁
|
||||
|
||||
---
|
||||
|
||||
## 類型定義
|
||||
|
||||
### 主要類型 (types/api.ts)
|
||||
|
||||
#### 認證
|
||||
- LoginRequest, LoginResponse, User
|
||||
|
||||
#### 文件上傳
|
||||
- UploadResponse, FileInfo
|
||||
|
||||
#### OCR 處理
|
||||
- ProcessRequest, ProcessResponse, TaskStatus, BatchStatus, FileResult
|
||||
|
||||
#### OCR 結果
|
||||
- OCRResult, OCRJsonData, TextBlock, LayoutInfo
|
||||
|
||||
#### 導出
|
||||
- ExportRequest, ExportOptions, ExportRule, CSSTemplate
|
||||
|
||||
#### 翻譯 (Future)
|
||||
- TranslateRequest, TranslateResponse, TranslationConfig
|
||||
|
||||
#### 錯誤處理
|
||||
- ApiError, ApiResponse
|
||||
|
||||
---
|
||||
|
||||
## 國際化配置
|
||||
|
||||
### i18n (i18n/index.ts)
|
||||
|
||||
- **庫**: react-i18next v16.3.0
|
||||
- **默認語言**: 繁體中文 (zh-TW)
|
||||
- **翻譯文件**: /i18n/locales/zh-TW.json
|
||||
|
||||
### 翻譯內容結構
|
||||
```json
|
||||
{
|
||||
"app": { title, subtitle },
|
||||
"nav": { upload, processing, results, export, settings, logout },
|
||||
"auth": { login, username, password, loginButton, loginError, welcomeBack },
|
||||
"upload": { title, dragAndDrop, dropFilesHere, invalidFiles, ... },
|
||||
"processing": { title, status, progress, currentFile, ... },
|
||||
"results": { title, filename, status, confidence, ... },
|
||||
"export": { title, format, formats, options, rules, cssTemplates, ... },
|
||||
"settings": { ... },
|
||||
"errors": { ... },
|
||||
"common": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 路由結構
|
||||
|
||||
### React Router v7.9.5 配置 (App.tsx)
|
||||
|
||||
```
|
||||
/login → LoginPage (公開)
|
||||
/ → Layout (受保護)
|
||||
├── /upload → UploadPage
|
||||
├── /processing → ProcessingPage
|
||||
├── /results → ResultsPage
|
||||
├── /export → ExportPage
|
||||
└── /settings → SettingsPage
|
||||
* → 重定向到 /
|
||||
```
|
||||
|
||||
**保護機制**: ProtectedRoute 組件基於 authStore.isAuthenticated
|
||||
|
||||
---
|
||||
|
||||
## 樣式設計工具
|
||||
|
||||
### Tailwind 工具函數 (lib/utils.ts)
|
||||
|
||||
```typescript
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
```
|
||||
|
||||
**功能**:
|
||||
- clsx: 條件類名組合
|
||||
- tailwind-merge: 解決 Tailwind 類名衝突
|
||||
- 用於動態生成和合併 Tailwind 類
|
||||
|
||||
---
|
||||
|
||||
## 依賴概覽
|
||||
|
||||
### 主要依賴
|
||||
```json
|
||||
{
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.9.5",
|
||||
"zustand": "^5.0.8",
|
||||
"@tanstack/react-query": "^5.90.7",
|
||||
"axios": "^1.13.2",
|
||||
"react-i18next": "^16.3.0",
|
||||
"i18next": "^25.6.2",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"clsx": "^2.1.1",
|
||||
"tailwind-merge": "^3.4.0"
|
||||
}
|
||||
```
|
||||
|
||||
### 開發依賴
|
||||
```json
|
||||
{
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.2.2",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"eslint": "^9.39.1",
|
||||
"typescript-eslint": "^8.46.3",
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"postcss": "^8.5.6",
|
||||
"autoprefixer": "^10.4.22"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 開發和構建
|
||||
|
||||
### 可用命令
|
||||
```bash
|
||||
npm run dev # 啟動開發服務器 (localhost:12011)
|
||||
npm run build # 生產構建 (TypeScript 編譯 + Vite 打包)
|
||||
npm run lint # ESLint 檢查
|
||||
npm run preview # 預覽構建結果
|
||||
```
|
||||
|
||||
### 開發服務器配置
|
||||
- **端口**: 12011
|
||||
- **代理**: /api → http://localhost:12010 (後端)
|
||||
|
||||
---
|
||||
|
||||
## 代碼質量
|
||||
|
||||
### ESLint 配置
|
||||
|
||||
- JavaScript ES2020 規範
|
||||
- TypeScript 類型檢查
|
||||
- React Hooks 規則 (recommended-latest)
|
||||
- React Refresh 支持
|
||||
- 瀏覽器全局變數支持
|
||||
|
||||
### TypeScript 配置
|
||||
|
||||
- 目標: ES2020
|
||||
- 模塊: ESNext
|
||||
- JSX: React-JSX (自動導入)
|
||||
- 嚴格模式啟用
|
||||
- 路徑別名: @ → ./src
|
||||
|
||||
---
|
||||
|
||||
## 文件統計
|
||||
|
||||
- **總文件數**: 28 個
|
||||
- **源碼總大小**: 152 KB
|
||||
- **代碼行數**: ~2,702 行 (TS/TSX/JSON)
|
||||
- **UI 組件**: 6 個基礎組件
|
||||
- **業務組件**: 4 個
|
||||
- **頁面**: 6 個
|
||||
- **工具函數**: 多個
|
||||
|
||||
---
|
||||
|
||||
## 樣式實現要點
|
||||
|
||||
1. **Tailwind CSS 優先**: 所有樣式都使用 Tailwind 工具類
|
||||
2. **CSS 變數系統**: 使用 HSL 色彩空間,支持主題切換
|
||||
3. **響應式設計**: 內置 Tailwind 響應式前綴支持
|
||||
4. **可訪問性**: 焦點環、禁用狀態等 UI 反饋
|
||||
5. **暗色模式**: 通過 .dark 類支持主題切換
|
||||
6. **組件化**: UI 組件開箱即用,無需額外 CSS
|
||||
|
||||
---
|
||||
|
||||
## 總結
|
||||
|
||||
Tool_OCR 前端項目採用現代化的技術棧:
|
||||
|
||||
- **框架**: React 19 + TypeScript
|
||||
- **構建**: Vite (快速開發和構建)
|
||||
- **樣式**: Tailwind CSS 4 (工具類優先)
|
||||
- **狀態**: Zustand (輕量級狀態管理)
|
||||
- **數據**: React Query (服務器狀態管理)
|
||||
- **路由**: React Router v7 (應用導航)
|
||||
- **國際化**: i18next (多語言支持)
|
||||
- **質量**: ESLint + TypeScript (靜態檢查)
|
||||
|
||||
**核心特點**:
|
||||
1. 類型安全的開發環境
|
||||
2. 響應式 UI 設計
|
||||
3. 模塊化組件架構
|
||||
4. 完整的狀態管理
|
||||
5. API 層分離
|
||||
6. 國際化就位
|
||||
7. 開發友好 (HMR, 快速刷新)
|
||||
|
||||
@@ -1,652 +0,0 @@
|
||||
# Tool_OCR 前端代碼示例和最佳實踐
|
||||
|
||||
## 1. Tailwind CSS 樣式使用示例
|
||||
|
||||
### 佈局組件 (Layout.tsx 提取)
|
||||
```typescript
|
||||
// 導航欄樣式示例
|
||||
<header className="border-b bg-card">
|
||||
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold text-foreground">{t('app.title')}</h1>
|
||||
<button
|
||||
className="px-4 py-2 text-sm font-medium text-foreground hover:text-primary transition-colors"
|
||||
>
|
||||
{t('nav.logout')}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* 導航選項卡 */}
|
||||
<nav className="border-b bg-card">
|
||||
<div className="container mx-auto px-4">
|
||||
<ul className="flex space-x-1">
|
||||
{navLinks.map((link) => (
|
||||
<NavLink
|
||||
to={link.to}
|
||||
className={({ isActive }) =>
|
||||
`block px-4 py-3 text-sm font-medium transition-colors ${
|
||||
isActive
|
||||
? 'text-primary border-b-2 border-primary'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`
|
||||
}
|
||||
>
|
||||
{link.label}
|
||||
</NavLink>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
```
|
||||
|
||||
### 按鈕變體示例 (button.tsx)
|
||||
```typescript
|
||||
// 默認按鈕
|
||||
<Button>Save Changes</Button>
|
||||
|
||||
// 危險操作
|
||||
<Button variant="destructive">Delete</Button>
|
||||
|
||||
// 輪廓樣式
|
||||
<Button variant="outline">Cancel</Button>
|
||||
|
||||
// 幽靈按鈕
|
||||
<Button variant="ghost">Remove</Button>
|
||||
|
||||
// 鏈接樣式
|
||||
<Button variant="link">Learn More</Button>
|
||||
|
||||
// 不同尺寸
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="lg">Large</Button>
|
||||
<Button size="icon">+</Button>
|
||||
```
|
||||
|
||||
### 響應式卡片示例 (UploadPage.tsx 提取)
|
||||
```typescript
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg">
|
||||
{t('upload.selectedFiles')} ({selectedFiles.length})
|
||||
</CardTitle>
|
||||
<Button variant="outline" size="sm" onClick={handleClearAll}>
|
||||
{t('upload.clearAll')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
{selectedFiles.map((file, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between p-3 bg-muted rounded-md"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground truncate">
|
||||
{file.name}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatFileSize(file.size)}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={() => handleRemoveFile(index)}>
|
||||
{t('upload.removeFile')}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 文件上傳拖放區域 (FileUpload.tsx 提取)
|
||||
```typescript
|
||||
<Card
|
||||
{...getRootProps()}
|
||||
className={cn(
|
||||
'border-2 border-dashed transition-colors cursor-pointer hover:border-primary/50',
|
||||
{
|
||||
'border-primary bg-primary/5': isDragActive && !isDragReject,
|
||||
'border-destructive bg-destructive/5': isDragReject,
|
||||
'opacity-50 cursor-not-allowed': disabled,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="p-12 text-center">
|
||||
<input {...getInputProps()} />
|
||||
|
||||
<div className="mb-4">
|
||||
<svg className="mx-auto h-12 w-12 text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{isDragActive ? (
|
||||
<p className="text-lg font-medium text-primary">
|
||||
{isDragReject ? t('upload.invalidFiles') : t('upload.dropFilesHere')}
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-lg font-medium text-foreground">
|
||||
{t('upload.dragAndDrop')}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('upload.supportedFormats')}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. React Query 使用示例
|
||||
|
||||
### 批次狀態查詢 (ResultsPage.tsx 提取)
|
||||
```typescript
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
export default function ResultsPage() {
|
||||
const { batchId } = useUploadStore()
|
||||
const [selectedFileId, setSelectedFileId] = useState<number | null>(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 <div>Please upload files first</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<p>Loading batch status...</p>
|
||||
) : (
|
||||
<ResultsTable data={batchStatus?.files} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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<AuthState>()(
|
||||
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<UploadState>()(
|
||||
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 <token>
|
||||
|
||||
// 登出
|
||||
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 (
|
||||
<div>
|
||||
<h1>{t('upload.title')}</h1>
|
||||
<p>{t('upload.dragAndDrop')}</p>
|
||||
|
||||
{/* 帶插值的翻譯 */}
|
||||
<p>{t('upload.fileCount', { count: 5 })}</p>
|
||||
{/* 會渲染: "已選擇 5 個檔案" */}
|
||||
|
||||
<button>{t('upload.uploadButton')}</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 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 <Navigate to="/login" replace />
|
||||
}
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Routes>
|
||||
{/* 公開路由 */}
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
|
||||
{/* 受保護的路由 */}
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Layout />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
>
|
||||
<Route index element={<Navigate to="/upload" replace />} />
|
||||
<Route path="upload" element={<UploadPage />} />
|
||||
<Route path="processing" element={<ProcessingPage />} />
|
||||
<Route path="results" element={<ResultsPage />} />
|
||||
<Route path="export" element={<ExportPage />} />
|
||||
<Route path="settings" element={<SettingsPage />} />
|
||||
</Route>
|
||||
|
||||
{/* 全部匹配 */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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<string, any>
|
||||
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 (
|
||||
<div className="space-y-4">
|
||||
{/* JSX */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 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` 添加全局樣式
|
||||
|
||||
@@ -1,397 +0,0 @@
|
||||
# Tool_OCR 前端快速參考指南
|
||||
|
||||
## 文件位置速查表
|
||||
|
||||
### 主要文件位置
|
||||
```
|
||||
/Users/egg/Projects/Tool_OCR/frontend/
|
||||
├── src/
|
||||
│ ├── App.tsx # 路由定義
|
||||
│ ├── main.tsx # 應用入口
|
||||
│ ├── index.css # 全局樣式
|
||||
│ ├── components/
|
||||
│ │ ├── ui/ # UI 組件庫
|
||||
│ │ ├── Layout.tsx # 主佈局
|
||||
│ │ ├── FileUpload.tsx # 文件上傳
|
||||
│ │ └── ...
|
||||
│ ├── pages/ # 頁面
|
||||
│ ├── store/ # 狀態管理
|
||||
│ ├── services/api.ts # API 客戶端
|
||||
│ ├── types/api.ts # 類型定義
|
||||
│ ├── i18n/ # 國際化
|
||||
│ └── lib/utils.ts # 工具函數
|
||||
├── vite.config.ts # Vite 配置
|
||||
├── tailwind.config.js # Tailwind 配置
|
||||
├── tsconfig.json # TypeScript 配置
|
||||
└── package.json # 依賴管理
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 技術棧速查
|
||||
|
||||
| 功能 | 技術 | 版本 |
|
||||
|-----|------|------|
|
||||
| UI 框架 | React | 19.2.0 |
|
||||
| 語言 | TypeScript | 5.9.3 |
|
||||
| 構建 | Vite | 7.2.2 |
|
||||
| 樣式 | Tailwind CSS | 4.1.17 |
|
||||
| 狀態管理 | Zustand | 5.0.8 |
|
||||
| 數據獲取 | React Query | 5.90.7 |
|
||||
| 路由 | React Router | 7.9.5 |
|
||||
| 國際化 | i18next | 25.6.2 |
|
||||
| HTTP | Axios | 1.13.2 |
|
||||
| 檔案上傳 | react-dropzone | 14.3.8 |
|
||||
|
||||
---
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 開發
|
||||
cd frontend
|
||||
npm run dev # 啟動開發服務器 (localhost:12011)
|
||||
npm run lint # 代碼檢查
|
||||
npm run build # 生產構建
|
||||
|
||||
# 類型檢查
|
||||
npx tsc --noEmit # 檢查 TypeScript 錯誤
|
||||
|
||||
# 格式化
|
||||
npm install -D prettier # (如果需要)
|
||||
npx prettier --write src/ # 格式化代碼
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 路由結構
|
||||
|
||||
```
|
||||
/login → LoginPage (公開)
|
||||
/upload → UploadPage (受保護)
|
||||
/processing → ProcessingPage (受保護)
|
||||
/results → ResultsPage (受保護)
|
||||
/export → ExportPage (受保護)
|
||||
/settings → SettingsPage (受保護)
|
||||
```
|
||||
|
||||
### 添加新路由
|
||||
```typescript
|
||||
// 在 App.tsx 中
|
||||
<Route path="new-page" element={<NewPage />} />
|
||||
|
||||
// 在導航中
|
||||
{ to: '/new-page', label: t('nav.newPage') }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常用代碼片段
|
||||
|
||||
### 使用 i18n 翻譯
|
||||
```typescript
|
||||
const { t } = useTranslation()
|
||||
<h1>{t('key.path')}</h1>
|
||||
```
|
||||
|
||||
### 使用 Zustand 狀態
|
||||
```typescript
|
||||
const { batchId, setBatchId } = useUploadStore()
|
||||
const { user, logout } = useAuthStore()
|
||||
```
|
||||
|
||||
### 調用 API
|
||||
```typescript
|
||||
const data = await apiClient.uploadFiles(files)
|
||||
const status = await apiClient.getBatchStatus(batchId)
|
||||
```
|
||||
|
||||
### React Query 查詢
|
||||
```typescript
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['key', id],
|
||||
queryFn: () => apiClient.getData(id),
|
||||
})
|
||||
```
|
||||
|
||||
### Tailwind 樣式
|
||||
```typescript
|
||||
className="flex items-center justify-between p-4 bg-card rounded-lg"
|
||||
className={cn('base', { 'conditional': isActive })}
|
||||
```
|
||||
|
||||
### 提示通知
|
||||
```typescript
|
||||
const { toast } = useToast()
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Action completed',
|
||||
variant: 'success',
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 調試技巧
|
||||
|
||||
### 查看存儲在 localStorage 的狀態
|
||||
```javascript
|
||||
// 在瀏覽器控制台
|
||||
console.log(JSON.parse(localStorage.getItem('auth-storage')))
|
||||
console.log(JSON.parse(localStorage.getItem('tool-ocr-upload-store')))
|
||||
```
|
||||
|
||||
### 查看 React Query 緩存
|
||||
```javascript
|
||||
// 在瀏覽器控制台安裝 React Query DevTools
|
||||
// npm install @tanstack/react-query-devtools
|
||||
```
|
||||
|
||||
### TypeScript 錯誤
|
||||
```bash
|
||||
# 運行編譯器檢查類型
|
||||
npx tsc --noEmit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 環境變數配置
|
||||
|
||||
### 開發環境 (.env)
|
||||
```env
|
||||
VITE_API_BASE_URL=http://localhost:12010
|
||||
```
|
||||
|
||||
### 使用環境變數
|
||||
```typescript
|
||||
const baseURL = import.meta.env.VITE_API_BASE_URL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常見問題
|
||||
|
||||
### Q: 如何添加新的頁面?
|
||||
A:
|
||||
1. 在 `/src/pages` 創建 `NewPage.tsx`
|
||||
2. 在 `App.tsx` 添加路由
|
||||
3. 在 Layout.tsx 導航中添加鏈接
|
||||
|
||||
### Q: 如何修改主題顏色?
|
||||
A:
|
||||
1. 編輯 `src/index.css` 中的 CSS 變數
|
||||
2. 或編輯 `tailwind.config.js`
|
||||
|
||||
### Q: 如何添加新的翻譯?
|
||||
A:
|
||||
1. 編輯 `src/i18n/locales/zh-TW.json`
|
||||
2. 在組件中使用 `t('key.path')`
|
||||
|
||||
### Q: 如何調試 API 請求?
|
||||
A:
|
||||
1. 打開瀏覽器開發者工具 (Network 標籤)
|
||||
2. 檢查 `/api/v1/` 的請求
|
||||
|
||||
### Q: 上傳文件怎樣無法工作?
|
||||
A:
|
||||
1. 檢查後端是否在運行 (localhost:12010)
|
||||
2. 檢查文件格式是否被支持
|
||||
3. 檢查文件大小是否超過 50MB
|
||||
|
||||
### Q: 認證登錄失敗?
|
||||
A:
|
||||
1. 檢查用戶名和密碼
|
||||
2. 檢查後端認證端點 `/api/v1/auth/login`
|
||||
3. 檢查 localStorage 中的 token
|
||||
|
||||
---
|
||||
|
||||
## 依賴更新
|
||||
|
||||
```bash
|
||||
# 檢查過期依賴
|
||||
npm outdated
|
||||
|
||||
# 更新依賴
|
||||
npm update
|
||||
|
||||
# 更新到最新版本
|
||||
npm install --save-latest package-name
|
||||
|
||||
# 清除 node_modules 並重新安裝
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 性能優化
|
||||
|
||||
### 代碼分割
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react'
|
||||
|
||||
const NewPage = lazy(() => import('./pages/NewPage'))
|
||||
|
||||
// 在 Routes 中
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<NewPage />
|
||||
</Suspense>
|
||||
```
|
||||
|
||||
### 優化 React Query
|
||||
```typescript
|
||||
const { data } = useQuery({
|
||||
queryKey: ['items'],
|
||||
queryFn: () => apiClient.getItems(),
|
||||
staleTime: 1000 * 60 * 5, // 5 分鐘內不重新獲取
|
||||
gcTime: 1000 * 60 * 10, // 10 分鐘後從緩存中移除
|
||||
})
|
||||
```
|
||||
|
||||
### 優化組件
|
||||
```typescript
|
||||
// 使用 React.memo 避免不必要的重新渲染
|
||||
export default React.memo(function MyComponent(props) {
|
||||
return <div>{props.data}</div>
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 測試 (可選)
|
||||
|
||||
```bash
|
||||
# 安裝測試工具
|
||||
npm install -D vitest @testing-library/react
|
||||
|
||||
# 運行測試
|
||||
npm test
|
||||
|
||||
# 生成覆蓋率報告
|
||||
npm test -- --coverage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 構建和部署
|
||||
|
||||
```bash
|
||||
# 生產構建
|
||||
npm run build
|
||||
|
||||
# 預覽構建
|
||||
npm run preview
|
||||
|
||||
# 構建後的文件位置
|
||||
# dist/
|
||||
|
||||
# 部署
|
||||
# 將 dist/ 目錄上傳到服務器
|
||||
# 配置 Web 服務器支持 SPA (所有路由都指向 index.html)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 貢獻指南
|
||||
|
||||
### 代碼風格
|
||||
- 使用 TypeScript 進行類型安全
|
||||
- 遵循現有的命名約定
|
||||
- 優先使用 Tailwind CSS
|
||||
- 添加必要的類型定義
|
||||
|
||||
### 提交代碼
|
||||
```bash
|
||||
# 檢查代碼
|
||||
npm run lint
|
||||
|
||||
# 提交
|
||||
git add .
|
||||
git commit -m "description of changes"
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 有用的資源
|
||||
|
||||
### 文檔
|
||||
- [React 文檔](https://react.dev)
|
||||
- [Tailwind CSS](https://tailwindcss.com)
|
||||
- [React Router](https://reactrouter.com)
|
||||
- [Zustand](https://github.com/pmndrs/zustand)
|
||||
- [React Query](https://tanstack.com/query)
|
||||
- [i18next](https://www.i18next.com)
|
||||
|
||||
### 工具
|
||||
- [Vite 文檔](https://vitejs.dev)
|
||||
- [TypeScript 文檔](https://www.typescriptlang.org)
|
||||
- [ESLint 文檔](https://eslint.org)
|
||||
|
||||
### IDE 擴展 (VS Code)
|
||||
- Tailwind CSS IntelliSense
|
||||
- Thunder Client (API 測試)
|
||||
- React Developer Tools
|
||||
- Redux DevTools (狀態檢查)
|
||||
|
||||
---
|
||||
|
||||
## 快速開始
|
||||
|
||||
```bash
|
||||
# 1. 進入前端目錄
|
||||
cd /Users/egg/Projects/Tool_OCR/frontend
|
||||
|
||||
# 2. 安裝依賴
|
||||
npm install
|
||||
|
||||
# 3. 啟動開發服務器
|
||||
npm run dev
|
||||
|
||||
# 4. 打開瀏覽器
|
||||
# 訪問 http://localhost:12011
|
||||
|
||||
# 5. 登錄
|
||||
# 使用後端提供的帳號密碼
|
||||
|
||||
# 6. 開始使用
|
||||
# 上傳檔案 → 處理 → 查看結果 → 匯出
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常用快捷鍵
|
||||
|
||||
### VS Code
|
||||
- `Ctrl+Shift+F` (或 `Cmd+Shift+F` 在 Mac) - 全文搜索
|
||||
- `Ctrl+K Ctrl+X` - 刪除行
|
||||
- `Alt+Up/Down` - 移動行
|
||||
- `Ctrl+/` - 註解行
|
||||
|
||||
### 瀏覽器開發者工具
|
||||
- `F12` 或 `Ctrl+Shift+I` - 打開開發者工具
|
||||
- `Ctrl+Shift+C` - 元素檢查器
|
||||
- `Ctrl+Shift+M` - 響應式設計模式
|
||||
|
||||
---
|
||||
|
||||
## 總結速記
|
||||
|
||||
**架構**:React 19 + TypeScript + Vite
|
||||
**樣式**:Tailwind CSS 4
|
||||
**狀態**:Zustand (輕量) + React Query (數據)
|
||||
**路由**:React Router v7
|
||||
**API**:Axios 單例客戶端
|
||||
**國際化**:i18next (繁體中文)
|
||||
**開發端口**:12011
|
||||
**後端 API**:localhost:12010
|
||||
|
||||
**核心文件**:
|
||||
- App.tsx (路由)
|
||||
- main.tsx (入口)
|
||||
- services/api.ts (API)
|
||||
- store/*.ts (狀態)
|
||||
- pages/ (頁面)
|
||||
- components/ (組件)
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
# Frontend 快速啟動指南
|
||||
|
||||
> 5 分鐘完成前端環境設置和測試
|
||||
|
||||
---
|
||||
|
||||
## 步驟 1: 安裝依賴 (2 分鐘)
|
||||
|
||||
```bash
|
||||
cd d:\WORK\user_scrip\TOOL\Tool_OCR\frontend
|
||||
npm install
|
||||
```
|
||||
|
||||
**如果出現缺少套件警告,請手動安裝**:
|
||||
|
||||
```bash
|
||||
npm install class-variance-authority react-markdown
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 步驟 2: 啟動開發伺服器 (1 分鐘)
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**成功訊息**:
|
||||
```
|
||||
VITE v7.2.2 ready in XXX ms
|
||||
|
||||
➜ Local: http://localhost:12011/
|
||||
➜ Network: use --host to expose
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 步驟 3: 開啟瀏覽器測試 (2 分鐘)
|
||||
|
||||
### 1. 打開瀏覽器
|
||||
|
||||
訪問: **http://localhost:12011**
|
||||
|
||||
### 2. 測試登入頁面
|
||||
|
||||
- **預期畫面**: 美觀的雙欄登入介面
|
||||
- 左側: 品牌展示 + 特色介紹
|
||||
- 右側: 登入表單
|
||||
|
||||
![登入頁面預期效果]
|
||||
- 藍色漸層背景
|
||||
- 3 個特色亮點卡片
|
||||
- 統計數據 (99% / 10+ / 1M+)
|
||||
- 專業的輸入框和按鈕
|
||||
|
||||
### 3. 測試上傳頁面 (需先登入)
|
||||
|
||||
**測試帳號** (請確認後端 API 已啟動):
|
||||
- 使用後端設定的測試帳號登入
|
||||
|
||||
**預期功能**:
|
||||
- 拖放區域顯示正常
|
||||
- 可以拖放檔案
|
||||
- 檔案清單顯示
|
||||
- 上傳按鈕可點擊
|
||||
|
||||
### 4. 測試處理頁面
|
||||
|
||||
**預期功能**:
|
||||
- 進度條動畫
|
||||
- 統計卡片 (已完成/處理中/失敗/總計)
|
||||
- 檔案狀態清單
|
||||
- 自動更新進度 (每 2 秒)
|
||||
|
||||
### 5. 測試結果頁面
|
||||
|
||||
**預期功能**:
|
||||
- 左側檔案清單
|
||||
- 右側預覽面板
|
||||
- Markdown / JSON 切換
|
||||
- 統計資訊卡片
|
||||
|
||||
---
|
||||
|
||||
## 常見問題排除
|
||||
|
||||
### Q1: npm install 失敗
|
||||
|
||||
**解決方案**:
|
||||
```bash
|
||||
# 清除快取
|
||||
npm cache clean --force
|
||||
|
||||
# 刪除 node_modules 和 package-lock.json
|
||||
rm -rf node_modules package-lock.json
|
||||
|
||||
# 重新安裝
|
||||
npm install
|
||||
```
|
||||
|
||||
### Q2: Vite 啟動失敗
|
||||
|
||||
**檢查事項**:
|
||||
- Node.js 版本 >= 18.0.0
|
||||
```bash
|
||||
node -v
|
||||
```
|
||||
- 端口 12011 是否被佔用
|
||||
```bash
|
||||
netstat -ano | findstr 12011
|
||||
```
|
||||
|
||||
### Q3: 頁面顯示空白
|
||||
|
||||
**檢查事項**:
|
||||
1. 瀏覽器控制台是否有錯誤訊息 (F12)
|
||||
2. 後端 API 是否已啟動 (http://localhost:12010)
|
||||
3. 網路請求是否正常 (Network 標籤)
|
||||
|
||||
### Q4: 登入失敗
|
||||
|
||||
**確認事項**:
|
||||
- 後端 API 服務已啟動
|
||||
- 測試帳號正確
|
||||
- 網路請求到達後端 (檢查 Network)
|
||||
|
||||
---
|
||||
|
||||
## 後續步驟
|
||||
|
||||
### 開發模式
|
||||
|
||||
```bash
|
||||
# 前端開發 (自動熱更新)
|
||||
npm run dev
|
||||
|
||||
# 程式碼檢查
|
||||
npm run lint
|
||||
|
||||
# TypeScript 型別檢查
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 生產建置
|
||||
|
||||
```bash
|
||||
# 建置
|
||||
npm run build
|
||||
|
||||
# 預覽建置結果
|
||||
npm run preview
|
||||
```
|
||||
|
||||
### 查看文件
|
||||
|
||||
1. **FRONTEND_API.md** - 完整前端架構和 API 整合文件
|
||||
2. **FRONTEND_UPGRADE_SUMMARY.md** - 升級完成報告
|
||||
3. **frontend/README.md** - 使用手冊
|
||||
|
||||
---
|
||||
|
||||
## 測試清單
|
||||
|
||||
完成以下測試確認功能正常:
|
||||
|
||||
- [ ] 前端開發伺服器啟動成功
|
||||
- [ ] 登入頁面顯示正常
|
||||
- [ ] 可以輸入使用者名稱和密碼
|
||||
- [ ] 所有頁面路由正常 (upload, processing, results, export, settings)
|
||||
- [ ] UI 組件樣式正確 (按鈕、卡片、輸入框等)
|
||||
- [ ] 響應式佈局正常 (調整瀏覽器視窗大小)
|
||||
|
||||
---
|
||||
|
||||
## 需要幫助?
|
||||
|
||||
參考以下文件:
|
||||
- **技術問題**: 查看 `FRONTEND_API.md` 的 "Troubleshooting" 章節
|
||||
- **API 整合**: 查看 `FRONTEND_API.md` 的 "API Integration Patterns"
|
||||
- **部署問題**: 查看 `frontend/README.md` 的 "部署說明"
|
||||
|
||||
---
|
||||
|
||||
**預祝使用順利!**
|
||||
@@ -1,185 +0,0 @@
|
||||
# Tool_OCR 前端文檔索引
|
||||
|
||||
歡迎查閱 Tool_OCR 前端項目的完整文檔。本頁面將幫助您快速找到所需的信息。
|
||||
|
||||
## 文檔列表
|
||||
|
||||
### 1. FRONTEND_ANALYSIS.md
|
||||
**完整的技術棧和項目結構分析**
|
||||
|
||||
包含內容:
|
||||
- 項目概況和技術棧詳解
|
||||
- 完整的目錄結構說明
|
||||
- 核心頁面和功能概述
|
||||
- 組件架構設計
|
||||
- 狀態管理方案 (Zustand)
|
||||
- API 服務層設計
|
||||
- 類型定義系統
|
||||
- 國際化配置
|
||||
- 路由結構
|
||||
- 依賴概覽
|
||||
- 代碼質量檢查
|
||||
- 樣式實現要點
|
||||
|
||||
**適合閱讀場景:**
|
||||
- 全新成員了解項目整體架構
|
||||
- 深入理解技術選擇和設計決策
|
||||
- 查看完整的模塊組織結構
|
||||
- 了解樣式系統的設計方式
|
||||
|
||||
### 2. FRONTEND_CODE_EXAMPLES.md
|
||||
**實際代碼示例和最佳實踐**
|
||||
|
||||
包含內容:
|
||||
- Tailwind CSS 使用示例 (佈局、按鈕、卡片等)
|
||||
- React Query 數據獲取示例
|
||||
- Zustand 狀態管理示例
|
||||
- API 客戶端使用示例
|
||||
- 國際化集成示例
|
||||
- 路由和路由保護示例
|
||||
- TypeScript 類型定義示例
|
||||
- 最佳實踐指南 (組件結構、錯誤處理等)
|
||||
- 環境變數配置
|
||||
- 常見開發流程
|
||||
|
||||
**適合閱讀場景:**
|
||||
- 開發新功能時參考代碼模式
|
||||
- 尋找特定技術的使用示例
|
||||
- 學習項目內的最佳實踐
|
||||
- 複製粘貼代碼片段快速開發
|
||||
|
||||
### 3. FRONTEND_QUICK_REFERENCE.md
|
||||
**快速查閱和常見問題**
|
||||
|
||||
包含內容:
|
||||
- 文件位置速查表
|
||||
- 技術棧版本速查
|
||||
- 常用開發命令
|
||||
- 路由結構速查
|
||||
- 常用代碼片段
|
||||
- 調試技巧
|
||||
- 常見問題解答 (FAQ)
|
||||
- 依賴更新指南
|
||||
- 性能優化建議
|
||||
- 構建和部署指南
|
||||
- 有用資源鏈接
|
||||
|
||||
**適合閱讀場景:**
|
||||
- 快速查找文件位置
|
||||
- 尋找常用命令
|
||||
- 解決常見問題
|
||||
- 快速參考語法
|
||||
- 了解開發工作流
|
||||
|
||||
## 快速開始路線圖
|
||||
|
||||
### 如果您是新開發者:
|
||||
1. 先讀 **FRONTEND_QUICK_REFERENCE.md** 的「快速開始」部分
|
||||
2. 再讀 **FRONTEND_ANALYSIS.md** 了解整體架構
|
||||
3. 需要時查閱 **FRONTEND_CODE_EXAMPLES.md** 的相關示例
|
||||
|
||||
### 如果您需要開發新功能:
|
||||
1. 查閱 **FRONTEND_QUICK_REFERENCE.md** 的「常用代碼片段」
|
||||
2. 參考 **FRONTEND_CODE_EXAMPLES.md** 的相關示例
|
||||
3. 根據功能類型選擇合適的設計模式
|
||||
|
||||
### 如果您遇到問題:
|
||||
1. 查閱 **FRONTEND_QUICK_REFERENCE.md** 的「常見問題」
|
||||
2. 查閱 **FRONTEND_QUICK_REFERENCE.md** 的「調試技巧」
|
||||
3. 參考 **FRONTEND_CODE_EXAMPLES.md** 的「最佳實踐」
|
||||
|
||||
## 項目核心信息速記
|
||||
|
||||
### 技術棧
|
||||
- **UI 框架**: React 19.2.0 + TypeScript
|
||||
- **構建工具**: Vite 7.2.2
|
||||
- **樣式**: Tailwind CSS 4.1.17
|
||||
- **狀態管理**: Zustand 5.0.8 + React Query 5.90.7
|
||||
- **路由**: React Router v7.9.5
|
||||
- **HTTP**: Axios
|
||||
- **國際化**: i18next (繁體中文)
|
||||
|
||||
### 開發環境
|
||||
- **開發端口**: 12011
|
||||
- **後端 API**: localhost:12010
|
||||
- **命令**: `npm run dev` | `npm run build` | `npm run lint`
|
||||
|
||||
### 主要頁面
|
||||
- `/login` - 登錄頁面
|
||||
- `/upload` - 文件上傳
|
||||
- `/processing` - OCR 處理
|
||||
- `/results` - 結果查看
|
||||
- `/export` - 結果導出
|
||||
- `/settings` - 系統設置
|
||||
|
||||
### 核心目錄
|
||||
- `/components` - React 組件
|
||||
- `/pages` - 應用頁面
|
||||
- `/store` - 狀態管理 (Zustand)
|
||||
- `/services` - API 客戶端
|
||||
- `/types` - TypeScript 類型
|
||||
- `/i18n` - 國際化配置
|
||||
|
||||
## 文檔使用建議
|
||||
|
||||
### 打開文檔
|
||||
所有文檔都保存在項目根目錄:
|
||||
```
|
||||
/Users/egg/Projects/Tool_OCR/FRONTEND_*.md
|
||||
```
|
||||
|
||||
推薦使用 VS Code 或任何 Markdown 編輯器打開。
|
||||
|
||||
### 導航技巧
|
||||
- 使用 Markdown 編輯器的大綱功能快速導航
|
||||
- 使用 Ctrl+F (或 Cmd+F) 搜索關鍵詞
|
||||
- 點擊標題鏈接快速跳轉
|
||||
|
||||
### 保持同步
|
||||
文檔基於以下源代碼分析:
|
||||
- React 19.2.0
|
||||
- Vite 7.2.2
|
||||
- Tailwind CSS 4.1.17
|
||||
- 其他依賴均按 package.json 記錄
|
||||
|
||||
如果依賴升級或架構調整,請相應更新文檔。
|
||||
|
||||
## 常用快速鏈接
|
||||
|
||||
### 官方文檔
|
||||
- [React 官方文檔](https://react.dev)
|
||||
- [Tailwind CSS](https://tailwindcss.com)
|
||||
- [Vite 文檔](https://vitejs.dev)
|
||||
- [React Router](https://reactrouter.com)
|
||||
- [Zustand](https://github.com/pmndrs/zustand)
|
||||
- [React Query](https://tanstack.com/query)
|
||||
- [i18next](https://www.i18next.com)
|
||||
|
||||
### IDE 推薦擴展 (VS Code)
|
||||
- Tailwind CSS IntelliSense
|
||||
- ES7+ React/Redux/React-Native snippets
|
||||
- Thunder Client (API 測試)
|
||||
- TypeScript Vue Plugin
|
||||
- ESLint
|
||||
|
||||
## 文檔維護
|
||||
|
||||
這些文檔是對項目當前狀態的快照。當進行以下操作時應考慮更新:
|
||||
|
||||
- 升級主要依賴版本
|
||||
- 更改項目結構
|
||||
- 添加新的關鍵功能
|
||||
- 改變設計模式或最佳實踐
|
||||
- 優化或重構大型模塊
|
||||
|
||||
## 反饋和建議
|
||||
|
||||
如果發現文檔中有錯誤或遺漏,歡迎:
|
||||
1. 直接編輯文檔並提交 PR
|
||||
2. 創建 Issue 報告問題
|
||||
3. 聯繫專案維護者
|
||||
|
||||
---
|
||||
|
||||
最後更新: 2024年11月12日
|
||||
文檔覆蓋版本: React 19.2.0, Vite 7.2.2, Tailwind CSS 4.1.17
|
||||
@@ -1,650 +0,0 @@
|
||||
# Frontend 現代化升級完成報告
|
||||
|
||||
> **完成日期**: 2025-01-13
|
||||
> **專案**: Tool_OCR Frontend Modernization
|
||||
> **目標**: 提供企業級專業 UI/UX 體驗
|
||||
|
||||
---
|
||||
|
||||
## 執行摘要
|
||||
|
||||
本次升級成功將 Tool_OCR 前端介面從基礎架構提升為現代化、專業的企業級應用程式。所有核心功能已整合完畢,UI/UX 設計符合現代設計標準,並提供完整的 API 整合文件。
|
||||
|
||||
### 升級成果
|
||||
|
||||
- ✅ **專業 UI/UX 設計系統**
|
||||
- ✅ **完整 shadcn/ui 組件庫**
|
||||
- ✅ **現代化頁面組件**
|
||||
- ✅ **完整 API 整合文件**
|
||||
- ✅ **工具函式庫**
|
||||
- ✅ **響應式設計**
|
||||
- ✅ **錯誤處理機制**
|
||||
- ✅ **部署就緒配置**
|
||||
|
||||
---
|
||||
|
||||
## 一、新增檔案清單
|
||||
|
||||
### 1. 核心工具檔案
|
||||
|
||||
| 檔案路徑 | 用途 | 說明 |
|
||||
|---------|------|------|
|
||||
| `frontend/src/lib/utils.ts` | 工具函式庫 | cn(), formatFileSize(), formatDate(), downloadBlob() 等實用函式 |
|
||||
|
||||
**主要功能**:
|
||||
```typescript
|
||||
// Class name 合併
|
||||
cn(...classes) -> string
|
||||
|
||||
// 檔案大小格式化
|
||||
formatFileSize(bytes) -> "10.5 MB"
|
||||
|
||||
// 日期格式化
|
||||
formatDate(date) -> "2025/01/13 14:30"
|
||||
|
||||
// 處理時間格式化
|
||||
formatProcessingTime(ms) -> "2 分 30 秒"
|
||||
|
||||
// 檔案類型顏色
|
||||
getFileTypeColor(filename) -> "text-red-500"
|
||||
|
||||
// Blob 下載
|
||||
downloadBlob(blob, filename) -> void
|
||||
|
||||
// Debounce / Throttle
|
||||
debounce(func, wait) -> function
|
||||
throttle(func, limit) -> function
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. shadcn/ui 組件庫補充
|
||||
|
||||
| 檔案路徑 | 組件名稱 | 主要用途 |
|
||||
|---------|---------|---------|
|
||||
| `frontend/src/components/ui/input.tsx` | Input | 文字輸入框 |
|
||||
| `frontend/src/components/ui/label.tsx` | Label | 表單標籤 |
|
||||
| `frontend/src/components/ui/select.tsx` | Select | 下拉選單 |
|
||||
| `frontend/src/components/ui/alert.tsx` | Alert | 提示訊息 (info/success/warning/error) |
|
||||
| `frontend/src/components/ui/dialog.tsx` | Dialog | 對話框彈窗 |
|
||||
| `frontend/src/components/ui/tabs.tsx` | Tabs | 分頁標籤 |
|
||||
|
||||
**組件特色**:
|
||||
- ✅ TypeScript 完整型別支援
|
||||
- ✅ Tailwind CSS 樣式整合
|
||||
- ✅ 支援多種 variant (變體)
|
||||
- ✅ 無障礙設計 (ARIA)
|
||||
- ✅ 鍵盤導航支援
|
||||
|
||||
**Alert 組件範例**:
|
||||
```tsx
|
||||
<Alert variant="success">
|
||||
<AlertTitle>成功</AlertTitle>
|
||||
<AlertDescription>檔案上傳完成</AlertDescription>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
**Dialog 組件範例**:
|
||||
```tsx
|
||||
<Dialog open={isOpen} onClose={() => setIsOpen(false)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>確認刪除</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogBody>
|
||||
<p>確定要刪除此規則嗎?</p>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setIsOpen(false)}>取消</Button>
|
||||
<Button variant="destructive" onClick={handleDelete}>刪除</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
**Tabs 組件範例**:
|
||||
```tsx
|
||||
<Tabs defaultValue="markdown">
|
||||
<TabsList>
|
||||
<TabsTrigger value="markdown">Markdown</TabsTrigger>
|
||||
<TabsTrigger value="json">JSON</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="markdown">
|
||||
<MarkdownPreview content={content} />
|
||||
</TabsContent>
|
||||
<TabsContent value="json">
|
||||
<JSONViewer data={data} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 文件檔案
|
||||
|
||||
| 檔案路徑 | 用途 | 說明 |
|
||||
|---------|------|------|
|
||||
| `FRONTEND_API.md` | 完整前端 API 文件 | **核心交付成果**,詳細記錄所有架構、組件、API 整合 |
|
||||
| `frontend/README.md` | 前端使用手冊 | 快速開始、功能介紹、部署指南 (已更新) |
|
||||
|
||||
---
|
||||
|
||||
## 二、修改檔案清單
|
||||
|
||||
### 1. 依賴套件更新
|
||||
|
||||
**檔案**: `frontend/package.json`
|
||||
|
||||
**新增依賴**:
|
||||
```json
|
||||
{
|
||||
"class-variance-authority": "^0.7.0", // 組件變體管理
|
||||
"react-markdown": "^9.0.1" // Markdown 渲染
|
||||
}
|
||||
```
|
||||
|
||||
**用途**:
|
||||
- `class-variance-authority`: 用於 Alert 等組件的多變體樣式管理
|
||||
- `react-markdown`: 在 ResultsPage 渲染 OCR 結果的 Markdown 格式
|
||||
|
||||
---
|
||||
|
||||
### 2. 前端 README 更新
|
||||
|
||||
**檔案**: `frontend/README.md`
|
||||
|
||||
**更新內容**:
|
||||
- 從預設 Vite 模板替換為專案專用文件
|
||||
- 新增詳細功能介紹
|
||||
- 新增技術棧說明
|
||||
- 新增專案結構圖
|
||||
- 新增開發指南和部署說明
|
||||
|
||||
---
|
||||
|
||||
## 三、UI/UX 設計系統
|
||||
|
||||
### 1. 配色方案
|
||||
|
||||
現有的 `frontend/src/styles/index.css` 已經包含完整的設計系統:
|
||||
|
||||
```css
|
||||
/* 主色調 - 專業藍色 */
|
||||
--primary: 217 91% 60%; /* #3b82f6 */
|
||||
|
||||
/* 狀態色 */
|
||||
--success: 142 72% 45%; /* 綠色 */
|
||||
--destructive: 0 85% 60%; /* 紅色 */
|
||||
--warning: 38 92% 50%; /* 黃色 */
|
||||
--accent: 173 80% 50%; /* 青色 */
|
||||
|
||||
/* 背景色 */
|
||||
--background: 220 15% 97%; /* 淺灰 */
|
||||
--card: 0 0% 100%; /* 白色 */
|
||||
--sidebar: 220 25% 12%; /* 深藍灰 */
|
||||
```
|
||||
|
||||
### 2. 現有頁面分析
|
||||
|
||||
#### LoginPage (已完成 ✅)
|
||||
- **狀態**: 已經非常專業現代化
|
||||
- **特色**:
|
||||
- 雙欄設計 (左側品牌展示 + 右側登入表單)
|
||||
- 漸層背景 + 圖案裝飾
|
||||
- 特色展示 (高精度識別、快速處理、安全可靠)
|
||||
- 統計數據展示 (99% 準確率、10+ 格式、1M+ 文件)
|
||||
- 響應式設計 (桌面/平板/手機)
|
||||
|
||||
#### Layout (已完成 ✅)
|
||||
- **狀態**: 專業側邊欄 + 頂欄設計
|
||||
- **特色**:
|
||||
- 固定側邊欄導航
|
||||
- 活動狀態高亮顯示
|
||||
- 使用者資訊區塊
|
||||
- 搜尋欄 + 通知鈴鐺
|
||||
- 響應式佈局
|
||||
|
||||
#### UploadPage (已完成 ✅)
|
||||
- **狀態**: 現代化拖放上傳介面
|
||||
- **特色**:
|
||||
- 步驟指示器 (3 步驟)
|
||||
- FileUpload 組件 (漸層動畫、圖示動態變化)
|
||||
- 檔案清單卡片 (檔案圖示、大小、狀態標籤)
|
||||
- 上傳進度條
|
||||
- 批次操作按鈕
|
||||
|
||||
#### ProcessingPage (已完成 ✅)
|
||||
- **狀態**: 即時進度追蹤儀表板
|
||||
- **特色**:
|
||||
- 批次進度條 (百分比顯示)
|
||||
- 4 個統計卡片 (已完成/處理中/失敗/總計)
|
||||
- 檔案狀態清單 (即時更新)
|
||||
- 自動輪詢 (每 2 秒)
|
||||
- 自動跳轉到結果頁面
|
||||
|
||||
#### ResultsPage (已完成 ✅)
|
||||
- **狀態**: 現有版本已經很好,無需大幅改動
|
||||
- **特色**:
|
||||
- 雙欄佈局 (2:3 比例)
|
||||
- 檔案清單 + 預覽面板
|
||||
- Markdown 和 JSON 雙視圖
|
||||
- 統計卡片 (準確率、處理時間、文字區塊)
|
||||
|
||||
---
|
||||
|
||||
## 四、已整合的 API 端點
|
||||
|
||||
所有頁面已完整整合以下 API:
|
||||
|
||||
### 認證相關
|
||||
- `POST /api/v1/auth/login` - 使用者登入
|
||||
|
||||
### 檔案上傳
|
||||
- `POST /api/v1/upload` - 批次上傳檔案
|
||||
|
||||
### OCR 處理
|
||||
- `POST /api/v1/ocr/process` - 開始 OCR 處理
|
||||
- `GET /api/v1/batch/{batch_id}/status` - 查詢批次狀態 (輪詢)
|
||||
- `GET /api/v1/ocr/result/{file_id}` - 取得 OCR 結果
|
||||
|
||||
### 匯出功能
|
||||
- `POST /api/v1/export` - 匯出批次結果
|
||||
- `GET /api/v1/export/pdf/{file_id}` - 下載 PDF
|
||||
- `GET /api/v1/export/rules` - 取得匯出規則清單
|
||||
- `POST /api/v1/export/rules` - 建立匯出規則
|
||||
- `PUT /api/v1/export/rules/{rule_id}` - 更新匯出規則
|
||||
- `DELETE /api/v1/export/rules/{rule_id}` - 刪除匯出規則
|
||||
- `GET /api/v1/export/css-templates` - 取得 CSS 模板清單
|
||||
|
||||
---
|
||||
|
||||
## 五、響應式設計
|
||||
|
||||
### 支援的螢幕尺寸
|
||||
|
||||
| 裝置類型 | 斷點 | 支援程度 | 說明 |
|
||||
|---------|------|---------|------|
|
||||
| Desktop | >= 1024px | ✅ 完整支援 | 主要開發目標 |
|
||||
| Tablet | 768px - 1023px | ✅ 良好支援 | 佈局自動調整 |
|
||||
| Mobile | < 768px | ⚠️ 基礎支援 | 可選功能 |
|
||||
|
||||
### 響應式特性
|
||||
|
||||
- **Login Page**: 桌面雙欄 → 平板/手機單欄
|
||||
- **Layout**: 桌面固定側邊欄 → 平板/手機可折疊
|
||||
- **Upload Page**: 桌面大卡片 → 平板/手機堆疊
|
||||
- **Results Page**: 桌面雙欄 → 平板/手機堆疊
|
||||
|
||||
---
|
||||
|
||||
## 六、錯誤處理機制
|
||||
|
||||
### 1. API 錯誤處理
|
||||
|
||||
```typescript
|
||||
// Axios 攔截器
|
||||
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)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Toast 通知系統
|
||||
|
||||
```typescript
|
||||
// 成功訊息
|
||||
toast({
|
||||
title: '上傳成功',
|
||||
description: '已上傳 5 個檔案',
|
||||
variant: 'success',
|
||||
})
|
||||
|
||||
// 錯誤訊息
|
||||
toast({
|
||||
title: '上傳失敗',
|
||||
description: error.response?.data?.detail || '網路錯誤',
|
||||
variant: 'destructive',
|
||||
})
|
||||
```
|
||||
|
||||
### 3. Alert 組件提示
|
||||
|
||||
```tsx
|
||||
// 頁面級別警告
|
||||
<Alert variant="warning">
|
||||
<AlertCircle className="h-5 w-5" />
|
||||
<AlertTitle>注意</AlertTitle>
|
||||
<AlertDescription>
|
||||
部分檔案處理失敗,請檢查錯誤訊息
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
### 4. 載入狀態
|
||||
|
||||
```tsx
|
||||
// React Query 自動處理
|
||||
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 />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、效能優化
|
||||
|
||||
### 已實施的優化
|
||||
|
||||
1. **React Query 快取**: 減少重複 API 呼叫
|
||||
2. **輪詢最佳化**: 處理完成後自動停止輪詢
|
||||
3. **Code Splitting**: 按路由分割程式碼 (Vite 自動處理)
|
||||
4. **CSS 最佳化**: Tailwind CSS 生產建置時自動 purge
|
||||
|
||||
### 建議的未來優化
|
||||
|
||||
- [ ] 使用 React.memo() 避免不必要的重渲染
|
||||
- [ ] 圖片懶載入
|
||||
- [ ] 虛擬滾動 (長清單)
|
||||
- [ ] Service Worker (PWA)
|
||||
|
||||
---
|
||||
|
||||
## 八、無障礙設計
|
||||
|
||||
### WCAG 2.1 AA 合規性
|
||||
|
||||
- ✅ **鍵盤導航**: 所有互動元素可用 Tab/Enter 操作
|
||||
- ✅ **焦點指示器**: 明顯的焦點框
|
||||
- ✅ **ARIA 標籤**: 所有圖示按鈕有 aria-label
|
||||
- ✅ **色彩對比**: 文字對比度 >= 4.5:1
|
||||
- ✅ **語意化 HTML**: 使用 `<nav>`, `<main>`, `<button>` 等標籤
|
||||
|
||||
---
|
||||
|
||||
## 九、部署就緒
|
||||
|
||||
### 1. 環境變數配置
|
||||
|
||||
```bash
|
||||
# .env.production
|
||||
VITE_API_BASE_URL=http://localhost:12010
|
||||
VITE_APP_NAME=Tool_OCR
|
||||
```
|
||||
|
||||
### 2. 建置指令
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
輸出目錄: `frontend/dist/`
|
||||
|
||||
### 3. Nginx 配置範例
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name tool-ocr.example.com;
|
||||
root /var/www/tool-ocr;
|
||||
|
||||
# SPA 路由支援
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# API 反向代理
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:12010;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# 靜態資源快取
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、核心交付成果: FRONTEND_API.md
|
||||
|
||||
### 文件結構
|
||||
|
||||
`FRONTEND_API.md` 是本次升級的**最重要交付成果**,包含:
|
||||
|
||||
1. **Project Overview** - 專案概述
|
||||
2. **Technology Stack** - 完整技術棧清單
|
||||
3. **Component Architecture** - 組件架構
|
||||
4. **Page → API Dependency Matrix** - **頁面與 API 依賴對照表**
|
||||
5. **Component Tree Structure** - **完整組件樹狀結構**
|
||||
6. **State Management Strategy** - 狀態管理策略
|
||||
7. **Route Configuration** - 路由配置
|
||||
8. **API Integration Patterns** - **API 整合模式和流程**
|
||||
9. **UI/UX Design System** - **設計系統規範**
|
||||
10. **Error Handling Patterns** - 錯誤處理模式
|
||||
11. **Deployment Configuration** - 部署配置
|
||||
|
||||
### 重要章節亮點
|
||||
|
||||
#### Page → API Dependency Matrix
|
||||
|
||||
完整對照每個頁面使用的 API 端點:
|
||||
|
||||
```markdown
|
||||
| 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/ocr/result/{file_id} | GET | Get OCR result details | No |
|
||||
```
|
||||
|
||||
#### API Integration Patterns
|
||||
|
||||
詳細的流程範例:
|
||||
|
||||
```typescript
|
||||
// 1. 登入流程
|
||||
const response = await apiClient.login({ username, password })
|
||||
localStorage.setItem('auth_token', response.access_token)
|
||||
setUser({ id: 1, username })
|
||||
navigate('/upload')
|
||||
|
||||
// 2. 檔案上傳流程
|
||||
const formData = new FormData()
|
||||
files.forEach((file) => formData.append('files', file))
|
||||
const response = await apiClient.uploadFiles(files)
|
||||
setBatchId(response.batch_id)
|
||||
navigate('/processing')
|
||||
|
||||
// 3. OCR 處理 + 輪詢流程
|
||||
await apiClient.processOCR({ batch_id, lang: 'ch' })
|
||||
const { data } = useQuery({
|
||||
queryKey: ['batchStatus', batchId],
|
||||
queryFn: () => apiClient.getBatchStatus(batchId),
|
||||
refetchInterval: 2000, // 每 2 秒輪詢
|
||||
})
|
||||
```
|
||||
|
||||
#### Component Tree Structure
|
||||
|
||||
完整的組件樹狀結構:
|
||||
|
||||
```
|
||||
App
|
||||
├── Router
|
||||
│ ├── PublicRoute
|
||||
│ │ └── LoginPage
|
||||
│ └── ProtectedRoute
|
||||
│ └── Layout
|
||||
│ ├── Sidebar
|
||||
│ │ ├── Logo
|
||||
│ │ ├── Navigation Links
|
||||
│ │ └── User Section
|
||||
│ ├── TopBar
|
||||
│ └── MainContent
|
||||
│ ├── UploadPage
|
||||
│ ├── ProcessingPage
|
||||
│ ├── ResultsPage
|
||||
│ └── ExportPage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十一、下一步建議
|
||||
|
||||
### 立即可做 (Priority 1)
|
||||
|
||||
1. **安裝新依賴**:
|
||||
```bash
|
||||
cd frontend
|
||||
npm install class-variance-authority react-markdown
|
||||
```
|
||||
|
||||
2. **測試開發環境**:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
開啟 http://localhost:12011 測試所有功能
|
||||
|
||||
3. **測試生產建置**:
|
||||
```bash
|
||||
npm run build
|
||||
npm run preview
|
||||
```
|
||||
|
||||
### 短期優化 (Priority 2)
|
||||
|
||||
1. **ExportPage 優化**: 添加更豐富的匯出選項 UI
|
||||
2. **SettingsPage 完善**: 實作規則管理的 CRUD UI
|
||||
3. **Toast 系統完善**: 確保所有操作都有適當的使用者回饋
|
||||
|
||||
### 長期規劃 (Priority 3)
|
||||
|
||||
1. **實作單元測試**: 使用 Vitest + React Testing Library
|
||||
2. **E2E 測試**: 使用 Playwright 測試完整使用者流程
|
||||
3. **暗黑模式**: 新增主題切換功能
|
||||
4. **PWA 支援**: Service Worker + 離線快取
|
||||
5. **翻譯功能**: 實作 Phase 5 的翻譯 UI
|
||||
|
||||
---
|
||||
|
||||
## 十二、品質保證檢查清單
|
||||
|
||||
### 功能完整性
|
||||
|
||||
- [x] 登入 / 認證
|
||||
- [x] 檔案上傳 (拖放 + 點擊選擇)
|
||||
- [x] OCR 處理追蹤 (即時進度)
|
||||
- [x] 結果預覽 (Markdown + JSON)
|
||||
- [x] 批次匯出
|
||||
- [x] 規則管理 (基礎)
|
||||
|
||||
### UI/UX 品質
|
||||
|
||||
- [x] 現代化設計風格
|
||||
- [x] 專業配色方案
|
||||
- [x] 一致的組件樣式
|
||||
- [x] 清晰的視覺層次
|
||||
- [x] 適當的間距和排版
|
||||
- [x] 流暢的動畫效果
|
||||
|
||||
### 技術品質
|
||||
|
||||
- [x] TypeScript 型別安全
|
||||
- [x] React Query 狀態管理
|
||||
- [x] Zustand 客戶端狀態
|
||||
- [x] Axios API 整合
|
||||
- [x] 錯誤處理機制
|
||||
- [x] 載入狀態處理
|
||||
|
||||
### 文件完整性
|
||||
|
||||
- [x] FRONTEND_API.md (核心文件)
|
||||
- [x] frontend/README.md (使用手冊)
|
||||
- [x] API_REFERENCE.md (後端 API)
|
||||
- [x] 程式碼註解 (關鍵邏輯)
|
||||
|
||||
### 部署就緒
|
||||
|
||||
- [x] 環境變數配置
|
||||
- [x] 建置腳本
|
||||
- [x] Nginx 配置範例
|
||||
- [x] 部署文件
|
||||
|
||||
---
|
||||
|
||||
## 十三、已知限制
|
||||
|
||||
1. **無 WebSocket**: 使用 HTTP 輪詢,未來可升級
|
||||
2. **無離線支援**: 需要網路連線
|
||||
3. **手機端優化**: 主要針對桌面/平板設計
|
||||
4. **翻譯功能**: 僅 UI Stub,後端未實作
|
||||
5. **深色模式**: 尚未實作
|
||||
|
||||
---
|
||||
|
||||
## 十四、總結
|
||||
|
||||
### 完成項目統計
|
||||
|
||||
- **新增檔案**: 7 個 (utils.ts + 6 個 UI 組件)
|
||||
- **修改檔案**: 2 個 (package.json + README.md)
|
||||
- **文件檔案**: 2 個 (FRONTEND_API.md + 本報告)
|
||||
- **程式碼行數**: 約 1,500+ 行 (新增組件 + 工具函式)
|
||||
- **文件行數**: 約 1,200+ 行 (FRONTEND_API.md)
|
||||
|
||||
### 技術亮點
|
||||
|
||||
1. **現代化技術棧**: React 18 + Vite + TypeScript + Tailwind CSS
|
||||
2. **企業級組件庫**: shadcn/ui 完整整合
|
||||
3. **智能狀態管理**: React Query (伺服器) + Zustand (客戶端)
|
||||
4. **完整 API 整合**: 所有後端端點已對接
|
||||
5. **專業 UI 設計**: 一致的設計系統和配色
|
||||
|
||||
### 使用者體驗亮點
|
||||
|
||||
1. **直觀的操作流程**: 3 步驟完成 OCR 處理
|
||||
2. **即時狀態追蹤**: 2 秒輪詢 + 自動更新
|
||||
3. **豐富的視覺回饋**: Toast、Alert、Progress、Badge
|
||||
4. **友善的錯誤提示**: 繁體中文錯誤訊息
|
||||
5. **響應式設計**: 適配多種螢幕尺寸
|
||||
|
||||
### 交付價值
|
||||
|
||||
✅ **開發者**: 完整文件、清晰架構、易於維護
|
||||
✅ **使用者**: 現代介面、流暢操作、專業體驗
|
||||
✅ **專案**: 企業級品質、部署就緒、可擴展性
|
||||
|
||||
---
|
||||
|
||||
## 聯絡資訊
|
||||
|
||||
**開發者**: Claude Code (AI Agent)
|
||||
**升級版本**: Frontend v0.1.0
|
||||
**完成日期**: 2025-01-13
|
||||
|
||||
**相關文件**:
|
||||
- `FRONTEND_API.md` - 完整前端 API 文件
|
||||
- `frontend/README.md` - 前端使用手冊
|
||||
- `API_REFERENCE.md` - 後端 API 參考
|
||||
|
||||
---
|
||||
|
||||
**報告結束**
|
||||
97
docker-compose.yml
Normal file
97
docker-compose.yml
Normal file
@@ -0,0 +1,97 @@
|
||||
services:
|
||||
tool_ocr:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: tool_ocr:latest
|
||||
container_name: tool_ocr
|
||||
restart: unless-stopped
|
||||
|
||||
ports:
|
||||
- "12015:12015" # Only one port needed!
|
||||
|
||||
environment:
|
||||
# Database Configuration
|
||||
- MYSQL_HOST=${MYSQL_HOST:-mysql.theaken.com}
|
||||
- MYSQL_PORT=${MYSQL_PORT:-33306}
|
||||
- MYSQL_USER=${MYSQL_USER:-A060}
|
||||
- MYSQL_PASSWORD=${MYSQL_PASSWORD:-WLeSCi0yhtc7}
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE:-db_A060}
|
||||
|
||||
# Application Configuration
|
||||
- BACKEND_PORT=8000 # Internal backend port
|
||||
- FRONTEND_PORT=12015 # External port
|
||||
- SECRET_KEY=${SECRET_KEY:-your-secret-key-here-please-change-this}
|
||||
- ALGORITHM=${ALGORITHM:-HS256}
|
||||
- ACCESS_TOKEN_EXPIRE_MINUTES=${ACCESS_TOKEN_EXPIRE_MINUTES:-1440}
|
||||
|
||||
# OCR Configuration
|
||||
- PADDLEOCR_MODEL_DIR=/app/backend/models/paddleocr
|
||||
- OCR_LANGUAGES=${OCR_LANGUAGES:-ch,en,japan,korean}
|
||||
- OCR_CONFIDENCE_THRESHOLD=${OCR_CONFIDENCE_THRESHOLD:-0.5}
|
||||
- MAX_OCR_WORKERS=${MAX_OCR_WORKERS:-4}
|
||||
|
||||
# File Upload Configuration
|
||||
- MAX_UPLOAD_SIZE=${MAX_UPLOAD_SIZE:-52428800}
|
||||
- ALLOWED_EXTENSIONS=${ALLOWED_EXTENSIONS:-png,jpg,jpeg,pdf,bmp,tiff,doc,docx,ppt,pptx}
|
||||
- UPLOAD_DIR=/app/backend/uploads
|
||||
- TEMP_DIR=/app/backend/uploads/temp
|
||||
- PROCESSED_DIR=/app/backend/uploads/processed
|
||||
- IMAGES_DIR=/app/backend/uploads/images
|
||||
|
||||
# Export Configuration
|
||||
- STORAGE_DIR=/app/backend/storage
|
||||
- MARKDOWN_DIR=/app/backend/storage/markdown
|
||||
- JSON_DIR=/app/backend/storage/json
|
||||
- EXPORTS_DIR=/app/backend/storage/exports
|
||||
|
||||
# PDF Generation Configuration
|
||||
- PANDOC_PATH=/usr/bin/pandoc
|
||||
- FONT_DIR=/usr/share/fonts
|
||||
- PDF_PAGE_SIZE=${PDF_PAGE_SIZE:-A4}
|
||||
- PDF_MARGIN_TOP=${PDF_MARGIN_TOP:-20}
|
||||
- PDF_MARGIN_BOTTOM=${PDF_MARGIN_BOTTOM:-20}
|
||||
- PDF_MARGIN_LEFT=${PDF_MARGIN_LEFT:-20}
|
||||
- PDF_MARGIN_RIGHT=${PDF_MARGIN_RIGHT:-20}
|
||||
|
||||
# Translation Configuration (Reserved)
|
||||
- ENABLE_TRANSLATION=${ENABLE_TRANSLATION:-false}
|
||||
- TRANSLATION_ENGINE=${TRANSLATION_ENGINE:-offline}
|
||||
- ARGOSTRANSLATE_MODELS_DIR=/app/backend/models/argostranslate
|
||||
|
||||
# Background Tasks Configuration
|
||||
- TASK_QUEUE_TYPE=${TASK_QUEUE_TYPE:-memory}
|
||||
|
||||
# CORS Configuration
|
||||
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:12015,http://127.0.0.1:12015}
|
||||
|
||||
# Logging Configuration
|
||||
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||
- LOG_FILE=/app/backend/logs/app.log
|
||||
|
||||
volumes:
|
||||
# Persist data directories
|
||||
- ./data/uploads:/app/backend/uploads
|
||||
- ./data/storage:/app/backend/storage
|
||||
- ./data/models:/app/backend/models
|
||||
- ./data/logs:/app/backend/logs
|
||||
|
||||
networks:
|
||||
- tool_ocr_network
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:12010/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
networks:
|
||||
tool_ocr_network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
uploads:
|
||||
storage:
|
||||
models:
|
||||
logs:
|
||||
89
docker/default.conf
Normal file
89
docker/default.conf
Normal file
@@ -0,0 +1,89 @@
|
||||
# Nginx Site Configuration for Tool_OCR
|
||||
|
||||
upstream backend {
|
||||
server 127.0.0.1:8000;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 12015;
|
||||
server_name _;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
# Root directory for frontend
|
||||
root /app/frontend/dist;
|
||||
index index.html;
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/tool_ocr_access.log;
|
||||
error_log /var/log/nginx/tool_ocr_error.log;
|
||||
|
||||
# Backend API proxy
|
||||
location /api/ {
|
||||
proxy_pass http://backend/api/;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# Headers
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
# Timeouts
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
|
||||
# Buffering
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
|
||||
# Health check endpoint (backend)
|
||||
location /health {
|
||||
proxy_pass http://backend/health;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Connection "";
|
||||
}
|
||||
|
||||
# API docs (backend)
|
||||
location /docs {
|
||||
proxy_pass http://backend/docs;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Connection "";
|
||||
}
|
||||
|
||||
location /openapi.json {
|
||||
proxy_pass http://backend/openapi.json;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Connection "";
|
||||
}
|
||||
|
||||
# Frontend static files with caching
|
||||
location /assets/ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Frontend - React Router support (SPA fallback)
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
expires -1;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
}
|
||||
|
||||
# Deny access to hidden files
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
}
|
||||
41
docker/entrypoint.sh
Normal file
41
docker/entrypoint.sh
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "========================================"
|
||||
echo "Tool_OCR Container Starting..."
|
||||
echo "========================================"
|
||||
|
||||
# Wait a moment for system to stabilize
|
||||
sleep 2
|
||||
|
||||
# Run database migrations if needed
|
||||
echo "Checking database migrations..."
|
||||
cd /app/backend
|
||||
if [ -f "alembic.ini" ]; then
|
||||
echo "Running Alembic migrations..."
|
||||
alembic upgrade head || echo "Warning: Migration failed or already up to date"
|
||||
fi
|
||||
|
||||
# Create necessary directories if they don't exist
|
||||
echo "Ensuring directories exist..."
|
||||
mkdir -p \
|
||||
/app/backend/uploads/temp \
|
||||
/app/backend/uploads/processed \
|
||||
/app/backend/uploads/images \
|
||||
/app/backend/storage/markdown \
|
||||
/app/backend/storage/json \
|
||||
/app/backend/storage/exports \
|
||||
/app/backend/models/paddleocr \
|
||||
/app/backend/logs
|
||||
|
||||
# Set permissions
|
||||
chmod -R 755 /app/backend/uploads /app/backend/storage /app/backend/logs
|
||||
|
||||
echo "========================================"
|
||||
echo "Starting services with Supervisor..."
|
||||
echo "- Nginx listening on port 12015"
|
||||
echo "- Backend API on internal port 8000"
|
||||
echo "========================================"
|
||||
|
||||
# Start supervisord
|
||||
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
||||
40
docker/nginx.conf
Normal file
40
docker/nginx.conf
Normal file
@@ -0,0 +1,40 @@
|
||||
# Nginx Main Configuration
|
||||
user www-data;
|
||||
worker_processes auto;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
use epoll;
|
||||
}
|
||||
|
||||
http {
|
||||
# Basic Settings
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
client_max_body_size 50M; # Match MAX_UPLOAD_SIZE in .env
|
||||
|
||||
# MIME Types
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
# Gzip Compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css text/xml text/javascript
|
||||
application/json application/javascript application/xml+rss
|
||||
application/rss+xml font/truetype font/opentype
|
||||
application/vnd.ms-fontobject image/svg+xml;
|
||||
|
||||
# Include site configurations
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
28
docker/supervisord.conf
Normal file
28
docker/supervisord.conf
Normal file
@@ -0,0 +1,28 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
logfile=/var/log/supervisor/supervisord.log
|
||||
pidfile=/var/run/supervisord.pid
|
||||
loglevel=info
|
||||
|
||||
[program:nginx]
|
||||
command=/usr/sbin/nginx -g "daemon off;"
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=10
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:backend]
|
||||
command=python -m uvicorn app.main:app --host 127.0.0.1 --port 8000 --log-level info
|
||||
directory=/app/backend
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=20
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
environment=PYTHONUNBUFFERED="1"
|
||||
@@ -1 +1 @@
|
||||
VITE_API_BASE_URL=http://localhost:12010
|
||||
VITE_API_BASE_URL=http://localhost:12015
|
||||
|
||||
7
frontend/.env.docker
Normal file
7
frontend/.env.docker
Normal file
@@ -0,0 +1,7 @@
|
||||
# Frontend Environment Variables for Docker Deployment
|
||||
# Copy this to frontend/.env.production for Docker builds
|
||||
|
||||
# API Base URL
|
||||
# In Docker environment, use empty string for same-origin requests
|
||||
# Nginx will proxy /api/* to the backend
|
||||
VITE_API_BASE_URL=
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Outlet, NavLink, useNavigate } from 'react-router-dom'
|
||||
import { Outlet, NavLink } from 'react-router-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAuthStore } from '@/store/authStore'
|
||||
import { apiClient } from '@/services/api'
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
|
||||
export default function Layout() {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const logout = useAuthStore((state) => state.logout)
|
||||
const user = useAuthStore((state) => state.user)
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ export default function ExportPage() {
|
||||
{t('export.options.confidenceThreshold')}
|
||||
</label>
|
||||
<span className="text-sm font-bold text-primary">
|
||||
{(options.confidence_threshold * 100).toFixed(0)}%
|
||||
{((options.confidence_threshold ?? 0.5) * 100).toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
@@ -348,7 +348,7 @@ export default function ExportPage() {
|
||||
className="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-colors"
|
||||
>
|
||||
{cssTemplates.map((template) => (
|
||||
<option key={template.filename} value={template.filename}>
|
||||
<option key={template.name} value={template.name}>
|
||||
{template.name} - {template.description}
|
||||
</option>
|
||||
))}
|
||||
@@ -391,7 +391,7 @@ export default function ExportPage() {
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground mb-1">準確率門檻</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{(options.confidence_threshold * 100).toFixed(0)}%
|
||||
{((options.confidence_threshold ?? 0.5) * 100).toFixed(0)}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Badge } from '@/components/ui/badge'
|
||||
import { useToast } from '@/components/ui/toast'
|
||||
import { useUploadStore } from '@/store/uploadStore'
|
||||
import { apiClient } from '@/services/api'
|
||||
import { Play, CheckCircle, FileText, AlertCircle, Clock, Activity, Loader2, TrendingUp } from 'lucide-react'
|
||||
import { Play, CheckCircle, FileText, AlertCircle, Clock, Activity, Loader2 } from 'lucide-react'
|
||||
|
||||
export default function ProcessingPage() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function ResultsPage() {
|
||||
const [selectedFileId, setSelectedFileId] = useState<number | null>(null)
|
||||
|
||||
// Get batch status to show results
|
||||
const { data: batchStatus, isLoading } = useQuery({
|
||||
const { data: batchStatus } = useQuery({
|
||||
queryKey: ['batchStatus', batchId],
|
||||
queryFn: () => apiClient.getBatchStatus(batchId!),
|
||||
enabled: !!batchId,
|
||||
@@ -28,7 +28,7 @@ export default function ResultsPage() {
|
||||
// Get OCR result for selected file
|
||||
const { data: ocrResult, isLoading: isLoadingResult } = useQuery({
|
||||
queryKey: ['ocrResult', selectedFileId],
|
||||
queryFn: () => apiClient.getOCRResult(selectedFileId!.toString()),
|
||||
queryFn: () => apiClient.getOCRResult(selectedFileId!),
|
||||
enabled: !!selectedFileId,
|
||||
})
|
||||
|
||||
|
||||
@@ -13,14 +13,16 @@ import type {
|
||||
CSSTemplate,
|
||||
TranslateRequest,
|
||||
TranslateResponse,
|
||||
TranslationConfig,
|
||||
ApiError,
|
||||
} from '@/types/api'
|
||||
|
||||
/**
|
||||
* API Client Configuration
|
||||
* - In Docker: VITE_API_BASE_URL is empty string, use relative path
|
||||
* - In development: Use VITE_API_BASE_URL from .env or default to localhost:12015
|
||||
*/
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:12010'
|
||||
const envApiBaseUrl = import.meta.env.VITE_API_BASE_URL
|
||||
const API_BASE_URL = envApiBaseUrl !== undefined ? envApiBaseUrl : 'http://localhost:12015'
|
||||
const API_VERSION = 'v1'
|
||||
|
||||
class ApiClient {
|
||||
|
||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
||||
port: 12011,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:12010',
|
||||
target: 'http://localhost:12015',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ email-validator>=2.0.0 # For pydantic EmailStr validation
|
||||
# ===== OCR Engine =====
|
||||
paddleocr>=3.0.0
|
||||
paddlepaddle>=3.0.0
|
||||
paddlex[ocr]>=3.0.0 # Required for PP-StructureV3 layout analysis
|
||||
|
||||
# ===== Image Processing =====
|
||||
pillow>=10.0.0
|
||||
|
||||
Reference in New Issue
Block a user