168 lines
4.8 KiB
JavaScript
168 lines
4.8 KiB
JavaScript
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();
|