const SETTINGS_KEY = "webToPixsoSettings"; const HELP_URL = "https://z8qrcvi3n5.feishu.cn/wiki/RV8TwlhFyiGsEekQXk8cX5SHn6f"; const DEFAULT_SETTINGS = { useProxy: false, concurrency: "8", captureMode: "mixed", captureWidth: null }; const MIN_CAPTURE_WIDTH = 320; const MAX_CAPTURE_WIDTH = 3840; const captureModeSelect = document.getElementById("captureMode"); const proxyToggle = document.getElementById("proxyToggle"); const captureWidthInput = document.getElementById("captureWidth"); const concurrencySelect = document.getElementById("concurrency"); const captureBtn = document.getElementById("captureBtn"); const btnText = document.getElementById("btnText"); const selectBtn = document.getElementById("selectBtn"); const selectBtnText = document.getElementById("selectBtnText"); const status = document.getElementById("status"); const statusText = document.getElementById("statusText"); const progressFill = document.getElementById("progressFill"); const closeBtn = document.getElementById("closeBtn"); const helpLink = document.getElementById("helpLink"); function normalizeSettings(value = {}) { const concurrency = String(value.concurrency || DEFAULT_SETTINGS.concurrency); const captureWidth = normalizeCaptureWidth(value.captureWidth, null); return { useProxy: Boolean(value.useProxy), concurrency: ["4", "6", "8", "10", "12", "16", "20", "infinite"].includes(concurrency) ? concurrency : DEFAULT_SETTINGS.concurrency, captureMode: value.captureMode === "editable" ? "editable" : "mixed", captureWidth }; } 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)); } 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; } } async function getSettings() { const result = await chrome.storage.local.get({ [SETTINGS_KEY]: DEFAULT_SETTINGS }); return normalizeSettings(result[SETTINGS_KEY]); } async function saveSettings(settings) { await chrome.storage.local.set({ [SETTINGS_KEY]: normalizeSettings(settings) }); } function setProgress(percent, text) { status.hidden = false; progressFill.style.width = `${Math.max(0, Math.min(100, percent))}%`; statusText.textContent = text; } function setBusy(isBusy) { captureBtn.disabled = isBusy; selectBtn.disabled = isBusy; captureBtn.classList.toggle("loading", isBusy); selectBtn.classList.toggle("loading", isBusy); captureBtn.classList.remove("success", "error"); btnText.textContent = isBusy ? "采集中..." : "开始采集"; selectBtnText.textContent = isBusy ? "正在打开浮窗..." : "打开页面浮窗"; } function setResult(kind, text) { captureBtn.classList.remove("loading", "success", "error"); captureBtn.classList.add(kind); btnText.textContent = text; } async function syncSettingsFromUI() { await saveSettings({ captureMode: captureModeSelect.value, useProxy: proxyToggle.checked, concurrency: concurrencySelect.value, captureWidth: normalizeCaptureWidth(captureWidthInput.value, null) }); } function sanitizeCaptureWidthInput({ clamp = false } = {}) { const digits = captureWidthInput.value.replace(/\D+/g, ""); captureWidthInput.value = digits; const width = normalizeCaptureWidth(digits, null); const rawNumber = Number.parseInt(digits, 10); let invalid = Boolean(digits) && Number.isFinite(rawNumber) && (rawNumber < MIN_CAPTURE_WIDTH || rawNumber > MAX_CAPTURE_WIDTH); captureWidthInput.classList.toggle("invalid", invalid); if (clamp && digits) { captureWidthInput.value = String(width); invalid = false; captureWidthInput.classList.remove("invalid"); } return width; } function readSettingsFromUI() { return normalizeSettings({ captureMode: captureModeSelect.value, useProxy: proxyToggle.checked, concurrency: concurrencySelect.value, captureWidth: sanitizeCaptureWidthInput({ clamp: true }) }); } async function startCapture() { setBusy(true); setProgress(12, "准备当前网页..."); const settings = readSettingsFromUI(); await saveSettings(settings); try { setProgress(28, "注入采集脚本..."); const response = await chrome.runtime.sendMessage({ type: "PIXSO_CAPTURE_START", options: settings }); if (!response || !response.ok) { throw new Error(response?.error || "采集失败"); } const viewportText = response.actualViewportWidth ? `已按 ${response.actualViewportWidth}px 采集` : "已按当前页面尺寸采集"; setProgress(100, `${viewportText}:${response.filename}`); setResult("success", "采集完成"); setTimeout(() => window.close(), 900); } catch (error) { setProgress(0, error.message || String(error)); setResult("error", "采集失败"); setTimeout(() => { setBusy(false); status.hidden = true; progressFill.style.width = "0"; }, 2600); } } async function startFloatingToolbar() { setBusy(true); setProgress(18, "正在打开页面采集浮窗..."); const settings = readSettingsFromUI(); await saveSettings(settings); try { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); if (!tab?.id) throw new Error("没有可选择的当前标签页"); await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ["element-picker.js"] }); await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: pickerSettings => window.__webToPixsoShowCaptureToolbar?.(pickerSettings), args: [settings] }); setProgress(100, "请在页面浮窗中选择整页或元素采集。"); setTimeout(() => window.close(), 500); } catch (error) { setProgress(0, error.message || String(error)); setResult("error", "采集失败"); setTimeout(() => { setBusy(false); status.hidden = true; progressFill.style.width = "0"; }, 2600); } } document.addEventListener("DOMContentLoaded", async () => { const settings = await getSettings(); const currentWidth = await getCurrentViewportWidth(); captureModeSelect.value = settings.captureMode; proxyToggle.checked = settings.useProxy; concurrencySelect.value = settings.concurrency; captureWidthInput.value = String(settings.captureWidth || currentWidth || ""); }); captureModeSelect.addEventListener("change", syncSettingsFromUI); proxyToggle.addEventListener("change", syncSettingsFromUI); concurrencySelect.addEventListener("change", syncSettingsFromUI); captureWidthInput.addEventListener("input", () => { sanitizeCaptureWidthInput(); syncSettingsFromUI(); }); captureWidthInput.addEventListener("blur", () => { sanitizeCaptureWidthInput({ clamp: true }); syncSettingsFromUI(); }); captureBtn.addEventListener("click", startCapture); selectBtn.addEventListener("click", startFloatingToolbar); closeBtn.addEventListener("click", () => window.close()); helpLink?.addEventListener("click", async event => { event.preventDefault(); await chrome.tabs.create({ url: HELP_URL }); window.close(); });