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:
beabigegg
2025-11-13 13:12:59 +08:00
parent 57cf91271c
commit 0f81d5e70b
26 changed files with 1166 additions and 2985 deletions

87
.dockerignore Normal file
View 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
View File

@@ -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
View 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

View File

@@ -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
View 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
View 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"]

View File

@@ -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, 快速刷新)

View File

@@ -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` 添加全局樣式

View File

@@ -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/ (組件)

View File

@@ -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` 的 "部署說明"
---
**預祝使用順利!**

View File

@@ -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

View File

@@ -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
View 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
View 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
View 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
View 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
View 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"

View File

@@ -1 +1 @@
VITE_API_BASE_URL=http://localhost:12010
VITE_API_BASE_URL=http://localhost:12015

7
frontend/.env.docker Normal file
View 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=

View File

@@ -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)

View File

@@ -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>

View File

@@ -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()

View File

@@ -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,
})

View File

@@ -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 {

View File

@@ -9,7 +9,7 @@ export default defineConfig({
port: 12011,
proxy: {
'/api': {
target: 'http://localhost:12010',
target: 'http://localhost:12015',
changeOrigin: true,
},
},

View File

@@ -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