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();
|