xiaohongshufabu/extension/popup/popup.js
2026-07-21 21:17:20 +08:00

168 lines
4.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const $ = (sel) => document.querySelector(sel);
// Promise-based sendMessage with timeout (MV3 reliable)
function sendMsg(msg, timeoutMs = 10000) {
return new Promise((resolve) => {
let settled = false;
const timer = setTimeout(() => {
if (!settled) {
settled = true;
resolve({ error: "消息超时,请检查插件是否正常加载" });
}
}, timeoutMs);
chrome.runtime.sendMessage(msg, (res) => {
if (settled) return;
settled = true;
clearTimeout(timer);
if (chrome.runtime.lastError) {
resolve({ error: chrome.runtime.lastError.message });
} else {
resolve(res || { error: "无响应" });
}
});
});
}
function setUI(state) {
const dot = $("#statusDot");
const text = $("#statusText");
const btn = $("#btnToggle");
if (state === "loading") {
dot.className = "dot dot-gray";
text.textContent = "连接中...";
btn.textContent = "启动";
btn.className = "btn btn-primary";
btn.disabled = true;
} else if (state === "error") {
dot.className = "dot dot-red";
text.textContent = "服务未连接";
btn.textContent = "启动";
btn.className = "btn btn-primary";
btn.disabled = true;
$("#statToday").textContent = "-";
$("#statPending").textContent = "-";
$("#statFails").textContent = "-";
$("#todayCount").textContent = "- / -";
$("#connTest").innerHTML = '<span class="fail">无法连接到后台脚本,请右键插件→重新加载</span>';
} else if (state === "running") {
dot.className = "dot dot-green";
text.textContent = "运行中";
btn.textContent = "停止";
btn.className = "btn btn-danger";
btn.disabled = false;
} else if (state === "stopped") {
dot.className = "dot dot-gray";
text.textContent = "已停止";
btn.textContent = "启动";
btn.className = "btn btn-primary";
btn.disabled = false;
}
}
async function loadStatus() {
setUI("loading");
const status = await sendMsg({ type: "GET_STATUS" });
if (!status || status.error) {
setUI("error");
return;
}
setUI(status.polling ? "running" : "stopped");
if (status.statusError) {
$("#connTest").innerHTML = `<span class="fail">${status.statusError}</span>`;
} else {
$("#connTest").innerHTML = "";
}
$("#statToday").textContent = status.todayCount;
$("#statFails").textContent = status.consecutiveFailures;
const config = await sendMsg({ type: "GET_CONFIG" });
if (config && !config.error) {
$("#shopId").value = config.shopId || "";
$("#serverUrl").value = config.serverUrl || "http://localhost:3000";
const limit = config.dailyLimit || 20;
const pending = status.pendingCount || 0;
$("#statPending").textContent = pending;
$("#todayCount").textContent = `${status.todayCount} / ${limit}`;
}
}
$("#btnToggle").addEventListener("click", async () => {
const btn = $("#btnToggle");
btn.disabled = true;
btn.textContent = "处理中...";
// Save config first
await saveConfig();
// Check current status and toggle
const status = await sendMsg({ type: "GET_STATUS" });
const isRunning = status && status.polling;
if (isRunning) {
await sendMsg({ type: "STOP_POLLING" });
} else {
const res = await sendMsg({ type: "START_POLLING" });
if (res && res.error) {
console.error("启动失败:", res.error);
}
}
// Reload status to update UI
await loadStatus();
});
$("#btnTest").addEventListener("click", async () => {
const testEl = $("#connTest");
testEl.innerHTML = "测试中...";
await saveConfig();
const res = await sendMsg({ type: "TEST_CONNECTION" }, 5000);
if (res && res.ok) {
testEl.innerHTML = '<span class="ok">✓ 连接成功</span>';
} else {
testEl.innerHTML = `<span class="fail">✗ ${res?.error || "连接失败,请确认本地服务已启动"}</span>`;
}
});
function saveConfig() {
return new Promise((resolve) => {
chrome.storage.local.get(["config"], (data) => {
const existing = data.config || {};
const config = {
...existing,
serverUrl: $("#serverUrl").value.trim() || "http://localhost:3000",
shopId: $("#shopId").value.trim(),
};
chrome.storage.local.set({ config }, resolve);
});
});
}
let refreshTimer = null;
function scheduleSaveAndRefresh() {
clearTimeout(refreshTimer);
refreshTimer = setTimeout(async () => {
await saveConfig();
await loadStatus();
}, 400);
}
// 输入店铺名时要立即保存并刷新数量change 只有失焦后才触发,容易看起来没有同步。
["shopId", "serverUrl"].forEach((id) => {
const input = $(`#${id}`);
input.addEventListener("input", scheduleSaveAndRefresh);
input.addEventListener("change", async () => {
clearTimeout(refreshTimer);
await saveConfig();
await loadStatus();
});
});
loadStatus();