const SETTINGS_KEY = 'webToPPTSettings'; const DEFAULT_SETTINGS = { useProxy: false, concurrency: '8', captureMode: 'mixed', captureWidth: null }; const MIN_CAPTURE_WIDTH = 320; const MAX_CAPTURE_WIDTH = 3840; const captureWidthInput = document.getElementById('captureWidth'); const exportBtn = document.getElementById('exportBtn'); const btnText = document.getElementById('btnText'); const status = document.getElementById('status'); const statusText = document.getElementById('statusText'); const progressFill = document.getElementById('progressFill'); function normalizeCaptureWidth(value, fallback = null) { const number = Number.parseInt(String(value || '').replace(/\D+/g, ''), 10); if (!Number.isFinite(number)) return fallback; return Math.max(MIN_CAPTURE_WIDTH, Math.min(MAX_CAPTURE_WIDTH, number)); } function normalizeSettings(value = {}) { return { useProxy: Boolean(value.useProxy), captureMode: 'mixed', captureWidth: normalizeCaptureWidth(value.captureWidth, null) }; } async function getSettings() { const result = await chrome.storage.local.get({ [SETTINGS_KEY]: DEFAULT_SETTINGS }); return normalizeSettings(result[SETTINGS_KEY]); } async function getCurrentViewportWidth() { try { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); if (!tab?.id) return null; const [{ result }] = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: () => Math.round(window.innerWidth || document.documentElement.clientWidth || 0) }); return normalizeCaptureWidth(result, null); } catch { return null; } } function setProgress(percent, text) { status.hidden = false; progressFill.style.width = `${Math.max(0, Math.min(100, percent))}%`; statusText.textContent = text; } function setBusy(isBusy) { exportBtn.disabled = isBusy; exportBtn.classList.toggle('loading', isBusy); exportBtn.classList.remove('success', 'error'); btnText.textContent = isBusy ? '处理中...' : '导出 PPTX'; } function setResult(kind, text) { exportBtn.classList.remove('loading', 'success', 'error'); exportBtn.classList.add(kind); btnText.textContent = text; } async function exportPptx() { setBusy(true); setProgress(5, '准备采集...'); const captureWidth = normalizeCaptureWidth(captureWidthInput.value, null); const settings = { ...DEFAULT_SETTINGS, captureWidth }; try { // 1. 抓取 DOM setProgress(15, '正在采集页面...'); const response = await chrome.runtime.sendMessage({ type: 'WEB_TO_PPT_CAPTURE_START', options: settings }); if (!response || !response.ok) { throw new Error(response?.error || '采集失败'); } // 2. 获取 JSON 数据(从 background 返回的文件路径读取不了,直接从 background 拿数据) // 需要让 background 返回 JSON 数据而不是文件 setProgress(40, '采集完成,正在转化...'); // background 已经下载了 JSON 文件,但我们需要数据来转化 // 用另一个消息获取数据 const dataResponse = await chrome.runtime.sendMessage({ type: 'WEB_TO_PPT_GET_DATA' }); if (!dataResponse || !dataResponse.ok) { throw new Error(dataResponse?.error || '获取数据失败'); } const jsonData = dataResponse.data; // 3. 转化 setProgress(50, '正在生成 PPTX...'); const schema = await WebToPPT.convertToPptx(jsonData, (msg) => { setProgress(50 + Math.min(40, Math.floor(Math.random() * 40)), msg); }); // 4. 生成 PPTX setProgress(90, '正在打包...'); const blob = await WebToPPT.schemaToPptxBlob(schema); // 5. 下载(弹出保存对话框) setProgress(95, '正在下载...'); const url = URL.createObjectURL(blob); const title = jsonData.source?.title || 'webpage'; const safeTitle = title.replace(/[\\/:*?"<>|]+/g, '-').replace(/\s+/g, '-').slice(0, 64) || 'webpage'; const filename = `web-to-ppt/${safeTitle}-${Date.now()}.pptx`; await chrome.downloads.download({ url, filename, saveAs: true }); setTimeout(() => URL.revokeObjectURL(url), 60000); setProgress(100, `已导出:${filename}`); setResult('success', '导出完成'); // 不自动关闭 popup,等用户选完保存位置 } catch (error) { setProgress(0, error.message || String(error)); setResult('error', '导出失败'); setTimeout(() => { setBusy(false); status.hidden = true; progressFill.style.width = '0'; }, 3000); } } // 初始化 document.addEventListener('DOMContentLoaded', async () => { const currentWidth = await getCurrentViewportWidth(); captureWidthInput.value = String(currentWidth || ''); }); exportBtn.addEventListener('click', exportPptx); captureWidthInput.addEventListener('input', () => { captureWidthInput.value = captureWidthInput.value.replace(/\D+/g, ''); });