/** * 主应用程序 - UI 交互逻辑 */ import { WatermarkEngine } from './core/watermarkEngine.js'; import i18n from './i18n.js'; // 全局状态 let engine = null; let imageQueue = []; let processedCount = 0; // DOM 元素 const uploadArea = document.getElementById('uploadArea'); const fileInput = document.getElementById('fileInput'); const singlePreview = document.getElementById('singlePreview'); const multiPreview = document.getElementById('multiPreview'); const imageList = document.getElementById('imageList'); const progressText = document.getElementById('progressText'); const downloadAllBtn = document.getElementById('downloadAllBtn'); const loadingOverlay = document.getElementById('loadingOverlay'); const originalCanvas = document.getElementById('originalCanvas'); const processedSection = document.getElementById('processedSection'); const processedImage = document.getElementById('processedImage'); const originalInfo = document.getElementById('originalInfo'); const processedInfo = document.getElementById('processedInfo'); const downloadBtn = document.getElementById('downloadBtn'); const resetBtn = document.getElementById('resetBtn'); const statusMessage = document.getElementById('statusMessage'); /** * 初始化应用 */ async function init() { try { await i18n.init(); setupLanguageSwitch(); showLoading(i18n.t('status.loading')); engine = await WatermarkEngine.create(); hideLoading(); setupEventListeners(); } catch (error) { hideLoading(); console.error('初始化错误:', error); } } /** * 设置语言切换 */ function setupLanguageSwitch() { const btn = document.getElementById('langSwitch'); btn.textContent = i18n.locale === 'zh-CN' ? 'EN' : '中文'; btn.addEventListener('click', async () => { const newLocale = i18n.locale === 'zh-CN' ? 'en-US' : 'zh-CN'; await i18n.switchLocale(newLocale); btn.textContent = newLocale === 'zh-CN' ? 'EN' : '中文'; updateDynamicTexts(); }); } /** * 设置事件监听器 */ function setupEventListeners() { uploadArea.addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', handleFileSelect); uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); handleFiles(Array.from(e.dataTransfer.files)); }); downloadAllBtn.addEventListener('click', downloadAll); resetBtn.addEventListener('click', reset); } function reset() { singlePreview.style.display = 'none'; multiPreview.style.display = 'none'; imageQueue = []; processedCount = 0; fileInput.value = ''; } function handleFileSelect(e) { handleFiles(Array.from(e.target.files)); } function handleFiles(files) { const validFiles = files.filter(file => { if (!file.type.match('image/(jpeg|png|webp)')) return false; if (file.size > 20 * 1024 * 1024) return false; return true; }); if (validFiles.length === 0) return; imageQueue = validFiles.map((file, index) => ({ id: Date.now() + index, file, name: file.name, status: 'pending', originalImg: null, processedBlob: null })); processedCount = 0; if (validFiles.length === 1) { singlePreview.style.display = 'block'; multiPreview.style.display = 'none'; processSingle(imageQueue[0]); } else { singlePreview.style.display = 'none'; multiPreview.style.display = 'block'; imageList.innerHTML = ''; updateProgress(); multiPreview.scrollIntoView({ behavior: 'smooth', block: 'start' }); imageQueue.forEach(item => createImageCard(item)); processQueue(); } } async function processSingle(item) { try { const img = await loadImage(item.file); item.originalImg = img; originalCanvas.width = img.width; originalCanvas.height = img.height; originalCanvas.getContext('2d').drawImage(img, 0, 0); // 显示图片信息 const watermarkInfo = engine.getWatermarkInfo(img.width, img.height); originalInfo.innerHTML = ` ${i18n.t('info.size')}:${img.width} × ${img.height} px
${i18n.t('info.watermark')}:${watermarkInfo.size}×${watermarkInfo.size} px
${i18n.t('info.position')}:(${watermarkInfo.position.x}, ${watermarkInfo.position.y}) `; const result = await engine.removeWatermarkFromImage(img); const blob = await new Promise(resolve => result.toBlob(resolve, 'image/png')); item.processedBlob = blob; processedImage.src = URL.createObjectURL(blob); processedSection.style.display = 'block'; downloadBtn.style.display = 'flex'; downloadBtn.onclick = () => downloadImage(item); processedInfo.innerHTML = ` ${i18n.t('info.size')}:${img.width} × ${img.height} px
${i18n.t('info.status')}:${i18n.t('info.removed')} `; processedSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch (error) { console.error(error); } } function createImageCard(item) { const card = document.createElement('div'); card.id = `card-${item.id}`; card.className = 'bg-white md:h-[130px] rounded-xl shadow-card border border-gray-100 overflow-hidden'; card.innerHTML = `

${item.name}

${i18n.t('status.pending')}
`; imageList.appendChild(card); } async function processQueue() { for (const item of imageQueue) { const img = await loadImage(item.file); item.originalImg = img; document.getElementById(`result-${item.id}`).src = img.src; } for (const item of imageQueue) { if (item.status !== 'pending') continue; item.status = 'processing'; updateStatus(item.id, i18n.t('status.processing')); try { const result = await engine.removeWatermarkFromImage(item.originalImg); const blob = await new Promise(resolve => result.toBlob(resolve, 'image/png')); item.processedBlob = blob; document.getElementById(`result-${item.id}`).src = URL.createObjectURL(blob); item.status = 'completed'; const watermarkInfo = engine.getWatermarkInfo(item.originalImg.width, item.originalImg.height); updateStatus(item.id, `${i18n.t('info.size')}:${item.originalImg.width} × ${item.originalImg.height} px
${i18n.t('info.watermark')}:${watermarkInfo.size}×${watermarkInfo.size} px
${i18n.t('info.position')}:(${watermarkInfo.position.x}, ${watermarkInfo.position.y})`, true); const downloadBtn = document.getElementById(`download-${item.id}`); downloadBtn.classList.remove('hidden'); downloadBtn.onclick = () => downloadImage(item); processedCount++; updateProgress(); } catch (error) { item.status = 'error'; updateStatus(item.id, i18n.t('status.failed')); console.error(error); } } if (processedCount > 0) { downloadAllBtn.style.display = 'flex'; } } function loadImage(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = reject; img.src = e.target.result; }; reader.onerror = reject; reader.readAsDataURL(file); }); } function updateStatus(id, text, isHtml = false) { const el = document.getElementById(`status-${id}`); if (el) el.innerHTML = isHtml ? text : text.replace(/\n/g, '
'); } function updateProgress() { progressText.textContent = `${i18n.t('progress.text')}: ${processedCount}/${imageQueue.length}`; } function updateDynamicTexts() { if (progressText.textContent) { updateProgress(); } } function downloadImage(item) { const a = document.createElement('a'); a.href = URL.createObjectURL(item.processedBlob); a.download = `unwatermarked_${item.name.replace(/\.[^.]+$/, '')}.png`; a.click(); } async function downloadAll() { const completed = imageQueue.filter(item => item.status === 'completed'); if (completed.length === 0) return; const zip = new JSZip(); completed.forEach(item => { const filename = `unwatermarked_${item.name.replace(/\.[^.]+$/, '')}.png`; zip.file(filename, item.processedBlob); }); const blob = await zip.generateAsync({ type: 'blob' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `unwatermarked_${Date.now()}.zip`; a.click(); } function showLoading(text = null) { loadingOverlay.style.display = 'flex'; const textEl = loadingOverlay.querySelector('p'); if (textEl && text) textEl.textContent = text; } function hideLoading() { loadingOverlay.style.display = 'none'; } init();