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
|
# Tool_OCR - Docker Environment Configuration
|
||||||
# Copy this file to .env and fill in your actual values
|
# Copy this file to .env when deploying with Docker
|
||||||
|
|
||||||
# ===== Database Configuration =====
|
# ===== Database Configuration =====
|
||||||
MYSQL_HOST=mysql.theaken.com
|
MYSQL_HOST=mysql.theaken.com
|
||||||
@@ -9,18 +9,17 @@ MYSQL_PASSWORD=WLeSCi0yhtc7
|
|||||||
MYSQL_DATABASE=db_A060
|
MYSQL_DATABASE=db_A060
|
||||||
|
|
||||||
# ===== Application Configuration =====
|
# ===== Application Configuration =====
|
||||||
# Server ports
|
# External port (exposed to host)
|
||||||
BACKEND_PORT=12010
|
FRONTEND_PORT=12010
|
||||||
FRONTEND_PORT=12011
|
|
||||||
|
|
||||||
# Security
|
# Security (IMPORTANT: Change SECRET_KEY in production!)
|
||||||
SECRET_KEY=your-secret-key-here-please-change-this-to-random-string
|
SECRET_KEY=your-secret-key-here-please-change-this-to-random-string
|
||||||
ALGORITHM=HS256
|
ALGORITHM=HS256
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES=1440
|
ACCESS_TOKEN_EXPIRE_MINUTES=1440
|
||||||
|
|
||||||
# ===== OCR Configuration =====
|
# ===== OCR Configuration =====
|
||||||
# PaddleOCR model directory
|
# PaddleOCR model directory (inside container)
|
||||||
PADDLEOCR_MODEL_DIR=./models/paddleocr
|
PADDLEOCR_MODEL_DIR=/app/backend/models/paddleocr
|
||||||
# Supported languages (comma-separated)
|
# Supported languages (comma-separated)
|
||||||
OCR_LANGUAGES=ch,en,japan,korean
|
OCR_LANGUAGES=ch,en,japan,korean
|
||||||
# Default confidence threshold
|
# Default confidence threshold
|
||||||
@@ -33,24 +32,24 @@ MAX_OCR_WORKERS=4
|
|||||||
MAX_UPLOAD_SIZE=52428800
|
MAX_UPLOAD_SIZE=52428800
|
||||||
# Allowed file extensions (comma-separated)
|
# Allowed file extensions (comma-separated)
|
||||||
ALLOWED_EXTENSIONS=png,jpg,jpeg,pdf,bmp,tiff,doc,docx,ppt,pptx
|
ALLOWED_EXTENSIONS=png,jpg,jpeg,pdf,bmp,tiff,doc,docx,ppt,pptx
|
||||||
# Upload directories
|
# Upload directories (inside container)
|
||||||
UPLOAD_DIR=./uploads
|
UPLOAD_DIR=/app/backend/uploads
|
||||||
TEMP_DIR=./uploads/temp
|
TEMP_DIR=/app/backend/uploads/temp
|
||||||
PROCESSED_DIR=./uploads/processed
|
PROCESSED_DIR=/app/backend/uploads/processed
|
||||||
IMAGES_DIR=./uploads/images
|
IMAGES_DIR=/app/backend/uploads/images
|
||||||
|
|
||||||
# ===== Export Configuration =====
|
# ===== Export Configuration =====
|
||||||
# Storage directories
|
# Storage directories (inside container)
|
||||||
STORAGE_DIR=./storage
|
STORAGE_DIR=/app/backend/storage
|
||||||
MARKDOWN_DIR=./storage/markdown
|
MARKDOWN_DIR=/app/backend/storage/markdown
|
||||||
JSON_DIR=./storage/json
|
JSON_DIR=/app/backend/storage/json
|
||||||
EXPORTS_DIR=./storage/exports
|
EXPORTS_DIR=/app/backend/storage/exports
|
||||||
|
|
||||||
# ===== PDF Generation Configuration =====
|
# ===== PDF Generation Configuration =====
|
||||||
# Pandoc path (auto-detected if installed via brew)
|
# Pandoc path (inside container)
|
||||||
PANDOC_PATH=/opt/homebrew/bin/pandoc
|
PANDOC_PATH=/usr/bin/pandoc
|
||||||
# WeasyPrint font directory
|
# Font directory (inside container)
|
||||||
FONT_DIR=/System/Library/Fonts
|
FONT_DIR=/usr/share/fonts
|
||||||
# Default PDF page size
|
# Default PDF page size
|
||||||
PDF_PAGE_SIZE=A4
|
PDF_PAGE_SIZE=A4
|
||||||
# Default PDF margins (mm)
|
# Default PDF margins (mm)
|
||||||
@@ -64,8 +63,8 @@ PDF_MARGIN_RIGHT=20
|
|||||||
ENABLE_TRANSLATION=false
|
ENABLE_TRANSLATION=false
|
||||||
# Translation engine: offline (argostranslate) or api (future)
|
# Translation engine: offline (argostranslate) or api (future)
|
||||||
TRANSLATION_ENGINE=offline
|
TRANSLATION_ENGINE=offline
|
||||||
# Argostranslate models directory
|
# Argostranslate models directory (inside container)
|
||||||
ARGOSTRANSLATE_MODELS_DIR=./models/argostranslate
|
ARGOSTRANSLATE_MODELS_DIR=/app/backend/models/argostranslate
|
||||||
|
|
||||||
# ===== Background Tasks Configuration =====
|
# ===== Background Tasks Configuration =====
|
||||||
# Task queue type: memory (default) or redis (future)
|
# Task queue type: memory (default) or redis (future)
|
||||||
@@ -75,8 +74,9 @@ TASK_QUEUE_TYPE=memory
|
|||||||
|
|
||||||
# ===== CORS Configuration =====
|
# ===== CORS Configuration =====
|
||||||
# Allowed origins (comma-separated, * for all)
|
# 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 =====
|
# ===== Logging Configuration =====
|
||||||
LOG_LEVEL=INFO
|
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 { useTranslation } from 'react-i18next'
|
||||||
import { useAuthStore } from '@/store/authStore'
|
import { useAuthStore } from '@/store/authStore'
|
||||||
import { apiClient } from '@/services/api'
|
import { apiClient } from '@/services/api'
|
||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
|
|
||||||
export default function Layout() {
|
export default function Layout() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
|
||||||
const logout = useAuthStore((state) => state.logout)
|
const logout = useAuthStore((state) => state.logout)
|
||||||
const user = useAuthStore((state) => state.user)
|
const user = useAuthStore((state) => state.user)
|
||||||
|
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ export default function ExportPage() {
|
|||||||
{t('export.options.confidenceThreshold')}
|
{t('export.options.confidenceThreshold')}
|
||||||
</label>
|
</label>
|
||||||
<span className="text-sm font-bold text-primary">
|
<span className="text-sm font-bold text-primary">
|
||||||
{(options.confidence_threshold * 100).toFixed(0)}%
|
{((options.confidence_threshold ?? 0.5) * 100).toFixed(0)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<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"
|
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) => (
|
{cssTemplates.map((template) => (
|
||||||
<option key={template.filename} value={template.filename}>
|
<option key={template.name} value={template.name}>
|
||||||
{template.name} - {template.description}
|
{template.name} - {template.description}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
@@ -391,7 +391,7 @@ export default function ExportPage() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-muted-foreground mb-1">準確率門檻</div>
|
<div className="text-xs text-muted-foreground mb-1">準確率門檻</div>
|
||||||
<div className="text-sm font-medium text-foreground">
|
<div className="text-sm font-medium text-foreground">
|
||||||
{(options.confidence_threshold * 100).toFixed(0)}%
|
{((options.confidence_threshold ?? 0.5) * 100).toFixed(0)}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Badge } from '@/components/ui/badge'
|
|||||||
import { useToast } from '@/components/ui/toast'
|
import { useToast } from '@/components/ui/toast'
|
||||||
import { useUploadStore } from '@/store/uploadStore'
|
import { useUploadStore } from '@/store/uploadStore'
|
||||||
import { apiClient } from '@/services/api'
|
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() {
|
export default function ProcessingPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default function ResultsPage() {
|
|||||||
const [selectedFileId, setSelectedFileId] = useState<number | null>(null)
|
const [selectedFileId, setSelectedFileId] = useState<number | null>(null)
|
||||||
|
|
||||||
// Get batch status to show results
|
// Get batch status to show results
|
||||||
const { data: batchStatus, isLoading } = useQuery({
|
const { data: batchStatus } = useQuery({
|
||||||
queryKey: ['batchStatus', batchId],
|
queryKey: ['batchStatus', batchId],
|
||||||
queryFn: () => apiClient.getBatchStatus(batchId!),
|
queryFn: () => apiClient.getBatchStatus(batchId!),
|
||||||
enabled: !!batchId,
|
enabled: !!batchId,
|
||||||
@@ -28,7 +28,7 @@ export default function ResultsPage() {
|
|||||||
// Get OCR result for selected file
|
// Get OCR result for selected file
|
||||||
const { data: ocrResult, isLoading: isLoadingResult } = useQuery({
|
const { data: ocrResult, isLoading: isLoadingResult } = useQuery({
|
||||||
queryKey: ['ocrResult', selectedFileId],
|
queryKey: ['ocrResult', selectedFileId],
|
||||||
queryFn: () => apiClient.getOCRResult(selectedFileId!.toString()),
|
queryFn: () => apiClient.getOCRResult(selectedFileId!),
|
||||||
enabled: !!selectedFileId,
|
enabled: !!selectedFileId,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,16 @@ import type {
|
|||||||
CSSTemplate,
|
CSSTemplate,
|
||||||
TranslateRequest,
|
TranslateRequest,
|
||||||
TranslateResponse,
|
TranslateResponse,
|
||||||
TranslationConfig,
|
|
||||||
ApiError,
|
ApiError,
|
||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API Client Configuration
|
* 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'
|
const API_VERSION = 'v1'
|
||||||
|
|
||||||
class ApiClient {
|
class ApiClient {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
|||||||
port: 12011,
|
port: 12011,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:12010',
|
target: 'http://localhost:12015',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ email-validator>=2.0.0 # For pydantic EmailStr validation
|
|||||||
# ===== OCR Engine =====
|
# ===== OCR Engine =====
|
||||||
paddleocr>=3.0.0
|
paddleocr>=3.0.0
|
||||||
paddlepaddle>=3.0.0
|
paddlepaddle>=3.0.0
|
||||||
|
paddlex[ocr]>=3.0.0 # Required for PP-StructureV3 layout analysis
|
||||||
|
|
||||||
# ===== Image Processing =====
|
# ===== Image Processing =====
|
||||||
pillow>=10.0.0
|
pillow>=10.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user