Files
LWLT-AIBOT/chrome-extension/ltjt-order-assistant/popup.js
T
2026-08-29 15:02:51 +08:00

229 lines
8.0 KiB
JavaScript

'use strict';
const BUSINESS_SOURCE = 'LIANSYN_PLATFORM';
const EXTENSION_SOURCE = 'LTJT_ORDER_ASSISTANT_EXTENSION';
const PRODUCTION_PLATFORM_ORIGIN = 'https://lwlt.nianxx.cn';
const $ = (selector) => document.querySelector(selector);
function setText(selector, text) {
const el = $(selector);
if (el) el.textContent = text;
}
function setBadge(text, mode = '') {
const badge = $('#stateBadge');
if (!badge) return;
badge.textContent = text;
badge.classList.toggle('bad', mode === 'bad');
badge.classList.toggle('paused', mode === 'paused');
}
function isBusinessSystemUrl(url) {
try {
const parsed = new URL(String(url || ''));
if (parsed.origin === PRODUCTION_PLATFORM_ORIGIN) return true;
return parsed.protocol === 'http:'
&& ['localhost', '127.0.0.1'].includes(parsed.hostname)
&& ['', '8765', '8786'].includes(parsed.port);
} catch (error) {
return false;
}
}
function friendlyError(error) {
const message = error?.message || String(error || '');
if (/Extension context invalidated/i.test(message)) {
return '插件刚重新加载,旧页面桥接已失效。请刷新业务系统页面后重新打开本面板。';
}
return message;
}
async function findBusinessTab() {
const tabs = await chrome.tabs.query({});
return tabs.find((tab) => isBusinessSystemUrl(tab.url)) || null;
}
async function pingBusinessBridge(tabId) {
await chrome.scripting.executeScript({
target: { tabId },
files: ['business-bridge.js']
});
const [probe] = await chrome.scripting.executeScript({
target: { tabId },
func: async (businessSource, extensionSource) => {
const requestId = `POPUP-PING-${Date.now()}-${Math.random().toString(16).slice(2)}`;
return new Promise((resolve) => {
let staleError = null;
const timer = window.setTimeout(() => {
window.removeEventListener('message', onMessage);
resolve(staleError || {
ok: false,
message: '业务系统页面未收到插件桥接响应。'
});
}, 1500);
function onMessage(event) {
if (event.source !== window) return;
const message = event.data || {};
if (message.source !== extensionSource || message.requestId !== requestId) return;
if (message.type === 'ERROR') {
const errorMessage = String(message.payload?.message || '');
if (/Extension context invalidated/i.test(errorMessage)) {
// An extension reload can leave old content-script handlers in the
// page. Keep waiting for the current bridge's valid PONG.
staleError = { ok: false, message: errorMessage };
return;
}
}
if (message.type !== 'PONG' || message.payload?.ok === false) return;
window.clearTimeout(timer);
window.removeEventListener('message', onMessage);
resolve(message.payload || { ok: false, message: '插件桥接返回为空。' });
}
window.addEventListener('message', onMessage);
window.postMessage({
source: businessSource,
requestId,
type: 'PING',
payload: {}
}, window.location.origin);
});
},
args: [BUSINESS_SOURCE, EXTENSION_SOURCE]
});
return probe?.result || {
ok: false,
message: '无法读取业务系统桥接检测结果。'
};
}
async function detectBusinessConnection() {
const tab = await findBusinessTab();
if (!tab?.id) {
return {
connected: false,
bridge: null,
text: '未打开业务系统页面',
hint: '请打开生产控制平面或 http://127.0.0.1:8786/,然后刷新本面板。'
};
}
try {
const bridge = await pingBusinessBridge(tab.id);
if (!bridge.ok) {
return {
connected: false,
bridge,
text: '业务系统未联通',
hint: bridge.message || '插件桥接未响应。'
};
}
return {
connected: true,
bridge,
text: '已联通',
hint: '业务系统页面已收到插件桥接响应。'
};
} catch (error) {
return {
connected: false,
bridge: null,
text: '业务系统未联通',
hint: friendlyError(error)
};
}
}
async function readAutomationEnabled() {
const saved = await chrome.storage.local.get('erpAutomationEnabled');
return saved.erpAutomationEnabled !== false;
}
async function readKeepaliveEnabled() {
const saved = await chrome.storage.local.get('erpKeepaliveEnabled');
return saved.erpKeepaliveEnabled !== false;
}
function keepaliveStatusText(connection, automationEnabled, userEnabled) {
if (!automationEnabled) return '随“允许插件操作 ERP”关闭。';
if (!userEnabled) return '已关闭;插件不会发送待机保活请求。';
const keepalive = connection.bridge?.erp_session?.erp_keepalive;
if (['scheduled', 'already_running', 'running'].includes(keepalive?.recovery_status)) {
return '检测到 ERP 链路异常,正在刷新现有 ERP 页面并进行只读复测。';
}
if (keepalive?.recovery_status === 'success' && keepalive.last_recovered_at) {
return `已自动刷新并恢复 ERP 链路:${new Date(keepalive.last_recovered_at).toLocaleString()}`;
}
if (keepalive?.recovery_status === 'failed' && keepalive.recovery_error) {
return `ERP 链路异常,已尝试刷新但仍未恢复:${keepalive.recovery_error}。`;
}
if (keepalive?.last_status === 'success' && keepalive.last_success_at) {
return `已开启;最近一次成功:${new Date(keepalive.last_success_at).toLocaleString()}`;
}
if (keepalive?.last_status === 'session_expired') return '已开启,但最近一次检测发现 ERP 会话已失效,请重新登录。';
if (keepalive?.last_status === 'failed') return `已开启;最近一次检测失败:${keepalive.last_error || '网络或页面不可用'}。`;
return '已开启;约每 5 分钟发送一次只读请求,不刷新当前页面。';
}
async function updateStatusPanel() {
setBadge('检测中');
setText('#versionLabel', `v${chrome.runtime.getManifest().version}`);
const [enabled, keepaliveEnabled, connection] = await Promise.all([
readAutomationEnabled(),
readKeepaliveEnabled(),
detectBusinessConnection()
]);
const toggle = $('#erpAutomationToggle');
if (toggle) toggle.checked = enabled;
const keepaliveToggle = $('#erpKeepaliveToggle');
if (keepaliveToggle) {
keepaliveToggle.checked = keepaliveEnabled;
keepaliveToggle.disabled = !enabled;
}
setText('#keepaliveText', keepaliveStatusText(connection, enabled, keepaliveEnabled));
if (!enabled) {
setBadge('已关闭', 'paused');
setText('#connectionText', connection.connected ? '已联通,ERP 操作已关闭' : '未联通,ERP 操作已关闭');
setText('#connectionHint', connection.connected
? '业务系统已连接;插件不会打开、填写或提交 ERP。'
: connection.hint);
return;
}
setBadge(connection.connected ? '已联通' : '未联通', connection.connected ? '' : 'bad');
setText('#connectionText', connection.connected ? '已联通,ERP 操作已开启' : connection.text);
setText('#connectionHint', connection.connected
? '插件允许操作 ERP。任务创建和进度跟踪请在业务系统中完成。'
: connection.hint);
}
document.addEventListener('DOMContentLoaded', async () => {
const toggle = $('#erpAutomationToggle');
if (toggle) {
toggle.checked = await readAutomationEnabled();
toggle.addEventListener('change', async (event) => {
await chrome.storage.local.set({ erpAutomationEnabled: event.currentTarget.checked });
await updateStatusPanel();
});
}
const keepaliveToggle = $('#erpKeepaliveToggle');
if (keepaliveToggle) {
keepaliveToggle.checked = await readKeepaliveEnabled();
keepaliveToggle.addEventListener('change', async (event) => {
await chrome.storage.local.set({ erpKeepaliveEnabled: event.currentTarget.checked });
await updateStatusPanel();
});
}
await updateStatusPanel();
window.setTimeout(() => updateStatusPanel().catch(() => {}), 800);
});