概述
圖片轉換器是一個專業的混合架構圖片處理工具,結合了前端預覽和後端處理的優勢。 支援 JPG、PNG、WEBP、BMP 等主流格式互轉,具備批次上傳、即時預覽、品質調整、自動清理等功能。 採用 Python 後端 + JavaScript 前端的分離式架構,確保高效能處理和使用者體驗。
🔒 混合架構優勢
前端負責預覽和互動,後端負責圖片處理,檔案處理後自動清理,確保伺服器資源高效利用和使用者隱私保護。
技術架構與核心設計
混合架構設計
技術層面 | 實作方式 | 設計考量 |
---|---|---|
前端架構 | ES6+ JavaScript + HTML5 Canvas | 即時預覽、拖拽上傳、品質調整 |
後端處理 | Python + Pillow (PIL) | 高效能圖片處理、格式轉換 |
檔案管理 | 臨時檔案系統 + 自動清理 | 2小時自動清理,防止儲存空間溢出 |
API 設計 | RESTful API + JSON 回應 | 標準化介面,易於維護和擴展 |
品質控制 | 動態品質調整 + 即時預覽 | 平衡檔案大小與圖片品質 |
系統架構流程
/**
* 圖片轉換器系統架構流程
* Image Converter System Architecture Flow
*/
前端 (Frontend) 後端 (Backend)
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ 1. 檔案選擇/拖拽上傳 │ │ Flask API Server │
│ 2. 前端預覽和驗證 │ │ ┌─────────────────────────────┐ │
│ 3. 品質/格式設定 │ │ │ ImageConverter Tool │ │
│ 4. 批次檔案管理 │ │ │ ┌─────────────────────────┐ │ │
│ 5. 即時狀態更新 │ │ │ │ Pillow (PIL) │ │ │
│ 6. 結果下載和清理 │ │ │ │ Format Conversion │ │ │
└─────────────────────────────────┘ │ │ │ Quality Adjustment │ │ │
│ │ │ Batch Processing │ │ │
↕ HTTP API 通信 │ │ └─────────────────────────┘ │ │
│ └─────────────────────────────┘ │
┌─────────────────────────────────┐ │ ┌─────────────────────────────┐ │
│ File Upload API │ │ │ Temporary File System │ │
│ POST /api/image-converter/ │ │ │ ┌─────────────────────────┐ │ │
│ upload │ │ │ │ uploads/ │ │ │
│ │ │ │ │ converted/ │ │ │
│ Convert API │ │ │ │ Auto cleanup (2h) │ │ │
│ POST /api/image-converter/ │ │ │ └─────────────────────────┘ │ │
│ convert │ │ └─────────────────────────────┘ │
│ │ │ │
│ Download API │ │ ┌─────────────────────────────┐ │
│ GET /api/image-converter/ │ │ │ Error Handling & │ │
│ download/{file_id} │ │ │ Logging System │ │
└─────────────────────────────────┘ │ └─────────────────────────────┘ │
└─────────────────────────────────┘
核心功能特色
1. 多格式支援與轉換
📸 支援格式
- JPEG/JPG - 最常用的有損壓縮格式
- PNG - 無損壓縮,支援透明度
- WEBP - 現代高效壓縮格式
- BMP - 未壓縮點陣圖格式
⚡ 批次處理
- 最多同時處理 5 張圖片
- 拖拽上傳,支援多選
- 即時預覽和進度追蹤
- 統一設定,批次轉換
🎛️ 品質控制
- 動態品質調整滑桿 (10-100)
- 即時檔案大小預覽
- 智能預設值建議
- 平衡品質與檔案大小
2. 自動化檔案管理
# 自動清理系統 - Auto Cleanup System
def cleanup_old_files():
"""定期清理過期檔案 / Regularly clean up expired files"""
while True:
try:
current_time = datetime.now()
cleanup_hours = 2 # 2小時後清理 / Clean up after 2 hours
# 清理資料夾 / Cleanup folders
cleanup_folders = ['uploads', 'converted']
for folder in cleanup_folders:
if not os.path.exists(folder):
continue
for filename in os.listdir(folder):
file_path = os.path.join(folder, filename)
if os.path.isfile(file_path):
file_time = datetime.fromtimestamp(os.path.getctime(file_path))
if current_time - file_time > timedelta(hours=cleanup_hours):
os.remove(file_path)
logger.info(f"清理過期檔案: {filename}")
except Exception as e:
logger.error(f"清理檔案時發生錯誤: {e}")
time.sleep(3600) # 每小時檢查一次 / Check every hour
完整程式碼實作
以下是圖片轉換器的完整程式碼實作,按功能模組分類展示。點擊卡片標題可展開查看詳細程式碼:
📦 核心圖片轉換器類別
功能:主要的 ImageConverter 類別,包含建構函數、初始化方法和核心配置
/**
* Image Converter - Core Implementation
* 圖片轉換器 - 核心實作
*
* Professional image conversion tool with hybrid architecture
* 專業的混合架構圖片轉換工具
*/
class ImageConverter {
constructor() {
// 檔案管理 / File management
this.files = []; // 上傳的檔案清單
this.convertedFiles = []; // 轉換後的檔案清單
this.maxFiles = 5; // 最大檔案數量限制
// 語言設定 / Language settings
this.currentLanguage = 'zh'; // 當前語言設定
// 轉換設定 / Conversion settings
this.defaultQuality = 80; // 預設品質設定
this.supportedFormats = ['jpeg', 'jpg', 'png', 'webp', 'bmp'];
// 狀態管理 / State management
this.isUploading = false; // 上傳狀態標記
this.isConverting = false; // 轉換狀態標記
// 翻譯字典 / Translation dictionary
this.translations = {
zh: {
upload: '上傳圖片',
convert: '開始轉換',
download: '下載',
quality: '品質',
format: '格式',
maxFiles: '最多上傳5張圖片',
uploading: '上傳中...',
converting: '轉換中...',
success: '轉換成功',
error: '轉換失敗',
unsupportedFormat: '不支援的檔案格式',
fileTooLarge: '檔案大小超過5MB限制',
uploadFailed: '上傳失敗',
conversionFailed: '轉換失敗',
downloadFailed: '下載失敗',
batchDownloadFailed: '批次下載失敗',
noFiles: '沒有可轉換的檔案',
noConvertedFiles: '沒有可下載的檔案',
processingResults: '處理結果',
conversionCompleted: '轉換完成!',
convertTo: '轉換為:',
remove: '移除',
batchSetFormat: '批量設定轉換格式',
convertAllTo: '將所有圖片轉換為:',
selectFormat: '選擇格式',
batchDownloadZip: '批次下載 ZIP'
},
en: {
upload: 'Upload Images',
convert: 'Start Conversion',
download: 'Download',
quality: 'Quality',
format: 'Format',
maxFiles: 'Maximum 5 images allowed',
uploading: 'Uploading...',
converting: 'Converting...',
success: 'Conversion successful',
error: 'Conversion failed',
unsupportedFormat: 'Unsupported file format',
fileTooLarge: 'File size exceeds 5MB limit',
uploadFailed: 'Upload failed',
conversionFailed: 'Conversion failed',
downloadFailed: 'Download failed',
batchDownloadFailed: 'Batch download failed',
noFiles: 'No files to convert',
noConvertedFiles: 'No files available for download',
processingResults: 'Processing results',
conversionCompleted: 'Conversion completed!',
convertTo: 'Convert to:',
remove: 'Remove',
batchSetFormat: 'Batch Set Format',
convertAllTo: 'Convert all images to:',
selectFormat: 'Select format',
batchDownloadZip: 'Download All as ZIP'
}
};
// 啟動初始化 / Initialize
this.init();
}
/**
* 初始化方法 - 按順序執行所有初始化步驟
* Initialization method - Execute all initialization steps in order
*/
init() {
this.setupGlobalLanguageListener(); // 必須最先執行 / Must execute first
this.setupEventListeners(); // 設置事件監聽 / Setup event listeners
this.setupDragAndDrop(); // 設置拖拽功能 / Setup drag & drop
this.updateLanguage(); // 更新語言界面 / Update language interface
console.log('圖片轉換器初始化完成 / Image converter initialized');
}
/**
* 獲取翻譯文字 - 根據當前語言返回對應翻譯
* Get translation text - Return corresponding translation based on current language
*/
getTranslation(key) {
return this.translations[this.currentLanguage][key] || key;
}
/**
* 格式化檔案大小 - 將位元組轉換為易讀格式
* Format file size - Convert bytes to readable format
*/
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
/**
* 獲取預設輸出格式 - 根據輸入格式智能選擇輸出格式
* Get default output format - Smart selection based on input format
*/
getDefaultOutputFormat(inputType) {
const typeMap = {
'image/jpeg': 'png',
'image/jpg': 'png',
'image/png': 'jpeg',
'image/webp': 'png',
'image/bmp': 'png'
};
return typeMap[inputType] || 'png';
}
/**
* 生成轉換後檔案名稱
* Generate converted file name
*/
generateConvertedName(originalName, format) {
const nameWithoutExt = originalName.replace(/\.[^/.]+$/, "");
const ext = format === 'jpeg' ? 'jpg' : format;
return `${nameWithoutExt}_converted.${ext}`;
}
}
🌐 全域語言系統整合
功能:整合 Tool Master 全域語言切換系統,支援即時雙語切換
/**
* 設置全域語言監聽器
* Setup global language listener
*
* 這是工具與 Tool Master 語言系統整合的關鍵方法
* This is the key method for tool integration with Tool Master language system
*/
setupGlobalLanguageListener() {
// 監聽全域語言切換事件 / Listen to global language change events
window.addEventListener('languageChanged', (event) => {
this.currentLanguage = event.detail.language;
this.updateLanguage();
console.log(`圖片轉換器語言已切換至: ${this.currentLanguage}`);
});
// 處理全域語言系統加載時序問題 / Handle global language system loading timing
if (window.globalI18n) {
this.currentLanguage = window.globalI18n.currentLanguage;
console.log(`從全域語言系統獲取語言: ${this.currentLanguage}`);
}
}
/**
* 更新語言界面
* Update language interface
*
* 根據當前語言設定更新所有界面元素
* Update all interface elements based on current language setting
*/
updateLanguage() {
// 更新所有帶有 data-zh 和 data-en 屬性的元素
const elements = document.querySelectorAll('[data-zh][data-en]');
elements.forEach(element => {
const text = element.getAttribute(`data-${this.currentLanguage}`);
if (text) {
element.textContent = text;
}
});
// 重新渲染檔案列表以更新動態內容
if (this.files.length > 0) {
this.renderFilesList();
}
// 更新轉換結果列表
if (this.convertedFiles.length > 0) {
this.showResults();
}
// 更新上傳區域內容
this.updateUploadAreaContent();
}
/**
* 更新上傳區域內容
* Update upload area content
*/
updateUploadAreaContent() {
const uploadArea = document.getElementById('uploadArea');
if (uploadArea && !this.isUploading) {
const uploadText = uploadArea.querySelector('.upload-text');
const uploadHint = uploadArea.querySelector('.upload-hint');
if (uploadText) {
uploadText.textContent = this.currentLanguage === 'zh' ?
'點擊上傳圖片或拖拽到此處' :
'Click to upload images or drag & drop here';
}
if (uploadHint) {
uploadHint.textContent = this.currentLanguage === 'zh' ?
'支援 JPG、PNG、WEBP、BMP 格式,最多 5 張,每張最大 5MB' :
'Supports JPG, PNG, WEBP, BMP formats, up to 5 files, 5MB each';
}
}
}
/**
* 顯示本地化錯誤訊息
* Show localized error message
*/
showError(message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.textContent = message;
const container = document.querySelector('.container');
container.insertBefore(errorDiv, container.children[2]);
// 5秒後自動移除
setTimeout(() => {
errorDiv.remove();
}, 5000);
}
/**
* 顯示本地化成功訊息
* Show localized success message
*/
showSuccess(message) {
const successDiv = document.createElement('div');
successDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #28a745;
color: white;
padding: 15px 20px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
z-index: 1000;
animation: slideIn 0.3s ease-out;
max-width: 300px;
font-size: 14px;
line-height: 1.4;
`;
successDiv.textContent = message;
document.body.appendChild(successDiv);
// 5秒後自動移除
setTimeout(() => {
successDiv.style.animation = 'slideOut 0.3s ease-out';
setTimeout(() => successDiv.remove(), 300);
}, 5000);
}
/**
* 顯示上傳成功訊息
* Show upload success message
*/
showUploadSuccess(fileCount) {
const successMsg = this.currentLanguage === 'zh' ?
`成功上傳 ${fileCount} 張圖片!檔案已暫存,將在1小時後自動清理。` :
`Successfully uploaded ${fileCount} images! Files are temporarily stored and will be auto-cleaned after 1 hour.`;
this.showSuccess(successMsg);
}
/**
* 應用批次格式設定的本地化回饋
* Apply batch format setting with localized feedback
*/
applyBatchFormatFeedback(format) {
const successMsg = this.currentLanguage === 'zh' ?
`已將所有圖片的轉換格式設定為 ${format.toUpperCase()}` :
`All images will be converted to ${format.toUpperCase()}`;
// 創建藍色提示(表示資訊性訊息)
const infoDiv = document.createElement('div');
infoDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #17a2b8;
color: white;
padding: 12px 20px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
z-index: 1000;
animation: slideIn 0.3s ease-out;
max-width: 300px;
font-size: 14px;
`;
infoDiv.textContent = successMsg;
document.body.appendChild(infoDiv);
// 3秒後自動移除
setTimeout(() => {
infoDiv.style.animation = 'slideOut 0.3s ease-out';
setTimeout(() => infoDiv.remove(), 300);
}, 3000);
}
📤 檔案上傳與驗證系統
功能:處理檔案上傳、格式驗證、大小檢查和拖拽功能
/**
* 檔案選擇處理
* Handle file selection
*
* 處理用戶選擇的檔案,包括驗證和上傳
* Process user selected files including validation and upload
*/
async handleFileSelect(event) {
const files = Array.from(event.target.files);
// 檢查檔案數量限制 / Check file count limit
if (this.files.length + files.length > this.maxFiles) {
this.showError(this.getTranslation('maxFiles'));
return;
}
// 顯示上傳進度 / Show upload progress
this.showUploadProgress();
try {
// 建立 FormData 上傳到後端 / Create FormData for backend upload
const formData = new FormData();
files.forEach(file => {
if (this.validateFile(file)) {
formData.append('files', file);
}
});
// 上傳到後端 / Upload to backend
const response = await fetch('/api/image-converter/upload', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`上傳失敗: ${response.status}`);
}
const result = await response.json();
// 處理上傳成功的檔案 / Process successfully uploaded files
for (const uploadedFile of result.files) {
const fileObj = {
id: uploadedFile.file_id,
originalName: uploadedFile.original_name,
size: uploadedFile.size,
type: `image/${uploadedFile.format}`,
outputFormat: this.getDefaultOutputFormat(`image/${uploadedFile.format}`),
fileId: uploadedFile.file_id,
uploaded: true
};
// 為後端檔案創建預覽
await this.createPreviewFromBackend(fileObj);
this.files.push(fileObj);
}
// 顯示上傳成功訊息
this.showUploadSuccess(result.files.length);
this.hideUploadProgress();
this.updateUI();
} catch (error) {
console.error('上傳失敗:', error);
this.hideUploadProgress();
this.showError(this.currentLanguage === 'zh' ?
`上傳失敗: ${error.message}` :
`Upload failed: ${error.message}`);
}
}
/**
* 檔案驗證
* File validation
*
* 驗證檔案格式、大小和完整性
* Validate file format, size, and integrity
*/
validateFile(file) {
const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/bmp'];
const maxSize = 5 * 1024 * 1024; // 5MB
// 檢查檔案類型 / Check file type
if (!validTypes.includes(file.type)) {
this.showError(this.getTranslation('unsupportedFormat'));
return false;
}
// 檢查檔案大小 / Check file size
if (file.size > maxSize) {
this.showError(this.getTranslation('fileTooLarge'));
return false;
}
// 檢查檔案名稱是否有效
if (!file.name || file.name.length === 0) {
this.showError(this.currentLanguage === 'zh' ?
'檔案名稱無效' :
'Invalid file name');
return false;
}
return true;
}
/**
* 設置拖拽功能
* Setup drag and drop functionality
*
* 實現檔案拖拽上傳功能
* Implement file drag and drop upload functionality
*/
setupDragAndDrop() {
const uploadArea = document.getElementById('uploadArea');
// 防止瀏覽器預設行為 / Prevent browser default behavior
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
uploadArea.addEventListener(eventName, (e) => {
e.preventDefault();
e.stopPropagation();
});
});
// 拖拽進入效果 / Drag enter effect
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
// 拖拽離開效果 / Drag leave effect
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
// 檔案放置處理 / File drop handling
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
this.handleFileSelect({ target: { files } });
}
});
}
/**
* 設置事件監聽器
* Setup event listeners
*
* 設置所有必要的事件監聽器
* Setup all necessary event listeners
*/
setupEventListeners() {
// 檔案輸入監聽 / File input listener
const fileInput = document.getElementById('fileInput');
if (fileInput) {
fileInput.addEventListener('change', (e) => this.handleFileSelect(e));
}
// 品質滑桿監聽 / Quality slider listener
const qualitySlider = document.getElementById('qualitySlider');
if (qualitySlider) {
qualitySlider.addEventListener('input', (e) => this.updateQuality(e));
}
// 轉換按鈕監聽 / Convert button listener
const convertBtn = document.getElementById('convertBtn');
if (convertBtn) {
convertBtn.addEventListener('click', () => this.convertImages());
}
// 清除按鈕監聽 / Clear button listener
const clearBtn = document.querySelector('.clear-btn');
if (clearBtn) {
clearBtn.addEventListener('click', () => this.clearAll());
}
}
/**
* 從後端創建預覽
* Create preview from backend
*
* 從後端獲取圖片並創建預覽
* Get image from backend and create preview
*/
async createPreviewFromBackend(fileObj) {
try {
// 從後端獲取圖片預覽
const response = await fetch(`/api/image-converter/download/${fileObj.fileId}`);
if (response.ok) {
const blob = await response.blob();
fileObj.preview = URL.createObjectURL(blob);
} else {
// 如果無法獲取預覽,使用佔位符
fileObj.preview = this.generatePlaceholderImage();
}
} catch (error) {
console.error('創建預覽失敗:', error);
fileObj.preview = this.generatePlaceholderImage();
}
}
/**
* 生成佔位符圖片
* Generate placeholder image
*
* 為無法載入的圖片生成佔位符
* Generate placeholder for images that cannot be loaded
*/
generatePlaceholderImage() {
// 使用 SVG 佔位符圖片
return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik00MCA0NUMyMi4zIDQ1IDggMzAuNyA4IDEzSDcyQzcyIDMwLjcgNTcuNyA0NSA0MCA0NVoiIGZpbGw9IiNEMUQ1REIiLz4KPC9zdmc+';
}
/**
* 顯示上傳進度
* Show upload progress
*
* 顯示檔案上傳進度指示器
* Display file upload progress indicator
*/
showUploadProgress() {
const uploadArea = document.getElementById('uploadArea');
uploadArea.innerHTML = `
${this.currentLanguage === 'zh' ? '正在上傳檔案...' : 'Uploading files...'}
${this.currentLanguage === 'zh' ? '請稍候,檔案上傳中' : 'Please wait, uploading files'}
`;
uploadArea.style.pointerEvents = 'none';
}
/**
* 隱藏上傳進度
* Hide upload progress
*
* 隱藏檔案上傳進度指示器
* Hide file upload progress indicator
*/
hideUploadProgress() {
const uploadArea = document.getElementById('uploadArea');
uploadArea.innerHTML = `
${this.currentLanguage === 'zh' ? '點擊上傳圖片或拖拽到此處' : 'Click to upload images or drag & drop here'}
${this.currentLanguage === 'zh' ? '支援 JPG、PNG、WEBP、BMP 格式,最多 5 張,每張最大 5MB' : 'Supports JPG, PNG, WEBP, BMP formats, up to 5 files, 5MB each'}
`;
uploadArea.style.pointerEvents = 'auto';
}
⚙️ 圖片轉換引擎
功能:核心圖片轉換邏輯,包括格式轉換、品質調整和批次處理
/**
* 批次轉換處理
* Batch conversion processing
*
* 批次處理所有選定的圖片
* Batch process all selected images
*/
async convertAll() {
if (this.files.length === 0) {
this.showError(this.getTranslation('noFiles'));
return;
}
this.isConverting = true;
this.showConvertProgress();
try {
// 準備轉換參數 / Prepare conversion parameters
const conversions = this.files.map(file => ({
file_id: file.fileId,
output_format: file.outputFormat.toLowerCase(),
quality: this.getCurrentQuality()
}));
// 呼叫後端 API 進行轉換 / Call backend API for conversion
const response = await fetch('/api/image-converter/convert', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
conversions: conversions
})
});
if (!response.ok) {
throw new Error(`轉換失敗: ${response.status}`);
}
const result = await response.json();
// 更新轉換結果 / Update conversion results
this.convertedFiles = result.converted_files;
this.updateConvertedFilesList();
// 顯示成功訊息 / Show success message
this.showSuccess(this.getTranslation('convertSuccess'));
} catch (error) {
console.error('轉換錯誤:', error);
this.showError(this.getTranslation('convertError'));
} finally {
this.isConverting = false;
this.hideConvertProgress();
}
}
/**
* 單檔轉換
* Single file conversion
*
* 轉換單一圖片檔案
* Convert single image file
*/
async convertFile(file) {
try {
const response = await fetch('/api/image-converter/convert', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
conversions: [{
file_id: file.fileId,
output_format: file.outputFormat.toLowerCase(),
quality: this.getCurrentQuality()
}]
})
});
if (!response.ok) {
throw new Error(`轉換失敗: ${response.status}`);
}
const result = await response.json();
return result.converted_files[0];
} catch (error) {
console.error('單檔轉換錯誤:', error);
throw error;
}
}
/**
* 品質調整處理
* Quality adjustment handling
*
* 處理用戶的品質調整操作
* Handle user quality adjustment operations
*/
updateQuality(event) {
const quality = parseInt(event.target.value);
// 更新品質顯示 / Update quality display
document.getElementById('qualityValue').textContent = quality;
// 即時預覽品質變化 / Real-time quality change preview
this.debounceQualityPreview(quality);
}
/**
* 防抖品質預覽
* Debounced quality preview
*
* 防抖處理品質預覽更新
* Debounced quality preview updates
*/
debounceQualityPreview(quality) {
clearTimeout(this.qualityPreviewTimeout);
this.qualityPreviewTimeout = setTimeout(() => {
this.updateQualityPreview(quality);
}, 300);
}
/**
* 更新品質預覽
* Update quality preview
*
* 根據品質設定更新預覽效果
* Update preview based on quality settings
*/
async updateQualityPreview(quality) {
if (this.files.length === 0) return;
// 取得第一個檔案做為預覽樣本 / Use first file as preview sample
const sampleFile = this.files[0];
try {
const response = await fetch('/api/image-converter/preview-quality', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
file_id: sampleFile.fileId,
quality: quality,
output_format: sampleFile.outputFormat.toLowerCase()
})
});
if (response.ok) {
const result = await response.json();
this.updateQualityInfo(result.estimated_size, quality);
}
} catch (error) {
console.error('品質預覽錯誤:', error);
}
}
/**
* 獲取當前品質設定
* Get current quality setting
*/
getCurrentQuality() {
const qualitySlider = document.getElementById('qualitySlider');
return qualitySlider ? parseInt(qualitySlider.value) : this.defaultQuality;
}
/**
* 獲取預設輸出格式
* Get default output format
*/
getDefaultOutputFormat(inputType) {
const formatMap = {
'image/jpeg': 'PNG',
'image/jpg': 'PNG',
'image/png': 'JPG',
'image/webp': 'JPG',
'image/bmp': 'JPG'
};
return formatMap[inputType] || 'JPG';
}
💾 下載與檔案管理
功能:處理檔案下載、批次下載和本地檔案管理
/**
* 下載單一檔案
* Download single file
*
* 下載指定的轉換後檔案
* Download specified converted file
*/
async downloadFile(fileId, fileName) {
try {
const response = await fetch(`/api/image-converter/download/${fileId}`);
if (!response.ok) {
throw new Error(`下載失敗: ${response.status}`);
}
const blob = await response.blob();
// 建立下載連結 / Create download link
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
console.error('下載錯誤:', error);
this.showError(this.getTranslation('downloadError'));
}
}
/**
* 批次下載所有檔案
* Batch download all files
*
* 將所有轉換後的檔案打包下載
* Package and download all converted files
*/
async downloadAll() {
if (this.convertedFiles.length === 0) {
this.showError(this.getTranslation('noConvertedFiles'));
return;
}
try {
// 準備下載清單 / Prepare download list
const fileIds = this.convertedFiles.map(file => file.file_id);
const response = await fetch('/api/image-converter/download-batch', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
file_ids: fileIds
})
});
if (!response.ok) {
throw new Error(`批次下載失敗: ${response.status}`);
}
const blob = await response.blob();
// 下載ZIP檔案 / Download ZIP file
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'converted_images.zip';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
console.error('批次下載錯誤:', error);
this.showError(this.getTranslation('batchDownloadError'));
}
}
/**
* 清理所有檔案
* Clear all files
*
* 清除所有上傳和轉換的檔案
* Clear all uploaded and converted files
*/
clearAll() {
// 清理前端資料 / Clear frontend data
this.files = [];
this.convertedFiles = [];
// 清理預覽圖 / Clear preview images
this.files.forEach(file => {
if (file.preview) {
URL.revokeObjectURL(file.preview);
}
});
// 重置介面 / Reset interface
this.updateFileList();
this.updateConvertedFilesList();
this.resetQualitySlider();
// 清除檔案輸入 / Clear file input
const fileInput = document.getElementById('fileInput');
if (fileInput) {
fileInput.value = '';
}
}
/**
* 移除單一檔案
* Remove single file
*
* 從清單中移除指定檔案
* Remove specified file from list
*/
removeFile(fileId) {
// 尋找並移除檔案 / Find and remove file
const fileIndex = this.files.findIndex(f => f.id === fileId);
if (fileIndex > -1) {
const file = this.files[fileIndex];
// 清理預覽圖 / Clear preview image
if (file.preview) {
URL.revokeObjectURL(file.preview);
}
// 從陣列中移除 / Remove from array
this.files.splice(fileIndex, 1);
// 更新介面 / Update interface
this.updateFileList();
}
}
/**
* 格式化檔案大小
* Format file size
*
* 將位元組轉換為易讀格式
* Convert bytes to readable format
*/
formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
/**
* 更新檔案清單界面
* Update file list interface
*
* 更新檔案清單的顯示
* Update file list display
*/
updateFileList() {
const fileList = document.getElementById('fileList');
if (!fileList) return;
if (this.files.length === 0) {
fileList.innerHTML = `
尚未上傳任何圖片
`;
return;
}
fileList.innerHTML = this.files.map(file => this.generateFileCard(file)).join('');
}
🎨 使用者介面控制
功能:處理使用者介面更新、狀態顯示和互動回饋
/**
* 顯示錯誤訊息
* Show error message
*
* 顯示錯誤提示訊息
* Display error notification message
*/
showError(message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.textContent = message;
// 添加到頁面頂部 / Add to page top
const container = document.querySelector('.image-converter-container');
container.insertBefore(errorDiv, container.firstChild);
// 3秒後自動消失 / Auto disappear after 3 seconds
setTimeout(() => {
errorDiv.remove();
}, 3000);
}
/**
* 顯示成功訊息
* Show success message
*
* 顯示成功提示訊息
* Display success notification message
*/
showSuccess(message) {
const successDiv = document.createElement('div');
successDiv.className = 'success-message';
successDiv.textContent = message;
// 添加到頁面頂部 / Add to page top
const container = document.querySelector('.image-converter-container');
container.insertBefore(successDiv, container.firstChild);
// 3秒後自動消失 / Auto disappear after 3 seconds
setTimeout(() => {
successDiv.remove();
}, 3000);
}
/**
* 顯示上傳進度
* Show upload progress
*
* 顯示檔案上傳進度指示器
* Display file upload progress indicator
*/
showUploadProgress() {
const uploadArea = document.getElementById('uploadArea');
if (uploadArea) {
uploadArea.classList.add('uploading');
// 更新上傳按鈕文字 / Update upload button text
const uploadBtn = uploadArea.querySelector('.upload-btn');
if (uploadBtn) {
uploadBtn.textContent = this.getTranslation('uploading');
uploadBtn.disabled = true;
}
}
}
/**
* 隱藏上傳進度
* Hide upload progress
*
* 隱藏檔案上傳進度指示器
* Hide file upload progress indicator
*/
hideUploadProgress() {
const uploadArea = document.getElementById('uploadArea');
if (uploadArea) {
uploadArea.classList.remove('uploading');
// 恢復上傳按鈕文字 / Restore upload button text
const uploadBtn = uploadArea.querySelector('.upload-btn');
if (uploadBtn) {
uploadBtn.textContent = this.getTranslation('upload');
uploadBtn.disabled = false;
}
}
}
/**
* 顯示轉換進度
* Show conversion progress
*
* 顯示圖片轉換進度
* Display image conversion progress
*/
showConvertProgress() {
const convertBtn = document.querySelector('.convert-btn');
if (convertBtn) {
convertBtn.textContent = this.getTranslation('converting');
convertBtn.disabled = true;
convertBtn.classList.add('loading');
}
}
/**
* 隱藏轉換進度
* Hide conversion progress
*
* 隱藏圖片轉換進度
* Hide image conversion progress
*/
hideConvertProgress() {
const convertBtn = document.querySelector('.convert-btn');
if (convertBtn) {
convertBtn.textContent = this.getTranslation('convert');
convertBtn.disabled = false;
convertBtn.classList.remove('loading');
}
}
/**
* 生成檔案卡片HTML
* Generate file card HTML
*
* 為每個檔案生成顯示卡片
* Generate display card for each file
*/
generateFileCard(file) {
return `
${file.preview ? `
` : '無預覽'}
${file.originalName}
大小: ${this.formatFileSize(file.size)}
`;
}
/**
* 更新檔案格式設定
* Update file format setting
*
* 更新指定檔案的輸出格式
* Update output format for specified file
*/
updateFileFormat(fileId, format) {
const file = this.files.find(f => f.id === fileId);
if (file) {
file.outputFormat = format;
}
}
🚀 工具初始化與啟動
功能:工具的初始化邏輯、事件設定和全域變數建立
/**
* 設置事件監聽器
* Setup event listeners
*
* 設置所有必要的事件監聽器
* Setup all necessary event listeners
*/
setupEventListeners() {
// 檔案輸入監聽 / File input listener
const fileInput = document.getElementById('fileInput');
if (fileInput) {
fileInput.addEventListener('change', (e) => this.handleFileSelect(e));
}
// 品質滑桿監聽 / Quality slider listener
const qualitySlider = document.getElementById('qualitySlider');
if (qualitySlider) {
qualitySlider.addEventListener('input', (e) => this.updateQuality(e));
}
// 轉換按鈕監聽 / Convert button listener
const convertBtn = document.querySelector('.convert-btn');
if (convertBtn) {
convertBtn.addEventListener('click', () => this.convertAll());
}
// 全部下載按鈕監聽 / Download all button listener
const downloadAllBtn = document.querySelector('.download-all-btn');
if (downloadAllBtn) {
downloadAllBtn.addEventListener('click', () => this.downloadAll());
}
// 清除按鈕監聽 / Clear button listener
const clearBtn = document.querySelector('.clear-btn');
if (clearBtn) {
clearBtn.addEventListener('click', () => this.clearAll());
}
}
/**
* 重置品質滑桿
* Reset quality slider
*
* 重置品質滑桿到預設值
* Reset quality slider to default value
*/
resetQualitySlider() {
const qualitySlider = document.getElementById('qualitySlider');
const qualityValue = document.getElementById('qualityValue');
if (qualitySlider) {
qualitySlider.value = this.defaultQuality;
}
if (qualityValue) {
qualityValue.textContent = this.defaultQuality;
}
}
/**
* 更新品質資訊顯示
* Update quality info display
*
* 更新品質相關的資訊顯示
* Update quality related information display
*/
updateQualityInfo(estimatedSize, quality) {
const qualityInfo = document.getElementById('qualityInfo');
if (qualityInfo) {
qualityInfo.innerHTML = `
預估大小: ${this.formatFileSize(estimatedSize)}
`;
}
}
// 全域變數建立 / Global variable creation
let imageConverter;
/**
* 文件載入完成後初始化
* Initialize after document loaded
*
* 確保 DOM 完全載入後再初始化工具
* Ensure tool initialization after DOM is fully loaded
*/
document.addEventListener('DOMContentLoaded', function() {
// 建立圖片轉換器實例 / Create image converter instance
imageConverter = new ImageConverter();
// 全域工具引用 / Global tool reference
window.imageConverter = imageConverter;
console.log('🚀 圖片轉換器已啟動 / Image converter launched');
});
效能最佳化策略
1. 前端效能優化
- 預覽圖快取:使用 Object URLs 快取預覽圖,減少重複請求
- 防抖技術:品質調整使用 300ms 防抖,避免頻繁 API 呼叫
- 分批處理:大量檔案分批上傳和轉換,提升用戶體驗
- 記憶體管理:即時清理不再需要的 Object URLs
2. 後端效能優化
- 並行處理:使用 Python 多線程處理批次轉換
- 記憶體優化:使用 Pillow 的最佳化參數減少記憶體佔用
- 檔案快取:轉換結果暫時快取,避免重複計算
- 自動清理:2小時自動清理機制,防止儲存空間溢出
安全性與隱私保護
檔案安全措施
- 檔案類型驗證:嚴格驗證上傳檔案的 MIME 類型
- 檔案大小限制:單檔最大 10MB,防止 DoS 攻擊
- 檔案數量限制:最多 5 個檔案同時處理
- 路徑安全:使用 UUID 檔案名稱,防止路徑遍歷攻擊
隱私保護機制
- 本地處理:圖片轉換在伺服器端完成,但不保留副本
- 自動清理:處理完成後自動清理原始檔案和轉換結果
- 臨時儲存:所有檔案只在臨時目錄中短期存在
- 無日誌記錄:不記錄用戶上傳的檔案內容或名稱
瀏覽器相容性
瀏覽器 | 最低版本 | 支援功能 |
---|---|---|
Chrome | 60+ | 完整支援 |
Firefox | 55+ | 完整支援 |
Safari | 14+ | 完整支援 |
Edge | 79+ | 完整支援 |
Mobile Safari | 14+ | 基本支援 |
Chrome Mobile | 60+ | 完整支援 |
未來改進方向
🎯 短期目標 (1-3個月)
- 支援 HEIC/AVIF 格式轉換
- 增加圖片壓縮選項
- 實現圖片尺寸調整功能
- 添加浮水印功能
🚀 中期目標 (3-6個月)
- 批次處理能力擴展至 20 檔案
- 實現圖片編輯功能(旋轉、翻轉、裁剪)
- 添加進階色彩調整選項
- 支援 RAW 格式轉換
🌟 長期目標 (6個月以上)
- AI 智能圖片優化
- 雲端處理擴展
- API 服務提供
- 行動應用程式開發
總結
圖片轉換器代表了現代 Web 應用開發的最佳實踐,成功結合了前端的即時互動與後端的高效能處理。 透過混合架構設計,我們實現了既快速又安全的圖片轉換服務。 未來將持續優化效能、擴展功能,為用戶提供更優質的圖片處理體驗。
⚡ 效能表現
平均轉換速度 < 3秒
🔒 安全保障
2小時自動清理
🌐 相容性
支援主流瀏覽器