// ==UserScript==
// @name 货查查助手(Tampermonkey)
// @namespace https://hcc321.com/
// @version 2.0.0
// @description 多 WMS 抓取与上传(PB / QY / JF),含登录、工位、监控与上传能力
// @author HCCvision
// @match https://cnwms.aiyacang.com/*
// @match https://wms.aiyacang.com/*
// @match http://wms.aiyacang.com/*
// @match https://gwmsth.best-inc.com/*
// @match http://gwmsth.best-inc.com/*
// @match https://oms.prorclub.com/*
// @match http://oms.prorclub.com/*
// @match https://*.jfwms.com/*
// @match http://*.jfwms.com/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @connect hcc321.com
// @run-at document-start
// ==/UserScript==
/**
* Pack Monitor - Tampermonkey Script
* 多 WMS 抓取与上传(PB / QY / JF)
*/
(() => {
if (window.__HCC_MULTI_WMS_MONITOR__) {
return;
}
window.__HCC_MULTI_WMS_MONITOR__ = true;
const IS_TOP = window.top === window;
const API_BASE_URL = 'https://hcc321.com';
const gmApi = {
getValue: async (key, defaultValue) => {
if (typeof GM_getValue === 'function') {
const value = GM_getValue(key);
return value === undefined ? defaultValue : value;
}
if (typeof GM !== 'undefined' && typeof GM.getValue === 'function') {
return GM.getValue(key, defaultValue);
}
return defaultValue;
},
setValue: async (key, value) => {
if (typeof GM_setValue === 'function') {
GM_setValue(key, value);
return;
}
if (typeof GM !== 'undefined' && typeof GM.setValue === 'function') {
await GM.setValue(key, value);
}
},
deleteValue: async (key) => {
if (typeof GM_deleteValue === 'function') {
GM_deleteValue(key);
return;
}
if (typeof GM !== 'undefined' && typeof GM.deleteValue === 'function') {
await GM.deleteValue(key);
}
},
listValues: async () => {
if (typeof GM_listValues === 'function') {
return GM_listValues();
}
if (typeof GM !== 'undefined' && typeof GM.listValues === 'function') {
return GM.listValues();
}
return [];
}
};
const createStorageArea = () => {
const listeners = new Set();
const emitChanges = (changes) => {
for (const listener of listeners) {
try {
listener(changes, 'local');
} catch (error) {
console.error('[TM storage.onChanged] listener error:', error);
}
}
};
const normalizeGetInput = async (keys) => {
const out = {};
if (keys == null) {
const allKeys = await gmApi.listValues();
for (const key of allKeys) {
out[key] = await gmApi.getValue(key);
}
return out;
}
if (Array.isArray(keys)) {
for (const key of keys) {
out[key] = await gmApi.getValue(key);
}
return out;
}
if (typeof keys === 'string') {
out[keys] = await gmApi.getValue(keys);
return out;
}
if (typeof keys === 'object') {
for (const [key, defaultValue] of Object.entries(keys)) {
out[key] = await gmApi.getValue(key, defaultValue);
}
}
return out;
};
return {
async get(keys, callback) {
const result = await normalizeGetInput(keys);
if (typeof callback === 'function') {
callback(result);
}
return result;
},
async set(items) {
const changes = {};
for (const [key, value] of Object.entries(items || {})) {
const oldValue = await gmApi.getValue(key);
await gmApi.setValue(key, value);
changes[key] = { oldValue, newValue: value };
}
if (Object.keys(changes).length > 0) {
emitChanges(changes);
}
},
async remove(keys) {
const keyList = Array.isArray(keys) ? keys : [keys];
const changes = {};
for (const key of keyList) {
const oldValue = await gmApi.getValue(key);
await gmApi.deleteValue(key);
changes[key] = { oldValue, newValue: undefined };
}
if (Object.keys(changes).length > 0) {
emitChanges(changes);
}
}
};
};
const createRuntimeApi = () => {
const listeners = new Set();
return {
sendMessage(message) {
let firstResponse;
for (const listener of listeners) {
let syncResponse;
const sendResponse = (payload) => {
if (firstResponse === undefined) {
firstResponse = payload;
}
syncResponse = payload;
};
try {
const maybeResult = listener(message, null, sendResponse);
if (firstResponse === undefined && maybeResult !== undefined) {
firstResponse = maybeResult;
} else if (firstResponse === undefined && syncResponse !== undefined) {
firstResponse = syncResponse;
}
} catch (error) {
console.error('[TM runtime.sendMessage] listener error:', error);
}
}
return Promise.resolve(firstResponse);
},
onMessage: {
addListener(listener) {
listeners.add(listener);
},
removeListener(listener) {
listeners.delete(listener);
}
}
};
};
const chrome = {
storage: {
local: createStorageArea(),
onChanged: {
addListener() {},
removeListener() {}
}
},
runtime: createRuntimeApi()
};
const parseRawHeaders = (rawHeaders) => {
const headers = new Map();
for (const line of String(rawHeaders || '').split(/\r?\n/)) {
const idx = line.indexOf(':');
if (idx <= 0) continue;
const key = line.slice(0, idx).trim().toLowerCase();
const value = line.slice(idx + 1).trim();
if (!key) continue;
headers.set(key, value);
}
return {
get(name) {
return headers.get(String(name || '').toLowerCase()) || null;
}
};
};
const platformFetch = async (url, options = {}) => {
if (typeof GM_xmlhttpRequest !== 'function') {
return fetch(url, options);
}
return new Promise((resolve, reject) => {
const body = options.body instanceof URLSearchParams ? options.body.toString() : options.body;
GM_xmlhttpRequest({
method: options.method || 'GET',
url,
headers: options.headers || {},
data: body,
onload: (response) => {
const textBody = response.responseText || '';
const headers = parseRawHeaders(response.responseHeaders);
resolve({
ok: response.status >= 200 && response.status < 300,
status: response.status,
headers,
json: async () => JSON.parse(textBody),
text: async () => textBody
});
},
onerror: (error) => reject(error),
ontimeout: () => reject(new Error('GM_xmlhttpRequest timeout'))
});
});
};
const STORAGE_KEYS = {
packMonitor: 'packMonitorData',
uploadStatus: 'uploadStatus',
osdMinimized: 'wmsOsdMinimized',
osdPosition: 'wmsOsdPosition',
isLoggedIn: 'isLoggedIn',
jwtToken: 'jwtToken',
userId: 'userId',
userData: 'userData',
currentStationId: 'currentStationId',
currentStationData: 'currentStationData'
};
const WMS_LABELS = {
pb: '朋博 WMS',
qy: '千易 WMS',
jf: '极风 WMS'
};
let currentStationId = null;
let activeDriver = null;
let activeDriverName = '';
let refreshToken = 0;
const osdState = {
root: null,
panel: null,
fab: null,
tracking: null,
sku: null,
status: null,
skuCompact: null,
statusCompact: null,
endOrderRow: null,
endOrderBtn: null,
// 登录视图
loginView: null,
loginIdInput: null,
loginPasswordInput: null,
loginBtn: null,
loginMessage: null,
// 工位选择视图
stationView: null,
stationList: null,
stationCurrentCard: null,
stationCurrentName: null,
stationCurrentDetail: null,
stationLoading: null,
stationEmpty: null,
stationPagination: null,
stationPrevBtn: null,
stationNextBtn: null,
stationPageInfo: null,
stationRefreshBtn: null,
stationEmptyRefreshBtn: null,
// 监控视图
monitorView: null,
logoutBtn: null,
copyTrackingBtn: null,
copySkuBtn: null,
updateTime: null
};
// 数字补零。
const pad = (n) => String(n).padStart(2, '0');
// 时间格式化为 yyyy-MM-dd HH:mm:ss。
const formatDateTime = (value) => {
if (!value) return '未记录';
const date = value instanceof Date ? value : new Date(value);
if (Number.isNaN(date.getTime())) return '未记录';
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
};
// 文本归一化并去除首尾空白。
const normalizeText = (value) => String(value || '').trim();
// 为高频事件创建防抖包装。
const debounce = (fn, delay) => {
let timer = null;
return function debounced(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
};
// 将数值限制在指定范围内。
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
// 输出步骤日志(包含模块与关键信息)。
const logStep = (scope, message, data) => {
if (data !== undefined) {
console.log(`[WMS:${scope}] ${message}`, data);
return;
}
console.log(`[WMS:${scope}] ${message}`);
};
// 更新 OSD 上传状态显示。
const updateOsdStatus = (statusData) => {
if (!IS_TOP || !osdState.status) return;
const status = statusData?.status || 'idle';
const message = statusData?.message || '待更新';
osdState.status.textContent = message;
osdState.status.setAttribute('data-status', status);
if (osdState.statusCompact) {
osdState.statusCompact.textContent = message;
osdState.statusCompact.setAttribute('data-status', status);
}
logStep('osd', '上传状态更新', { status, message });
};
// 更新 OSD 运单号与 SKU 显示。
const updateOsdData = (data) => {
if (!IS_TOP || !osdState.tracking || !osdState.sku) return;
const tracking = data?.trackingNo || '';
const sku = data?.sku || '';
logStep('osd', '数据更新', { tracking, sku });
// 渲染值并触发短暂高亮动画。
const setValue = (el, value) => {
el.textContent = value || '等待中';
if (value) {
el.classList.add('pulse');
window.setTimeout(() => el.classList.remove('pulse'), 220);
}
};
const flashSku = (el) => {
if (!el) return;
if (el.__skuFlashTimer) {
window.clearTimeout(el.__skuFlashTimer);
el.__skuFlashTimer = null;
}
el.classList.add('sku-hot');
el.__skuFlashTimer = window.setTimeout(() => {
el.classList.remove('sku-hot');
el.__skuFlashTimer = null;
}, 3000);
};
setValue(osdState.tracking, tracking);
setValue(osdState.sku, sku);
if (osdState.skuCompact) {
setValue(osdState.skuCompact, sku);
}
if (sku) {
flashSku(osdState.sku);
flashSku(osdState.skuCompact);
} else {
osdState.sku.classList.remove('sku-hot');
if (osdState.skuCompact) osdState.skuCompact.classList.remove('sku-hot');
}
// 显示/隐藏结束订单按钮(有SKU时显示)
if (osdState.endOrderRow) {
osdState.endOrderRow.style.display = sku ? 'flex' : 'none';
}
// 更新时间
if (osdState.updateTime && data?.timestamp) {
const date = new Date(data.timestamp);
const timeStr = `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`;
osdState.updateTime.textContent = `${timeStr} 更新`;
}
};
// 切换 OSD 最小化/展开状态。
const applyOsdMinimized = (minimized) => {
if (!IS_TOP || !osdState.panel || !osdState.fab) return;
// 使用紧凑模式代替完全隐藏,避免占用过多空间。
osdState.panel.classList.toggle('compact', minimized);
osdState.panel.classList.remove('hidden');
osdState.fab.classList.add('hidden');
logStep('osd', minimized ? '面板紧凑' : '面板展开');
};
// 应用已保存的 OSD 面板位置(支持百分比相对位置)。
const applyOsdPosition = (pos) => {
if (!IS_TOP || !osdState.root || !pos) return;
const vw = window.innerWidth;
const vh = window.innerHeight;
// 支持新的百分比格式,兼容旧的像素格式
let leftPct, topPct;
if (typeof pos.leftPct === 'number' && typeof pos.topPct === 'number') {
leftPct = pos.leftPct;
topPct = pos.topPct;
} else if (typeof pos.left === 'number' && typeof pos.top === 'number') {
// 旧格式:转换为百分比(基于当前视口)
const rect = osdState.root.getBoundingClientRect();
const width = rect.width || 270;
const height = rect.height || 200;
leftPct = Math.min(100, Math.max(0, (pos.left / vw) * 100));
topPct = Math.min(100, Math.max(0, (pos.top / vh) * 100));
} else {
return;
}
// 计算实际像素位置(确保不超出视口)
const rootWidth = osdState.root.offsetWidth || 270;
const rootHeight = osdState.root.offsetHeight || 200;
const maxLeft = vw - rootWidth - 8;
const maxTop = vh - rootHeight - 8;
const actualLeft = Math.min(Math.max(8, (leftPct / 100) * vw), Math.max(8, maxLeft));
const actualTop = Math.min(Math.max(8, (topPct / 100) * vh), Math.max(8, maxTop));
osdState.root.style.left = `${actualLeft}px`;
osdState.root.style.top = `${actualTop}px`;
osdState.root.style.right = 'auto';
osdState.root.style.bottom = 'auto';
// 存储百分比供下次使用
osdState.root.dataset.leftPct = leftPct;
osdState.root.dataset.topPct = topPct;
logStep('osd', '应用面板位置', { leftPct, topPct, actualLeft, actualTop });
};
// 保存 OSD 面板位置到存储(使用百分比相对位置)。
const saveOsdPosition = (pos) => {
if (!chrome?.storage?.local) return;
let positionToSave;
if (pos && typeof pos.leftPct === 'number' && typeof pos.topPct === 'number') {
// 已经是百分比格式
positionToSave = { leftPct: pos.leftPct, topPct: pos.topPct };
} else if (pos && typeof pos.left === 'number' && typeof pos.top === 'number') {
// 像素格式:转换为百分比
const vw = window.innerWidth;
const vh = window.innerHeight;
positionToSave = {
leftPct: (pos.left / vw) * 100,
topPct: (pos.top / vh) * 100
};
} else {
return;
}
chrome.storage.local.set({
[STORAGE_KEYS.osdPosition]: positionToSave
});
logStep('osd', '保存面板位置(百分比)', positionToSave);
};
// 启用 OSD 拖拽移动。
const enableOsdDrag = (handle) => {
if (!handle || !osdState.root) return;
logStep('osd', '启用拖拽');
let dragging = false;
let offsetX = 0;
let offsetY = 0;
// 处理拖拽移动更新。
const onPointerMove = (event) => {
if (!dragging) return;
const rect = osdState.root.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const nextLeft = clamp(event.clientX - offsetX, 8, viewportWidth - rect.width - 8);
const nextTop = clamp(event.clientY - offsetY, 8, viewportHeight - rect.height - 8);
osdState.root.style.left = `${nextLeft}px`;
osdState.root.style.top = `${nextTop}px`;
osdState.root.style.right = 'auto';
osdState.root.style.bottom = 'auto';
};
// 结束拖拽并保存位置(使用百分比)。
const onPointerUp = () => {
if (!dragging) return;
dragging = false;
const rect = osdState.root.getBoundingClientRect();
const vw = window.innerWidth;
const vh = window.innerHeight;
// 保存为百分比,确保屏幕缩放后面板保持相对位置
saveOsdPosition({
leftPct: (rect.left / vw) * 100,
topPct: (rect.top / vh) * 100
});
window.removeEventListener('pointermove', onPointerMove);
window.removeEventListener('pointerup', onPointerUp);
};
handle.addEventListener('pointerdown', (event) => {
if (event.target?.closest?.('button')) return;
if (event.pointerType === 'mouse' && event.button !== 0) return;
dragging = true;
const rect = osdState.root.getBoundingClientRect();
offsetX = event.clientX - rect.left;
offsetY = event.clientY - rect.top;
handle.setPointerCapture?.(event.pointerId);
window.addEventListener('pointermove', onPointerMove);
window.addEventListener('pointerup', onPointerUp);
});
};
// 创建并挂载 OSD 界面。
const initOsd = () => {
if (!IS_TOP) return;
if (document.getElementById('hcc-wms-osd-root')) return;
logStep('osd', '初始化 OSD');
const root = document.createElement('div');
root.id = 'hcc-wms-osd-root';
const shadow = root.attachShadow({ mode: 'open' });
shadow.innerHTML = `
`;
document.documentElement.appendChild(root);
logStep('osd', 'OSD 挂载完成');
osdState.root = root;
osdState.panel = shadow.getElementById('osd-panel');
osdState.fab = shadow.getElementById('osd-expand');
osdState.tracking = shadow.getElementById('osd-tracking');
osdState.sku = shadow.getElementById('osd-sku');
osdState.skuCompact = shadow.getElementById('osd-sku-compact');
osdState.status = shadow.getElementById('osd-status');
osdState.statusCompact = shadow.getElementById('osd-status-compact');
osdState.endOrderRow = shadow.getElementById('end-order-row');
osdState.endOrderBtn = shadow.getElementById('osd-end-order');
// 登录视图元素
osdState.loginView = shadow.getElementById('view-login');
osdState.loginIdInput = shadow.getElementById('osd-login-id');
osdState.loginPasswordInput = shadow.getElementById('osd-login-password');
osdState.loginBtn = shadow.getElementById('osd-login-btn');
osdState.loginMessage = shadow.getElementById('osd-login-message');
// 工位选择视图元素
osdState.stationView = shadow.getElementById('view-station');
osdState.stationList = shadow.getElementById('osd-station-list');
osdState.stationCurrentCard = shadow.getElementById('osd-current-station-card');
osdState.stationCurrentName = shadow.getElementById('osd-current-station-name');
osdState.stationCurrentDetail = shadow.getElementById('osd-current-station-detail');
osdState.stationLoading = shadow.getElementById('osd-loading-state');
osdState.stationEmpty = shadow.getElementById('osd-empty-state');
osdState.stationPagination = shadow.getElementById('osd-pagination');
osdState.stationPrevBtn = shadow.getElementById('osd-prev-btn');
osdState.stationNextBtn = shadow.getElementById('osd-next-btn');
osdState.stationPageInfo = shadow.getElementById('osd-page-info');
osdState.stationRefreshBtn = shadow.getElementById('osd-refresh-station');
osdState.stationEmptyRefreshBtn = shadow.getElementById('osd-empty-refresh-btn');
// 监控视图元素
osdState.monitorView = shadow.getElementById('view-monitor');
osdState.logoutBtn = shadow.getElementById('osd-logout-btn');
osdState.copyTrackingBtn = shadow.getElementById('osd-copy-tracking');
osdState.copySkuBtn = shadow.getElementById('osd-copy-sku');
osdState.updateTime = shadow.getElementById('osd-update-time');
const tag = shadow.getElementById('osd-tag');
if (tag && activeDriverName) {
tag.textContent = WMS_LABELS[activeDriverName] || 'WMS';
}
const minimizeBtn = shadow.getElementById('osd-minimize');
const expandCompactBtn = shadow.getElementById('osd-expand-compact');
const applyMinimizeButtonState = (minimized) => {
minimizeBtn.textContent = minimized ? '+' : '—';
minimizeBtn.title = minimized ? '展开' : '最小化';
};
minimizeBtn.addEventListener('click', () => {
const isMinimized = osdState.panel?.classList.contains('compact');
const nextMinimized = !isMinimized;
applyOsdMinimized(nextMinimized);
applyMinimizeButtonState(nextMinimized);
chrome.storage.local.set({ [STORAGE_KEYS.osdMinimized]: nextMinimized });
});
expandCompactBtn.addEventListener('click', () => {
applyOsdMinimized(false);
applyMinimizeButtonState(false);
chrome.storage.local.set({ [STORAGE_KEYS.osdMinimized]: false });
});
osdState.fab.addEventListener('click', () => {
applyOsdMinimized(false);
applyMinimizeButtonState(false);
chrome.storage.local.set({ [STORAGE_KEYS.osdMinimized]: false });
});
// 绑定结束订单按钮事件
if (osdState.endOrderBtn) {
osdState.endOrderBtn.addEventListener('click', () => {
if (activeDriver && typeof activeDriver.forceEndOrder === 'function') {
logStep('osd', '手动触发结束订单');
activeDriver.forceEndOrder();
}
});
}
// ============ OSD 视图切换和登录/工位选择逻辑 ============
// 工位选择状态
let stationPage = 1;
let stationTotalPages = 1;
let selectedStationId = null;
// 视图切换函数
const switchView = (viewName) => {
const views = ['login', 'station', 'monitor'];
views.forEach((v) => {
const viewEl = osdState[`${v}View`];
if (viewEl) {
viewEl.classList.toggle('hidden', v !== viewName);
}
});
// 更新面板 class
osdState.panel.classList.remove('view-login', 'view-station');
if (viewName === 'login') {
osdState.panel.classList.add('view-login');
} else if (viewName === 'station') {
osdState.panel.classList.add('view-station');
}
logStep('osd', '切换视图', { view: viewName });
};
// 显示登录消息
const showLoginMessage = (text, isError = true) => {
if (!osdState.loginMessage) return;
osdState.loginMessage.textContent = text;
osdState.loginMessage.className = `status-message ${isError ? 'error' : 'success'}`;
};
// 设置登录按钮状态
const setLoginButtonState = (isLoading) => {
if (!osdState.loginBtn) return;
osdState.loginBtn.disabled = isLoading;
osdState.loginBtn.textContent = isLoading ? '登录中...' : '登 录';
};
// 登录 API
const doLogin = async () => {
const id = osdState.loginIdInput?.value?.trim() || '';
const password = osdState.loginPasswordInput?.value?.trim() || '';
if (!id) {
showLoginMessage('请输入账号 ID');
return;
}
if (!password) {
showLoginMessage('请输入密码');
return;
}
try {
setLoginButtonState(true);
showLoginMessage('');
const formData = new URLSearchParams({ id, password });
const jwtToken = await getJwtToken();
const headers = {
'accept': 'application/json, text/plain, */*',
'content-type': 'application/x-www-form-urlencoded'
};
if (jwtToken) {
headers['Authorization'] = `Bearer ${jwtToken}`;
}
const response = await platformFetch(`${API_BASE_URL}/user/pluginLogin`, {
method: 'POST',
headers,
body: formData
});
const contentType = response.headers.get('content-type') || '';
const data = contentType.includes('application/json') ? await response.json() : await response.text();
if (response.ok && data.code === 200) {
const token = data.data?.jwtToken || '';
const userData = data.data || { id };
await chrome.storage.local.set({
isLoggedIn: true,
userId: id,
jwtToken: token,
userData,
loginTime: Date.now()
});
showLoginMessage('登录成功!', false);
chrome.runtime.sendMessage({
type: 'LOGIN_SUCCESS',
data: { userId: id, token, userData }
});
setTimeout(() => {
switchView('station');
loadStations(1);
}, 500);
} else {
const errorMsg = data.msg || data.message || '登录失败,请检查账号密码';
showLoginMessage(errorMsg);
}
} catch (error) {
console.error('登录错误:', error);
showLoginMessage('网络错误,请稍后重试');
} finally {
setLoginButtonState(false);
}
};
// 登出
const doLogout = async () => {
await chrome.storage.local.remove([
'isLoggedIn', 'userId', 'jwtToken', 'userData', 'loginTime',
'currentStationId', 'currentStationData'
]);
chrome.runtime.sendMessage({ type: 'LOGOUT' });
currentStationId = null;
selectedStationId = null;
if (osdState.loginIdInput) osdState.loginIdInput.value = '';
if (osdState.loginPasswordInput) osdState.loginPasswordInput.value = '';
if (osdState.loginMessage) osdState.loginMessage.textContent = '';
switchView('login');
logStep('osd', '用户登出');
};
// 加载工位列表
const loadStations = async (page = 1) => {
if (!osdState.stationList) return;
osdState.stationLoading?.classList.remove('hidden');
osdState.stationList.innerHTML = '';
osdState.stationEmpty?.classList.add('hidden');
osdState.stationPagination?.classList.add('hidden');
try {
const jwtToken = await getJwtToken();
const headers = {
'accept': 'application/json, text/plain, */*',
'content-type': 'application/x-www-form-urlencoded'
};
if (jwtToken) {
headers['Authorization'] = `Bearer ${jwtToken}`;
}
const formData = new URLSearchParams({
page: String(page),
size: '10',
sortBy: 'id',
desc: false
});
const response = await platformFetch(`${API_BASE_URL}/station/select`, {
method: 'POST',
headers,
body: formData
});
const contentType = response.headers.get('content-type') || '';
const result = contentType.includes('application/json') ? await response.json() : await response.text();
if (response.ok && result.code === 200) {
const stations = result.data?.content || [];
const pages = result.data?.pages || 1;
stationPage = page;
stationTotalPages = pages;
if (stations.length > 0) {
displayStations(stations);
updatePagination();
} else {
showEmptyStationState();
}
} else {
console.error('加载工位失败:', result);
showEmptyStationState();
}
} catch (error) {
console.error('加载工位错误:', error);
showEmptyStationState();
} finally {
osdState.stationLoading?.classList.add('hidden');
}
};
// 显示工位列表
const displayStations = (stations) => {
if (!osdState.stationList) return;
osdState.stationList.innerHTML = '';
stations.forEach((station, index) => {
const isSelected = station.id === selectedStationId;
const isDisabled = !station.enable;
const item = document.createElement('div');
item.className = `station-item${isSelected ? ' selected' : ''}${isDisabled ? ' disabled' : ''}`;
item.dataset.stationId = station.id;
const leftDiv = document.createElement('div');
leftDiv.className = 'station-item-left';
leftDiv.innerHTML = `
${station.stationName || '未命名工位'}
IP: ${station.stationIp || 'N/A'}
`;
const rightDiv = document.createElement('div');
rightDiv.className = 'station-item-right';
rightDiv.innerHTML = `
${station.stationType || '未知'}
${isSelected ? '' : ''}
`;
item.appendChild(leftDiv);
item.appendChild(rightDiv);
if (!isDisabled) {
item.addEventListener('click', () => selectStation(station));
}
osdState.stationList.appendChild(item);
if (index < stations.length - 1) {
const divider = document.createElement('div');
divider.className = 'list-divider';
osdState.stationList.appendChild(divider);
}
});
};
// 选择工位
const selectStation = async (station) => {
selectedStationId = station.id;
await chrome.storage.local.set({
currentStationId: station.id,
currentStationData: station
});
currentStationId = station.id;
updateCurrentStationDisplay(station);
displayStations((function() {
// 重新渲染列表以显示选中状态
return [{ ...station }];
})());
chrome.runtime.sendMessage({
type: 'STATION_SELECTED',
data: { stationId: station.id, stationData: station }
});
setTimeout(() => {
switchView('monitor');
refreshDriver();
}, 300);
};
// 更新当前工位显示
const updateCurrentStationDisplay = (station) => {
if (!osdState.stationCurrentName || !osdState.stationCurrentDetail) return;
if (station) {
osdState.stationCurrentName.textContent = station.stationName || '未命名';
osdState.stationCurrentDetail.textContent = `${station.stationType || ''} · IP: ${station.stationIp || 'N/A'}`;
} else {
osdState.stationCurrentName.textContent = '未选择';
osdState.stationCurrentDetail.textContent = '';
}
};
// 更新分页
const updatePagination = () => {
if (!osdState.stationPagination) return;
if (stationTotalPages <= 1) {
osdState.stationPagination.classList.add('hidden');
return;
}
osdState.stationPagination.classList.remove('hidden');
if (osdState.stationPageInfo) {
osdState.stationPageInfo.textContent = `${stationPage} / ${stationTotalPages}`;
}
if (osdState.stationPrevBtn) {
osdState.stationPrevBtn.disabled = stationPage <= 1;
}
if (osdState.stationNextBtn) {
osdState.stationNextBtn.disabled = stationPage >= stationTotalPages;
}
};
// 显示空状态
const showEmptyStationState = () => {
if (osdState.stationList) osdState.stationList.innerHTML = '';
if (osdState.stationPagination) osdState.stationPagination.classList.add('hidden');
if (osdState.stationEmpty) osdState.stationEmpty.classList.remove('hidden');
};
// 检查登录状态并初始化视图
const initOsdView = async () => {
const loginData = await chrome.storage.local.get(['isLoggedIn', 'currentStationId', 'currentStationData']);
if (loginData.isLoggedIn) {
if (!loginData.currentStationId) {
switchView('station');
loadStations(1);
} else {
currentStationId = loginData.currentStationId;
selectedStationId = loginData.currentStationId;
updateCurrentStationDisplay(loginData.currentStationData);
switchView('monitor');
}
} else {
switchView('login');
}
};
// 绑定登录按钮事件
if (osdState.loginBtn) {
osdState.loginBtn.addEventListener('click', doLogin);
}
// 绑定回车登录
if (osdState.loginPasswordInput) {
osdState.loginPasswordInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
doLogin();
}
});
}
// 绑定登出按钮
if (osdState.logoutBtn) {
osdState.logoutBtn.addEventListener('click', doLogout);
}
// 绑定复制运单号
if (osdState.copyTrackingBtn) {
osdState.copyTrackingBtn.addEventListener('click', async () => {
const tracking = osdState.tracking?.textContent || '';
if (tracking && tracking !== '等待中') {
try {
await navigator.clipboard.writeText(tracking);
osdState.copyTrackingBtn.style.color = 'var(--success-green)';
setTimeout(() => {
osdState.copyTrackingBtn.style.color = '';
}, 1500);
} catch (err) {
console.error('复制失败:', err);
}
}
});
}
// 绑定复制 SKU
if (osdState.copySkuBtn) {
osdState.copySkuBtn.addEventListener('click', async () => {
const sku = osdState.sku?.textContent || '';
if (sku && sku !== '等待中') {
try {
await navigator.clipboard.writeText(sku);
osdState.copySkuBtn.style.color = 'var(--success-green)';
setTimeout(() => {
osdState.copySkuBtn.style.color = '';
}, 1500);
} catch (err) {
console.error('复制失败:', err);
}
}
});
}
// 绑定刷新工位列表
if (osdState.stationRefreshBtn) {
osdState.stationRefreshBtn.addEventListener('click', () => {
loadStations(stationPage);
});
}
// 绑定上一页
if (osdState.stationPrevBtn) {
osdState.stationPrevBtn.addEventListener('click', () => {
if (stationPage > 1) {
loadStations(stationPage - 1);
}
});
}
// 绑定下一页
if (osdState.stationNextBtn) {
osdState.stationNextBtn.addEventListener('click', () => {
if (stationPage < stationTotalPages) {
loadStations(stationPage + 1);
}
});
}
// 绑定空状态刷新
if (osdState.stationEmptyRefreshBtn) {
osdState.stationEmptyRefreshBtn.addEventListener('click', () => {
loadStations(1);
});
}
// 初始化视图
initOsdView();
if (chrome?.storage?.local) {
chrome.storage.local.get([STORAGE_KEYS.osdMinimized, STORAGE_KEYS.osdPosition], (res) => {
const minimized = Boolean(res?.[STORAGE_KEYS.osdMinimized]);
applyOsdMinimized(minimized);
applyMinimizeButtonState(minimized);
applyOsdPosition(res?.[STORAGE_KEYS.osdPosition]);
});
// 监听窗口大小变化,重新计算百分比位置
window.addEventListener('resize', () => {
chrome.storage.local.get([STORAGE_KEYS.osdPosition], (res) => {
applyOsdPosition(res?.[STORAGE_KEYS.osdPosition]);
});
});
}
const header = shadow.querySelector('.header');
const compactBar = shadow.querySelector('.compact-bar');
enableOsdDrag(header);
enableOsdDrag(compactBar);
};
// 根据匹配状态显示或隐藏 OSD。
const setOsdVisible = (visible) => {
if (!IS_TOP || !osdState.root) return;
osdState.root.style.display = visible ? 'block' : 'none';
logStep('osd', visible ? '显示 OSD' : '隐藏 OSD');
const tag = osdState.root.shadowRoot?.getElementById('osd-tag');
if (tag) {
tag.textContent = WMS_LABELS[activeDriverName] || 'WMS';
}
};
// 保存上传状态供弹窗/OSD 使用。
const setUploadStatus = (status, message) => {
const payload = {
status,
message,
timestamp: Date.now()
};
chrome.storage.local.set({
[STORAGE_KEYS.uploadStatus]: payload
});
if (IS_TOP) {
updateOsdStatus(payload);
} else {
window.top?.postMessage(
{
type: 'HCC_WMS_UPLOAD_STATUS',
payload
},
location.origin
);
}
logStep('upload', '状态写入', payload);
};
// 从扩展存储读取 JWT。
const getJwtToken = async () => {
logStep('auth', '读取 JWT');
const data = await chrome.storage.local.get(['jwtToken', 'isLoggedIn']);
return data.isLoggedIn ? data.jwtToken : null;
};
// 向后端发送扫描上传请求。
const uploadRecord = async (record) => {
logStep('upload', '开始上传', { id: record?.id, count: record?.subGoodsList?.length || 0 });
setUploadStatus('uploading', '上传中...');
const jwtToken = await getJwtToken();
const headers = { 'content-type': 'application/json' };
if (jwtToken) {
headers.Authorization = `Bearer ${jwtToken}`;
}
const response = await platformFetch(`${API_BASE_URL}/goods/modeOneAddWithSubGoods`, {
method: 'POST',
headers,
body: JSON.stringify(record)
});
const contentType = response.headers.get('content-type') || '';
const data = contentType.includes('application/json') ? await response.json() : await response.text();
if (!response.ok) {
console.error('[Pack Monitor] 上传失败:', response.status, data);
setUploadStatus('error', `上传失败 (${response.status})`);
logStep('upload', '上传失败', { status: response.status });
return { ok: false, status: response.status, data };
}
console.log('[Pack Monitor] 上传成功:', data);
setUploadStatus('success', '上传成功');
logStep('upload', '上传成功', { status: response.status });
return { ok: true, status: response.status, data };
};
// 构建上传数据结构。
const buildUploadRecord = ({ trackingNo, scans, startTime, endTime }) => {
if (!trackingNo) return null;
const stationId = currentStationId ? Number(currentStationId) : 0;
logStep('upload', '构建上传结构', { trackingNo, count: scans?.length || 0 });
// 构建 subGoodsList 列表。
const subGoodsList = (scans || []).map((item) => ({
scanTime: formatDateTime(item.time),
subGoodsId: item.sku,
error: false
}));
return {
id: trackingNo,
station_id: stationId,
subGoodsList,
headerList: [],
startTime: formatDateTime(startTime),
endTime: formatDateTime(endTime)
};
};
// 校验扫描批次并上传。
const finalizeAndUploadScans = async ({ trackingNo, scans, startTime, endTime, reason }) => {
logStep('upload', '准备上传批次', { trackingNo, count: scans?.length || 0, reason });
if (!trackingNo) {
console.warn('[Pack Monitor] 运单号为空,跳过上传', reason || '');
setUploadStatus('skipped', '运单号为空,跳过上传');
return;
}
if (!scans || scans.length === 0) {
console.warn('[Pack Monitor] 子货列表为空,跳过上传', reason || '');
setUploadStatus('skipped', '子货列表为空,跳过上传');
return;
}
const record = buildUploadRecord({ trackingNo, scans, startTime, endTime });
if (!record) {
setUploadStatus('skipped', '运单号为空,跳过上传');
return;
}
console.log('[Pack Monitor] 上传订单:', reason || '');
await uploadRecord(record);
};
// 保存监控数据供弹窗/OSD 使用。
const updateMonitorData = (data) => {
logStep('monitor', '更新监控数据', { trackingNo: data?.trackingNo, sku: data?.sku });
const payload = {
...data,
wmsType: activeDriverName,
wmsLabel: WMS_LABELS[activeDriverName] || '',
timestamp: Date.now()
};
chrome.storage.local.set({
[STORAGE_KEYS.packMonitor]: payload
});
if (IS_TOP) {
updateOsdData(payload);
} else {
window.top?.postMessage(
{
type: 'HCC_WMS_MONITOR_UPDATE',
payload
},
location.origin
);
}
};
// 校验登录与工位选择。
const ensureLogin = async () => {
logStep('auth', '校验登录状态');
const loginData = await chrome.storage.local.get(['isLoggedIn', 'currentStationId']);
if (!loginData.isLoggedIn || !loginData.currentStationId) {
currentStationId = null;
logStep('auth', '未登录或未选择工位');
return false;
}
currentStationId = loginData.currentStationId;
logStep('auth', '登录有效', { stationId: currentStationId });
return true;
};
// 创建 PB(朋博)页面驱动。
const createPbDriver = () => {
const TABLE_IDS = {
scanBody: 'scanData1',
scanCountBody: 'scanData'
};
const SECTION_IDS = {
skuBody: 'SkuBody'
};
const STATE = {
tracking: '',
sku: '',
initialized: false
};
const SCAN_STATE = {
pendingScans: [],
activeStartTime: null,
activeTracking: '',
expectedSkuCounts: new Map(),
scanCountBySku: new Map(),
scanTableSnapshot: new Map(),
lastScanTableSnapshot: '',
lastScanCountSnapshot: '',
skuBodyDebounceTimer: null,
lastSkuBodySnapshot: ''
};
let pageObserver = null;
let scanObserver = null;
let scanObserverTarget = null;
let scanCountObserver = null;
let scanCountObserverTarget = null;
let skuObserver = null;
let skuObserverTarget = null;
// 判断是否匹配当前驱动页面。
const match = () => window.location.hostname === 'oms.prorclub.com';
// 从 SkuBody 解析 SKU 计数。
const buildExpectedSkuMap = (body) => {
const nextMap = new Map();
if (!body) return nextMap;
body.querySelectorAll('div.label').forEach((label) => {
const text = normalizeText(label.textContent);
if (!text) return;
const matchText = text.match(/([0-9A-Za-z._-]+)\s*X\s*(\d+)/);
if (!matchText) return;
const sku = matchText[1];
const qty = parseInt(matchText[2], 10);
if (!Number.isFinite(qty)) return;
nextMap.set(sku, qty);
});
return nextMap;
};
// 刷新 SKU 计数映射。
const updateExpectedSkus = () => {
const body = document.getElementById(SECTION_IDS.skuBody);
const nextMap = buildExpectedSkuMap(body);
SCAN_STATE.expectedSkuCounts = nextMap;
logStep('pb', '刷新 SKU 计数映射', { size: nextMap.size });
return Boolean(body);
};
// 解析 scanData 表为 SKU 扫描数汇总。
const buildScanCountMap = (body) => {
const nextMap = new Map();
if (!body) return nextMap;
body.querySelectorAll('tr').forEach((row) => {
const cells = row.querySelectorAll('td');
if (cells.length < 6) return;
const sku = normalizeText(cells[2]?.textContent || '');
if (!sku) return;
const scanCountText = normalizeText(cells[5]?.textContent || '');
const scanCountValue = parseInt(scanCountText, 10);
const scanCount = Number.isFinite(scanCountValue) ? scanCountValue : 0;
const prev = nextMap.get(sku) || 0;
nextMap.set(sku, prev + scanCount);
});
return nextMap;
};
// 刷新 scanData 扫描数缓存。
const updateScanCounts = () => {
const body = document.getElementById(TABLE_IDS.scanCountBody);
const nextMap = buildScanCountMap(body);
SCAN_STATE.scanCountBySku = nextMap;
return Boolean(body);
};
// 批次变化时重置扫描状态。
const resetScanState = (reason) => {
logStep('pb', '重置扫描状态', { reason });
SCAN_STATE.pendingScans = [];
SCAN_STATE.activeStartTime = null;
SCAN_STATE.activeTracking = '';
SCAN_STATE.expectedSkuCounts.clear();
SCAN_STATE.scanCountBySku.clear();
SCAN_STATE.scanTableSnapshot.clear();
SCAN_STATE.lastScanTableSnapshot = '';
SCAN_STATE.lastScanCountSnapshot = '';
SCAN_STATE.lastSkuBodySnapshot = '';
if (SCAN_STATE.skuBodyDebounceTimer) {
window.clearTimeout(SCAN_STATE.skuBodyDebounceTimer);
SCAN_STATE.skuBodyDebounceTimer = null;
}
updateExpectedSkus();
if (reason) {
console.log(`[PB] 批次变更,已重置扫描记录 (${reason})`);
}
};
// 清空已扫描 SKU 缓存。
const clearSkuSession = (reason) => {
logStep('pb', '清空已扫 SKU', { reason });
SCAN_STATE.pendingScans = [];
SCAN_STATE.activeStartTime = null;
SCAN_STATE.expectedSkuCounts.clear();
SCAN_STATE.lastSkuBodySnapshot = '';
if (reason) {
console.log(`[PB] 已清空扫描SKU记录 (${reason})`);
}
};
// 记录来自 SkuBody 的扫描。
const recordSkuScan = (sku) => {
if (!sku) return;
logStep('pb', '记录 SKU 扫描(SkuBody)', { sku });
const time = new Date();
SCAN_STATE.pendingScans.push({ sku, time });
if (!SCAN_STATE.activeStartTime) {
SCAN_STATE.activeStartTime = time;
}
handleStateUpdate(STATE.tracking, sku);
};
// 读取表格单元格文本。
const getCellText = (cell) => normalizeText(cell?.textContent || '');
// 解析扫描表格行。
const parseScanRow = (row) => {
if (!row || row.nodeType !== 1) return null;
const cells = row.querySelectorAll('td');
if (!cells.length) return null;
const trackingNo = getCellText(cells[1]);
const sku = getCellText(cells[2]);
return {
row,
trackingNo,
sku
};
};
// 生成 scanData1 快照(tracking+sku -> {count,...})。
const buildScanTableMap = (body) => {
const nextMap = new Map();
if (!body) return nextMap;
body.querySelectorAll('tr').forEach((row) => {
const data = parseScanRow(row);
if (!data?.trackingNo || !data.sku) return;
const key = `${data.trackingNo}||${data.sku}`;
const prev = nextMap.get(key);
if (prev) {
prev.count += 1;
} else {
nextMap.set(key, {
trackingNo: data.trackingNo,
sku: data.sku,
count: 1
});
}
});
return nextMap;
};
// 设置当前识别到的运单号并同步监控数据。
const setActiveTracking = (trackingNo, reason) => {
if (!trackingNo) return;
SCAN_STATE.activeTracking = trackingNo;
if (STATE.tracking === trackingNo) return;
STATE.tracking = trackingNo;
logStep('pb', '更新运单号', { trackingNo, reason });
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: '',
sku: STATE.sku,
trackingNo
});
};
// 以当前 tracking 输出 pending 扫描并清空。
const flushPendingWithTracking = async (reason) => {
const trackingNo = SCAN_STATE.activeTracking || '';
if (!trackingNo || SCAN_STATE.pendingScans.length === 0) return;
const scans = SCAN_STATE.pendingScans.slice();
const startTime = SCAN_STATE.activeStartTime || scans[0]?.time || new Date();
const endTime = new Date();
SCAN_STATE.pendingScans = [];
SCAN_STATE.activeStartTime = null;
logStep('pb', '表格变更触发上传', { trackingNo, count: scans.length, reason });
await finalizeAndUploadScans({
trackingNo,
scans,
startTime,
endTime,
reason
});
};
// scanData1 变化时刷新快照,并尝试为 pendingScans 绑定 tracking。
const handleScanTableMutation = (body) => {
if (!body) return;
const snapshot = normalizeText(body.textContent);
if (snapshot && snapshot === SCAN_STATE.lastScanTableSnapshot) return;
SCAN_STATE.lastScanTableSnapshot = snapshot;
const prevMap = SCAN_STATE.scanTableSnapshot;
const nextMap = buildScanTableMap(body);
SCAN_STATE.scanTableSnapshot = nextMap;
const newRows = [];
nextMap.forEach((next) => {
const key = `${next.trackingNo}||${next.sku}`;
const prev = prevMap.get(key);
const prevCount = prev?.count || 0;
const delta = next.count - prevCount;
if (delta > 0) {
for (let i = 0; i < delta; i += 1) {
newRows.push({
trackingNo: next.trackingNo,
sku: next.sku
});
}
}
});
if (!newRows.length) return;
logStep('pb', '检测到新表格行', { count: newRows.length });
const trackingSet = new Set();
newRows.forEach((row) => {
if (row.trackingNo) trackingSet.add(row.trackingNo);
});
if (trackingSet.size === 1) {
setActiveTracking(Array.from(trackingSet)[0], 'scanTable_delta');
}
void flushPendingWithTracking('pb_scan_table_delta');
};
// 绑定扫描表格观察器。
const connectScanTable = () => {
const body = document.getElementById(TABLE_IDS.scanBody);
if (!body) return;
if (scanObserverTarget === body) return;
if (scanObserver) {
scanObserver.disconnect();
}
scanObserverTarget = body;
SCAN_STATE.scanTableSnapshot = buildScanTableMap(body);
SCAN_STATE.lastScanTableSnapshot = normalizeText(body.textContent);
void flushPendingWithTracking('pb_scan_table_init');
const Observer = window.MutationObserver || MutationObserver;
scanObserver = new Observer(() => handleScanTableMutation(body));
scanObserver.observe(body, { childList: true, subtree: true, characterData: true });
logStep('pb', '绑定扫描表格观察器');
};
// 监听 scanData 扫描数变化(增量视为扫描)。
const connectScanCountTable = () => {
const body = document.getElementById(TABLE_IDS.scanCountBody);
if (!body) return;
if (scanCountObserverTarget === body) return;
if (scanCountObserver) {
scanCountObserver.disconnect();
}
scanCountObserverTarget = body;
updateScanCounts();
const handleScanCountMutation = () => {
const snapshot = normalizeText(body.textContent);
if (snapshot && snapshot === SCAN_STATE.lastScanCountSnapshot) return;
SCAN_STATE.lastScanCountSnapshot = snapshot;
const prevMap = SCAN_STATE.scanCountBySku;
const nextMap = buildScanCountMap(body);
SCAN_STATE.scanCountBySku = nextMap;
nextMap.forEach((qty, sku) => {
const prevQty = prevMap.get(sku) || 0;
const delta = qty - prevQty;
if (delta > 0) {
for (let i = 0; i < delta; i += 1) {
recordSkuScan(sku);
}
}
});
};
const Observer = window.MutationObserver || MutationObserver;
scanCountObserver = new Observer(handleScanCountMutation);
scanCountObserver.observe(body, { childList: true, subtree: true, characterData: true });
logStep('pb', '绑定 scanData 观察器');
};
// 筛选与 SKU 计数相关的变动。
const mutationTouchesSkuCount = (mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'characterData') {
const parent = mutation.target?.parentElement;
if (parent && parent.id && parent.id.startsWith('sku')) {
return true;
}
}
if (mutation.type === 'childList') {
const targetEl = mutation.target?.nodeType === 1
? mutation.target
: mutation.target?.parentElement;
if (targetEl?.id && targetEl.id.startsWith('sku')) return true;
if (targetEl?.querySelector?.("span[id^='sku']")) return true;
const nodes = [...mutation.addedNodes, ...mutation.removedNodes];
for (const node of nodes) {
if (node.nodeType === 1) {
const el = node;
if (el.id && el.id.startsWith('sku')) return true;
if (el.querySelector?.("span[id^='sku']")) return true;
}
if (node.nodeType === 3) {
const parent = node.parentElement;
if (parent && parent.id && parent.id.startsWith('sku')) return true;
}
}
}
}
return false;
};
// 绑定 SkuBody 观察器。
const connectSkuBody = () => {
const body = document.getElementById(SECTION_IDS.skuBody);
if (!body) return;
if (skuObserverTarget === body) return;
if (skuObserver) {
skuObserver.disconnect();
}
skuObserverTarget = body;
updateExpectedSkus();
logStep('pb', '绑定 SkuBody 观察器');
// 处理 SKU 计数变动。
const handleSkuBodyMutation = (mutations) => {
if (!mutationTouchesSkuCount(mutations)) return;
if (SCAN_STATE.skuBodyDebounceTimer) {
window.clearTimeout(SCAN_STATE.skuBodyDebounceTimer);
}
SCAN_STATE.skuBodyDebounceTimer = window.setTimeout(() => {
SCAN_STATE.skuBodyDebounceTimer = null;
const snapshot = normalizeText(body.textContent);
if (snapshot && snapshot === SCAN_STATE.lastSkuBodySnapshot) return;
SCAN_STATE.lastSkuBodySnapshot = snapshot;
const prevMap = SCAN_STATE.expectedSkuCounts;
const nextMap = buildExpectedSkuMap(body);
SCAN_STATE.expectedSkuCounts = nextMap;
let hasIncrease = false;
nextMap.forEach((qty, sku) => {
const prevQty = prevMap.get(sku) || 0;
const delta = qty - prevQty;
if (delta > 0) {
hasIncrease = true;
for (let i = 0; i < delta; i += 1) {
recordSkuScan(sku);
}
}
});
if (!hasIncrease) {
return;
}
}, 60);
};
const Observer = window.MutationObserver || MutationObserver;
skuObserver = new Observer(handleSkuBodyMutation);
skuObserver.observe(body, { childList: true, subtree: true, characterData: true });
};
// 监听清空已扫 SKU 按钮。
const connectClearSkuButton = () => {
if (document.body?.dataset?.pbClearSkuBound === '1') return;
if (document.body) {
document.body.dataset.pbClearSkuBound = '1';
}
logStep('pb', '绑定清空 SKU 按钮');
document.addEventListener(
'click',
(event) => {
const target = event.target;
if (!(target instanceof Element)) return;
const button = target.closest('button');
if (!button) return;
const onclick = (button.getAttribute('onclick') || '').toLowerCase();
const label = normalizeText(button.textContent || '');
if (onclick.includes('btnclearsku') || onclick.includes('btnclare') || label.includes('清空已扫SKU')) {
clearSkuSession('清空已扫SKU');
}
},
true
);
};
// 同步状态并在运单变化时重置。
const handleStateUpdate = (nextTracking, nextSku) => {
const prevTracking = STATE.tracking;
let changed = false;
if (nextTracking !== STATE.tracking) {
STATE.tracking = nextTracking;
changed = true;
}
if (nextSku !== STATE.sku) {
STATE.sku = nextSku;
changed = true;
}
if (changed || !STATE.initialized) {
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: '',
sku: STATE.sku,
trackingNo: STATE.tracking
});
}
if (prevTracking !== nextTracking) {
resetScanState(prevTracking ? '运单号更新' : '');
}
STATE.initialized = true;
logStep('pb', '状态更新', { trackingNo: STATE.tracking, sku: STATE.sku });
};
// 确保朋博观察器生效。
const ensureObservers = () => {
// 避免在频繁 DOM 变动时刷屏日志。
connectScanTable();
connectScanCountTable();
connectSkuBody();
connectClearSkuButton();
};
// 启动驱动监听。
const start = () => {
logStep('pb', '启动驱动');
ensureObservers();
if (pageObserver) {
pageObserver.disconnect();
}
pageObserver = new MutationObserver(() => {
ensureObservers();
});
pageObserver.observe(document.documentElement, {
childList: true,
subtree: true
});
};
// 停止驱动监听并清理状态。
const stop = () => {
logStep('pb', '停止驱动');
if (pageObserver) {
pageObserver.disconnect();
pageObserver = null;
}
if (scanObserver) {
scanObserver.disconnect();
scanObserver = null;
}
if (scanCountObserver) {
scanCountObserver.disconnect();
scanCountObserver = null;
}
if (skuObserver) {
skuObserver.disconnect();
skuObserver = null;
}
scanObserverTarget = null;
scanCountObserverTarget = null;
skuObserverTarget = null;
SCAN_STATE.pendingScans = [];
SCAN_STATE.activeStartTime = null;
SCAN_STATE.activeTracking = '';
SCAN_STATE.expectedSkuCounts.clear();
SCAN_STATE.scanCountBySku.clear();
SCAN_STATE.scanTableSnapshot.clear();
SCAN_STATE.lastScanTableSnapshot = '';
SCAN_STATE.lastScanCountSnapshot = '';
SCAN_STATE.lastSkuBodySnapshot = '';
if (SCAN_STATE.skuBodyDebounceTimer) {
window.clearTimeout(SCAN_STATE.skuBodyDebounceTimer);
SCAN_STATE.skuBodyDebounceTimer = null;
}
STATE.sku = '';
STATE.tracking = '';
STATE.initialized = false;
};
// 返回当前驱动状态。
const getStatus = () => ({
isTargetPage: true,
url: window.location.href,
batchNo: '',
sku: STATE.sku,
trackingNo: STATE.tracking
});
// 手动结束订单
const forceEndOrder = () => {
if (!SCAN_STATE.activeTracking || SCAN_STATE.pendingScans.length === 0) {
logStep('pb', '手动结束订单: 无待提交数据');
return;
}
logStep('pb', '手动结束订单', { tracking: SCAN_STATE.activeTracking, count: SCAN_STATE.pendingScans.length });
void flushPendingWithTracking('manual_force_end');
};
return {
name: 'pb',
match,
start,
stop,
refresh: () => {
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: '',
sku: STATE.sku,
trackingNo: STATE.tracking
});
},
getStatus,
forceEndOrder
};
};
// 创建 QY(千易)页面驱动。
const createQyDriver = () => {
const TARGET_ORIGINS = ['https://cnwms.aiyacang.com', 'https://wms.aiyacang.com', 'http://wms.aiyacang.com', 'https://gwmsth.best-inc.com', 'http://gwmsth.best-inc.com'];
const state = {
active: false,
tableEl: null,
tableObserver: null,
pageObserver: null,
lastSkuSignature: null,
prevRowMap: null,
lastScanTracking: null,
lastScanSku: null,
pendingScans: []
};
// 判断是否匹配当前驱动页面。
const match = () => TARGET_ORIGINS.includes(window.location.origin);
// 规范化千易表格文本。
const setText = (value) => normalizeText(value);
// 解析表格单元格中的数值。
const toNumber = (value) => {
const cleaned = String(value || '').replace(/[^\d.-]/g, '');
const parsed = Number(cleaned);
return Number.isFinite(parsed) ? parsed : 0;
};
// 计算表格签名用于差异检测。
const hashSku = (value) => {
let hash = 5381;
const str = String(value || '');
for (let i = 0; i < str.length; i += 1) {
hash = ((hash << 5) + hash) + str.charCodeAt(i);
hash &= 0xffffffff;
}
return hash;
};
// 计算千易表格签名。
const computeSkuSignature = (rows, indexes) => {
let sum = 0;
let count = 0;
rows.forEach((row) => {
const sku = row.cells[indexes.skuIndex] || '';
const tracking = Number.isInteger(indexes.trackingIndex)
? (row.cells[indexes.trackingIndex] || '')
: '';
const wms = Number.isInteger(indexes.wmsIndex)
? (row.cells[indexes.wmsIndex] || '')
: '';
const composite = `${tracking}|${sku}|${wms}`;
sum += hashSku(composite);
count += 1;
});
return `${count}:${sum}`;
};
// 构建行唯一键用于对比。
const buildRowKey = ({ sku, tracking, wms, index }) => {
const parts = [];
if (wms) parts.push(`wms:${wms}`);
if (tracking) parts.push(`tracking:${tracking}`);
if (sku) parts.push(`sku:${sku}`);
if (parts.length === 0) parts.push(`row:${index}`);
return parts.join('|');
};
// 构建行数据映射用于对比。
const buildRowMap = (rows, indexes) => {
const map = new Map();
rows.forEach((row, index) => {
const sku = row.cells[indexes.skuIndex] || '';
const tracking = Number.isInteger(indexes.trackingIndex)
? (row.cells[indexes.trackingIndex] || '')
: '';
const wms = Number.isInteger(indexes.wmsIndex)
? (row.cells[indexes.wmsIndex] || '')
: '';
const scannedRaw = row.cells[indexes.scannedIndex] || '0';
const scanned = toNumber(scannedRaw);
const pendingRaw = Number.isInteger(indexes.pendingIndex)
? (row.cells[indexes.pendingIndex] || '0')
: '0';
const pending = toNumber(pendingRaw);
const key = buildRowKey({ sku, tracking, wms, index });
map.set(key, { sku, tracking, wms, scanned, pending });
});
return map;
};
// 表头文字映射到列索引。
const buildIndexMap = (headers) => {
if (!headers || headers.length === 0) return null;
const normalized = headers.map((text) => text.toLowerCase());
// 根据候选表头查找列索引。
const findIndex = (candidates) => {
for (const candidate of candidates) {
const needle = candidate.toLowerCase();
const exactIndex = normalized.indexOf(needle);
if (exactIndex !== -1) return exactIndex;
const includesIndex = normalized.findIndex((text) => text.includes(needle));
if (includesIndex !== -1) return includesIndex;
}
return -1;
};
const map = {};
const lookup = {
wms: ['WMS出库单号', 'wms出库单号', '出库单号', 'wms'],
erp: ['ERP单号', 'erp单号', 'erp'],
tracking: ['运单号', 'tracking', '运单'],
sku: ['SKU', 'sku'],
pending: ['待验货', '待验'],
scanned: ['扫描量', '扫描']
};
Object.keys(lookup).forEach((key) => {
const index = findIndex(lookup[key]);
if (index !== -1) {
map[key] = index;
}
});
logStep('qy', '表头索引映射', map);
return map;
};
// 读取表头文字。
const getHeaderTexts = (table) => {
const headerTable = getHeaderTable(table);
const headers = Array.from(headerTable.querySelectorAll('thead th'));
if (headers.length === 0) return [];
return headers.map((th) => setText(th.innerText || th.textContent || ''));
};
// 定位千易表头表格。
const getHeaderTable = (table) => {
if (table.querySelectorAll('thead th').length > 0) {
return table;
}
const container = table.closest('div.wmstool-table-content') ||
document.querySelector('div.wmstool-table-content') ||
table.parentElement ||
document;
const candidates = Array.from(container.querySelectorAll('table.wmstool-table-fixed'));
const withHeaders = candidates.find((candidate) => candidate.querySelectorAll('thead th').length > 0);
return withHeaders || table;
};
// 定位千易活动表格。
const getActiveTable = () => {
const content = document.querySelector('div.wmstool-table-content');
if (content) {
const scopedTables = Array.from(content.querySelectorAll('table.wmstool-table-fixed'));
if (scopedTables.length >= 2) {
return scopedTables[1];
}
}
const tables = Array.from(document.querySelectorAll('table.wmstool-table-fixed'));
if (tables.length === 0) return null;
const withRows = tables.find((table) => table.querySelectorAll('tbody tr').length > 0);
if (withRows) return withRows;
const withBody = tables.find((table) => table.querySelector('tbody'));
return withBody || tables[tables.length - 1];
};
// 生成朋博表格快照。
const getTableSnapshot = () => {
const table = getActiveTable();
if (!table) {
return { rows: [], indexMap: null };
}
const headers = getHeaderTexts(table);
const indexMap = buildIndexMap(headers);
const rows = Array.from(table.querySelectorAll('tbody tr'));
const mappedRows = rows.map((row) => {
const cells = Array.from(row.querySelectorAll('td')).map((td) => setText(td.innerText));
return { cells };
});
logStep('qy', '表格快照', { rows: mappedRows.length });
return { rows: mappedRows, indexMap };
};
// 提交待上传扫描并触发上传。
const flushPendingScans = async (reason) => {
if (!state.lastScanTracking || state.pendingScans.length === 0) {
return;
}
logStep('qy', '提交扫描批次', { tracking: state.lastScanTracking, count: state.pendingScans.length, reason });
const scans = state.pendingScans.slice();
const startTime = new Date(scans[0].time);
const endTime = new Date();
await finalizeAndUploadScans({
trackingNo: state.lastScanTracking,
scans,
startTime,
endTime,
reason
});
state.pendingScans = [];
};
// 记录千易表格增量扫描。
const recordScan = (row) => {
const tracking = row.tracking || '';
if (state.lastScanTracking && tracking && tracking !== state.lastScanTracking) {
void flushPendingScans('qy_tracking_changed');
}
if (tracking && tracking !== state.lastScanTracking) {
state.lastScanTracking = tracking;
}
const entry = {
sku: row.sku,
time: new Date()
};
state.pendingScans.push(entry);
state.lastScanSku = entry.sku;
logStep('qy', '记录表格扫描', { tracking, sku: entry.sku });
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: '',
sku: state.lastScanSku || '',
trackingNo: state.lastScanTracking || ''
});
};
// 处理表格差异并记录扫描。
const processTableSnapshot = (snapshot) => {
if (!snapshot || !snapshot.indexMap) return;
const skuIndex = snapshot.indexMap.sku;
const scannedIndex = snapshot.indexMap.scanned;
const pendingIndex = snapshot.indexMap.pending;
const trackingIndex = snapshot.indexMap.tracking;
const wmsIndex = snapshot.indexMap.wms;
if (!Number.isInteger(skuIndex) || !Number.isInteger(scannedIndex)) {
return;
}
const signature = computeSkuSignature(snapshot.rows, {
skuIndex,
trackingIndex,
wmsIndex
});
if (state.lastSkuSignature === null) {
state.lastSkuSignature = signature;
state.prevRowMap = buildRowMap(snapshot.rows, {
skuIndex,
scannedIndex,
trackingIndex,
wmsIndex,
pendingIndex
});
return;
}
if (signature !== state.lastSkuSignature) {
void flushPendingScans('qy_table_changed');
state.lastSkuSignature = signature;
state.prevRowMap = buildRowMap(snapshot.rows, {
skuIndex,
scannedIndex,
trackingIndex,
wmsIndex,
pendingIndex
});
state.pendingScans = [];
state.lastScanTracking = null;
logStep('qy', '表格变化重置扫描');
return;
}
const prevMap = state.prevRowMap || new Map();
const currentMap = buildRowMap(snapshot.rows, {
skuIndex,
scannedIndex,
pendingIndex,
trackingIndex,
wmsIndex
});
let pendingChanged = false;
currentMap.forEach((row, key) => {
const prev = prevMap.get(key);
const prevScanned = prev ? prev.scanned : 0;
if (row.scanned > prevScanned) {
recordScan({
sku: row.sku,
tracking: row.tracking
});
}
if (Number.isInteger(pendingIndex)) {
if (prev && row.pending < prev.pending) {
pendingChanged = true;
}
}
});
if (pendingChanged) {
void flushPendingScans('qy_pending_changed');
state.pendingScans = [];
state.lastScanTracking = null;
logStep('qy', '待验货变化清空扫描');
}
state.prevRowMap = currentMap;
state.lastSkuSignature = signature;
};
// 刷新千易表格快照。
const updateSnapshot = () => {
logStep('qy', '刷新表格快照');
const snapshot = getTableSnapshot();
processTableSnapshot(snapshot);
};
// 监听千易表格变动。
const attachTableObserver = (table) => {
if (!table || table === state.tableEl) return;
state.tableEl = table;
if (state.tableObserver) {
state.tableObserver.disconnect();
}
state.tableObserver = new MutationObserver(() => {
updateSnapshot();
});
const body = table.querySelector('tbody') || table;
state.tableObserver.observe(body, { childList: true, subtree: true, characterData: true });
logStep('qy', '绑定表格观察器');
updateSnapshot();
};
// 定位千易表格元素。
const scanForElements = () => {
if (!state.active) return;
const table = getActiveTable();
if (table) {
attachTableObserver(table);
} else if (state.tableObserver) {
state.tableObserver.disconnect();
state.tableObserver = null;
state.tableEl = null;
}
logStep('qy', '扫描页面元素');
};
// 监听页面变动以重连。
const ensurePageObserver = () => {
if (state.pageObserver || !document.body) return;
state.pageObserver = new MutationObserver(() => {
if (!state.active) return;
scanForElements();
});
state.pageObserver.observe(document.body, { childList: true, subtree: true });
logStep('qy', '绑定页面观察器');
};
// 启动驱动监听。
const start = () => {
if (state.active) return;
logStep('qy', '启动驱动');
state.active = true;
ensurePageObserver();
scanForElements();
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: '',
sku: state.lastScanSku || '',
trackingNo: state.lastScanTracking || ''
});
};
// 停止驱动监听并清理状态。
const stop = () => {
logStep('qy', '停止驱动');
state.active = false;
state.tableEl = null;
if (state.tableObserver) {
state.tableObserver.disconnect();
state.tableObserver = null;
}
if (state.pageObserver) {
state.pageObserver.disconnect();
state.pageObserver = null;
}
state.pendingScans = [];
state.lastScanTracking = null;
state.lastScanSku = null;
state.prevRowMap = null;
state.lastSkuSignature = null;
};
// 返回当前驱动状态。
const getStatus = () => ({
isTargetPage: true,
url: window.location.href,
batchNo: '',
sku: state.lastScanSku || '',
trackingNo: state.lastScanTracking || ''
});
// 手动结束订单
const forceEndOrder = () => {
if (!state.lastScanTracking || state.pendingScans.length === 0) {
logStep('qy', '手动结束订单: 无待提交数据');
return;
}
logStep('qy', '手动结束订单', { tracking: state.lastScanTracking, count: state.pendingScans.length });
void flushPendingScans('manual_force_end');
};
return {
name: 'qy',
match,
start,
stop,
refresh: () => {
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: '',
sku: state.lastScanSku || '',
trackingNo: state.lastScanTracking || ''
});
},
getStatus,
forceEndOrder
};
};
// 创建 JF(极风)页面驱动。
const createJfDriver = () => {
const state = {
active: false,
tableEl: null,
tableObserver: null,
pageObserver: null,
prevRowMap: null,
activeTrackingNo: null,
pendingScans: [],
activeStartTime: null
};
// 判断是否匹配当前驱动页面。
const match = () => {
return window.location.hostname.endsWith('.jfwms.com') &&
window.location.pathname.includes('/web/out/package/inspection');
};
// 解析表格行数据(支持一行多SKU)。
const parseTableRow = (row) => {
if (!row || row.nodeType !== 1) return [];
// 第二列:运单号
const trackingCol = row.querySelector('td:nth-child(2)');
if (!trackingCol) return [];
const firstP = trackingCol.querySelector('p');
const trackingNo = normalizeText(firstP?.textContent || '');
if (!trackingNo) return [];
// 第三列:SKU和扫描数
const skuCol = row.querySelector('td:nth-child(3)');
if (!skuCol) return [];
const result = [];
// 获取所有 sku_flex 和 flex_135 对
const skuFlexes = Array.from(skuCol.querySelectorAll('.flex.sku_flex'));
const flex135s = Array.from(skuCol.querySelectorAll('.flex_135'));
// 遍历配对
skuFlexes.forEach((skuFlex, index) => {
const skuEl = skuFlex.querySelector('.t_name');
const sku = normalizeText(skuEl?.textContent || '');
if (!sku) return;
const flex135 = flex135s[index];
const scannedSpan = flex135?.querySelector('span');
const scannedText = normalizeText(scannedSpan?.textContent || '0');
const scannedCount = parseInt(scannedText, 10);
result.push({
trackingNo,
sku,
scannedCount: Number.isFinite(scannedCount) ? scannedCount : 0
});
});
return result;
};
// 生成行唯一键。
const getRowKey = (rowData) => {
if (!rowData) return '';
return `${rowData.trackingNo}|${rowData.sku}`;
};
// 构建行数据映射。
const buildRowMap = (tableEl) => {
const map = new Map();
if (!tableEl) return map;
const rows = tableEl.querySelectorAll('tr.el-table__row');
rows.forEach((row) => {
const items = parseTableRow(row);
items.forEach((data) => {
if (data) {
const key = getRowKey(data);
map.set(key, data);
}
});
});
return map;
};
// 提交待上传扫描并触发上传。
const flushPendingScans = async (trackingNo) => {
if (!trackingNo || state.pendingScans.length === 0) {
return;
}
logStep('jf', '提交扫描批次', { tracking: trackingNo, count: state.pendingScans.length });
const scans = state.pendingScans.slice();
const startTime = state.activeStartTime || new Date(scans[0].time);
const endTime = new Date();
state.pendingScans = [];
state.activeStartTime = null;
await finalizeAndUploadScans({
trackingNo,
scans,
startTime,
endTime,
reason: 'jf_scan_complete'
});
};
// 记录扫描。
const recordScan = ({ trackingNo, sku }) => {
// 运单号切换时提交上一个批次
if (state.activeTrackingNo && trackingNo && trackingNo !== state.activeTrackingNo) {
void flushPendingScans(state.activeTrackingNo);
}
if (trackingNo && trackingNo !== state.activeTrackingNo) {
state.activeTrackingNo = trackingNo;
}
const entry = {
sku,
time: new Date()
};
state.pendingScans.push(entry);
if (!state.activeStartTime) {
state.activeStartTime = entry.time;
}
logStep('jf', '记录扫描', { trackingNo, sku });
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: '',
sku: entry.sku,
trackingNo: state.activeTrackingNo || ''
});
};
// 处理表格更新。
const processTableUpdate = () => {
if (!state.tableEl) return;
const currentMap = buildRowMap(state.tableEl);
if (state.prevRowMap === null) {
state.prevRowMap = currentMap;
return;
}
const prevMap = state.prevRowMap;
// 检测 scannedCount +1 的行
currentMap.forEach((current, key) => {
const prev = prevMap.get(key);
const prevScanned = prev ? prev.scannedCount : 0;
if (current.scannedCount > prevScanned) {
// 每次 +1 记录一次扫描
const delta = current.scannedCount - prevScanned;
for (let i = 0; i < delta; i++) {
recordScan({
trackingNo: current.trackingNo,
sku: current.sku
});
}
}
});
state.prevRowMap = currentMap;
};
// 定位表格元素。
const getTableElement = () => {
return document.querySelector('.el-table .el-table__body');
};
// 绑定表格观察器。
const attachTableObserver = (table) => {
if (!table || table === state.tableEl) return;
state.tableEl = table;
if (state.tableObserver) {
state.tableObserver.disconnect();
}
state.tableObserver = new MutationObserver(() => {
processTableUpdate();
});
state.tableObserver.observe(table, {
childList: true,
subtree: true,
characterData: true
});
logStep('jf', '绑定表格观察器');
state.prevRowMap = buildRowMap(table);
};
// 扫描页面元素。
const scanForElements = () => {
if (!state.active) return;
const table = getTableElement();
if (table) {
attachTableObserver(table);
} else if (state.tableObserver) {
state.tableObserver.disconnect();
state.tableObserver = null;
state.tableEl = null;
}
logStep('jf', '扫描页面元素');
};
// 监听页面变动以重连。
const ensurePageObserver = () => {
if (state.pageObserver || !document.body) return;
state.pageObserver = new MutationObserver(() => {
if (!state.active) return;
scanForElements();
});
state.pageObserver.observe(document.body, { childList: true, subtree: true });
logStep('jf', '绑定页面观察器');
};
// 启动驱动监听。
const start = () => {
if (state.active) return;
logStep('jf', '启动驱动');
state.active = true;
ensurePageObserver();
scanForElements();
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: '',
sku: '',
trackingNo: ''
});
};
// 停止驱动监听并清理状态。
const stop = () => {
logStep('jf', '停止驱动');
state.active = false;
state.tableEl = null;
state.prevRowMap = null;
state.activeTrackingNo = null;
state.activeStartTime = null;
if (state.tableObserver) {
state.tableObserver.disconnect();
state.tableObserver = null;
}
if (state.pageObserver) {
state.pageObserver.disconnect();
state.pageObserver = null;
}
// 停止前尝试提交剩余扫描
if (state.activeTrackingNo && state.pendingScans.length > 0) {
void flushPendingScans(state.activeTrackingNo);
}
state.pendingScans = [];
};
// 返回当前驱动状态。
const getStatus = () => ({
isTargetPage: true,
url: window.location.href,
batchNo: '',
sku: state.pendingScans.length > 0 ? state.pendingScans[state.pendingScans.length - 1].sku : '',
trackingNo: state.activeTrackingNo || ''
});
// 手动结束订单
const forceEndOrder = () => {
if (!state.activeTrackingNo || state.pendingScans.length === 0) {
logStep('jf', '手动结束订单: 无待提交数据');
return;
}
logStep('jf', '手动结束订单', { tracking: state.activeTrackingNo, count: state.pendingScans.length });
void flushPendingScans(state.activeTrackingNo);
};
return {
name: 'jf',
match,
start,
stop,
refresh: () => {
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: '',
sku: state.pendingScans.length > 0 ? state.pendingScans[state.pendingScans.length - 1].sku : '',
trackingNo: state.activeTrackingNo || ''
});
},
getStatus,
forceEndOrder
};
};
const DRIVERS = [createPbDriver(), createQyDriver(), createJfDriver()];
// 停止当前激活的驱动。
const stopActiveDriver = () => {
if (activeDriver) {
logStep('core', '停止当前驱动', { name: activeDriver.name });
activeDriver.stop();
}
activeDriver = null;
activeDriverName = '';
};
// 根据 URL/登录状态重选驱动。
const refreshDriver = async () => {
logStep('core', '刷新驱动');
const token = ++refreshToken;
const loginReady = await ensureLogin();
if (token !== refreshToken) return;
if (!loginReady) {
stopActiveDriver();
logStep('core', '未登录,停止驱动');
updateMonitorData({
isTargetPage: false,
needLogin: true,
url: window.location.href,
batchNo: '',
sku: '',
trackingNo: ''
});
// 未登录时切换到登录视图,而不是隐藏 OSD
switchView('login');
return;
}
const nextDriver = DRIVERS.find((driver) => driver.match());
if (!nextDriver) {
stopActiveDriver();
logStep('core', '未匹配到驱动');
updateMonitorData({
isTargetPage: false,
needLogin: false,
url: window.location.href,
batchNo: '',
sku: '',
trackingNo: ''
});
// 未匹配到驱动时最小化 OSD,而不是隐藏
applyOsdMinimized(true);
return;
}
if (!activeDriver || activeDriver.name !== nextDriver.name) {
stopActiveDriver();
activeDriver = nextDriver;
activeDriverName = nextDriver.name;
logStep('core', '切换驱动', { name: activeDriverName });
activeDriver.start();
}
setOsdVisible(true);
activeDriver.refresh?.();
logStep('core', '驱动刷新完成', { name: activeDriverName });
};
// 监听 SPA 路由变化。
const watchUrlChange = () => {
logStep('core', '启动 URL 监听');
let lastUrl = window.location.href;
setInterval(() => {
const currentUrl = window.location.href;
if (currentUrl !== lastUrl) {
lastUrl = currentUrl;
logStep('core', 'URL 变化', { url: currentUrl });
refreshDriver();
}
}, 500);
window.addEventListener('popstate', refreshDriver);
window.addEventListener('hashchange', refreshDriver);
};
// 初始化 OSD 与驱动路由。
const init = () => {
logStep('core', '初始化');
initOsd();
refreshDriver();
watchUrlChange();
};
if (IS_TOP) {
window.addEventListener('message', (event) => {
if (event.origin !== location.origin) return;
const payload = event.data?.payload;
if (event.data?.type === 'HCC_WMS_MONITOR_UPDATE') {
updateOsdData(payload);
return;
}
if (event.data?.type === 'HCC_WMS_UPLOAD_STATUS') {
updateOsdStatus(payload);
}
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'getStatus') {
if (activeDriver) {
sendResponse(activeDriver.getStatus());
} else {
sendResponse({
isTargetPage: false,
url: window.location.href,
batchNo: '',
sku: '',
trackingNo: ''
});
}
} else if (request.action === 'forceEndOrder') {
// 处理手动结束订单请求
if (activeDriver && typeof activeDriver.forceEndOrder === 'function') {
logStep('core', '收到手动结束订单请求', { driver: activeDriver.name });
activeDriver.forceEndOrder();
sendResponse({ success: true, message: '已触发结束订单' });
} else {
logStep('core', '手动结束订单失败: 当前驱动不支持', { driver: activeDriver?.name });
sendResponse({ success: false, message: '当前页面不支持结束订单' });
}
}
return true;
});
const registerTampermonkeyMenu = () => {
if (!IS_TOP || typeof GM_registerMenuCommand !== 'function') return;
GM_registerMenuCommand('HCC: 强制结束当前订单', async () => {
const result = await chrome.runtime.sendMessage({ action: 'forceEndOrder' });
if (result?.message) {
alert(result.message);
}
});
GM_registerMenuCommand('HCC: 查看当前状态', async () => {
const status = await chrome.runtime.sendMessage({ action: 'getStatus' });
alert(JSON.stringify(status || {}, null, 2));
});
GM_registerMenuCommand('HCC: 清空登录并刷新', async () => {
await chrome.storage.local.remove([
'isLoggedIn', 'userId', 'jwtToken', 'userData', 'loginTime',
'currentStationId', 'currentStationData'
]);
location.reload();
});
GM_registerMenuCommand('HCC: 清空工位并刷新', async () => {
await chrome.storage.local.remove(['currentStationId', 'currentStationData']);
location.reload();
});
};
registerTampermonkeyMenu();
})();