// ==UserScript==
// @name 货查查助手-千易 WMS
// @namespace https://hcc321.com/
// @version 1.0.4
// @description 千易 WMS 验货页面商品扫描抓单上传(API 拦截方案),保留登录、工位、监控与上传能力
// @author HCCvision
// @match https://*.aiyacang.com/*
// @match http://*.aiyacang.com/*
// @match https://gwmsth.best-inc.com/*
// @match http://gwmsth.best-inc.com/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @connect hcc321.com
// @inject-into content
// @run-at document-start
// ==/UserScript==
/**
* Pack Monitor Script
* 千易 WMS 验货页面商品扫描抓单上传 - API 拦截方案
* 通过拦截 fetch/XHR 获取 /service/ob/pack/prepare 与 /service/ob/pack/confirm 接口数据
*/
(() => {
if (window.__HCC_QY_WMS_MONITOR__) {
return;
}
window.__HCC_QY_WMS_MONITOR__ = true;
const IS_TOP = window.top === window;
const API_BASE_URL = 'https://hcc321.com';
const readValue = 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;
};
const writeValue = 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);
}
};
const deleteValue = async (key) => {
if (typeof GM_deleteValue === 'function') {
GM_deleteValue(key);
return;
}
if (typeof GM !== 'undefined' && typeof GM.deleteValue === 'function') {
await GM.deleteValue(key);
}
};
const Storage = {
async get(keys, callback) {
const result = {};
if (Array.isArray(keys)) {
for (const key of keys) {
result[key] = await readValue(key);
}
} else if (typeof keys === 'string') {
result[keys] = await readValue(keys);
} else if (keys && typeof keys === 'object') {
for (const [key, defaultValue] of Object.entries(keys)) {
result[key] = await readValue(key, defaultValue);
}
}
if (typeof callback === 'function') {
callback(result);
}
return result;
},
async set(items) {
for (const [key, value] of Object.entries(items || {})) {
await writeValue(key, value);
}
},
async remove(keys) {
const keyList = Array.isArray(keys) ? keys : [keys];
for (const key of keyList) {
await deleteValue(key);
}
}
};
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 request = 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 postJson = (url, body, headers = {}) => request(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
...headers
},
body: JSON.stringify(body)
});
const menuCommandIds = [];
const unregisterMenuCommands = () => {
if (typeof GM_unregisterMenuCommand !== 'function') return;
while (menuCommandIds.length > 0) {
const menuId = menuCommandIds.pop();
if (menuId != null) {
GM_unregisterMenuCommand(menuId);
}
}
};
const registerMenuCommand = (label, handler) => {
if (typeof GM_registerMenuCommand !== 'function') return null;
const menuId = GM_registerMenuCommand(label, handler);
if (menuId != null) {
menuCommandIds.push(menuId);
}
return menuId;
};
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 = {
qy: '千易 WMS'
};
let currentStationId = null;
let activeDriver = null;
let activeDriverName = 'qy';
let refreshToken = 0;
const osdState = {
root: null,
panel: null,
fab: null,
sku: null,
status: null,
skuCompact: null,
statusCompact: 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,
copySkuBtn: null,
updateTime: null
};
const pad = (n) => String(n).padStart(2, '0');
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 isAiyacangOrigin = (origin) => {
try {
const { hostname } = new URL(origin);
return hostname === 'aiyacang.com' || hostname.endsWith('.aiyacang.com');
} catch (error) {
return false;
}
};
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}`);
};
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 });
};
const updateOsdData = (data) => {
if (!IS_TOP || !osdState.sku) return;
const sku = data?.sku || '';
logStep('osd', '数据更新', { 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.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');
}
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} 更新`;
}
};
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 ? '面板紧凑' : '面板展开');
};
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') {
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 });
};
const saveOsdPosition = (pos) => {
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;
}
Storage.set({
[STORAGE_KEYS.osdPosition]: positionToSave
});
logStep('osd', '保存面板位置(百分比)', positionToSave);
};
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);
});
};
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 = `
`;
const mountTarget = document.body || document.documentElement;
mountTarget.appendChild(root);
logStep('osd', 'OSD 挂载完成');
osdState.root = root;
osdState.panel = shadow.getElementById('osd-panel');
osdState.fab = shadow.getElementById('osd-expand');
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.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.copySkuBtn = shadow.getElementById('osd-copy-sku');
osdState.updateTime = shadow.getElementById('osd-update-time');
const minimizeBtn = shadow.querySelector('.header');
// We need the actual button, not the header
const minimizeButton = shadow.getElementById('osd-minimize');
const expandCompactBtn = shadow.getElementById('osd-expand-compact');
const applyMinimizeButtonState = (minimized) => {
minimizeButton.textContent = minimized ? '+' : '—';
minimizeButton.title = minimized ? '展开' : '最小化';
};
minimizeButton.addEventListener('click', () => {
const isMinimized = osdState.panel?.classList.contains('compact');
const nextMinimized = !isMinimized;
applyOsdMinimized(nextMinimized);
applyMinimizeButtonState(nextMinimized);
Storage.set({ [STORAGE_KEYS.osdMinimized]: nextMinimized });
});
expandCompactBtn.addEventListener('click', () => {
applyOsdMinimized(false);
applyMinimizeButtonState(false);
Storage.set({ [STORAGE_KEYS.osdMinimized]: false });
});
osdState.fab.addEventListener('click', () => {
applyOsdMinimized(false);
applyMinimizeButtonState(false);
Storage.set({ [STORAGE_KEYS.osdMinimized]: false });
});
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);
}
});
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 ? '登录中...' : '登 录';
};
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 request(`${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 Storage.set({
isLoggedIn: true,
userId: id,
jwtToken: token,
userData,
loginTime: Date.now()
});
showLoginMessage('登录成功!', false);
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 Storage.remove([
'isLoggedIn', 'userId', 'jwtToken', 'userData', 'loginTime',
'currentStationId', 'currentStationData'
]);
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 request(`${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 Storage.set({
currentStationId: station.id,
currentStationData: station
});
currentStationId = station.id;
updateCurrentStationDisplay(station);
displayStations((function() {
return [{ ...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 Storage.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.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();
Storage.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', () => {
Storage.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);
};
const setOsdVisible = (visible) => {
if (!IS_TOP || !osdState.root) return;
osdState.root.style.display = visible ? 'block' : 'none';
logStep('osd', visible ? '显示 OSD' : '隐藏 OSD');
};
const setUploadStatus = (status, message) => {
const payload = {
status,
message,
timestamp: Date.now()
};
Storage.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);
};
const getJwtToken = async () => {
logStep('auth', '读取 JWT');
const data = await Storage.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 postJson(`${API_BASE_URL}/goods/modeOneAddWithSubGoodsWithTrustTime`, record, headers);
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;
const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
logStep('upload', '构建上传结构', { trackingNo, count: scans?.length || 0, zone });
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),
zone
};
};
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);
};
const updateMonitorData = (data) => {
logStep('monitor', '更新监控数据', { trackingNo: data?.trackingNo, sku: data?.sku });
const payload = {
...data,
wmsType: activeDriverName,
wmsLabel: WMS_LABELS[activeDriverName] || '',
timestamp: Date.now()
};
Storage.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 Storage.get(['isLoggedIn', 'currentStationId']);
if (!loginData.isLoggedIn || !loginData.currentStationId) {
currentStationId = null;
logStep('auth', '未登录或未选择工位');
return false;
}
currentStationId = loginData.currentStationId;
logStep('auth', '登录有效', { stationId: currentStationId });
return true;
};
// ============================================================
// API 拦截桥接 (类似 flash.user.js 方案)
// ============================================================
const QY_API_BRIDGE_MESSAGE = 'HCC_QY_API_BRIDGE';
const QY_API_BRIDGE_LISTENER_FLAG = '__HCC_QY_API_BRIDGE_LISTENER_INSTALLED__';
const QY_API_PATCH_FLAG = '__HCC_QY_API_INTERCEPTOR_INSTALLED__';
const QY_API_BRIDGE_SCRIPT_ID = 'hcc-qy-api-bridge';
const QY_API_PATHS = ['/service/ob/pack/prepare', '/service/ob/pack/confirm', '/service/ob/pack/confirmAndApply'];
let qyApiResponseHandler = null;
const installBridgeListener = () => {
if (window[QY_API_BRIDGE_LISTENER_FLAG]) return;
window[QY_API_BRIDGE_LISTENER_FLAG] = true;
window.addEventListener('message', (event) => {
if (event.origin !== location.origin) return;
if (event.data?.type !== QY_API_BRIDGE_MESSAGE) return;
if (typeof qyApiResponseHandler === 'function') {
qyApiResponseHandler(event.data.payload || {});
}
});
};
const buildBridgeSource = () => {
const messageType = JSON.stringify(QY_API_BRIDGE_MESSAGE);
const patchFlag = JSON.stringify(QY_API_PATCH_FLAG);
const apiPaths = JSON.stringify(QY_API_PATHS);
return `
(function() {
if (window[${patchFlag}]) return;
window[${patchFlag}] = true;
var API_PATHS = ${apiPaths};
function shouldObserveUrl(url) {
try {
var u = new URL(String(url || ''), window.location.origin);
return API_PATHS.indexOf(u.pathname) !== -1;
} catch (e) {
return false;
}
}
function serializeBody(body) {
if (typeof body === 'string') return body;
if (body instanceof URLSearchParams) return body.toString();
if (body instanceof FormData) {
try { return JSON.stringify(Array.from(body.entries())); } catch (e) { return ''; }
}
if (body == null) return '';
try { return String(body); } catch (e) { return ''; }
}
function publish(detail) {
window.postMessage({ type: ${messageType}, payload: detail }, window.location.origin);
}
// Override fetch
var origFetch = window.fetch;
if (typeof origFetch === 'function') {
window.fetch = async function(...args) {
var response = await origFetch.apply(window, args);
try {
var resource = args[0];
var init = args[1];
var requestUrl = typeof resource === 'string' ? resource : (resource ? resource.url : '');
if (shouldObserveUrl(requestUrl)) {
var clone = response.clone();
var text = await clone.text();
publish({
url: String(requestUrl || ''),
requestBody: serializeBody(init ? init.body : undefined),
responseText: text,
transport: 'fetch'
});
}
} catch (error) {
console.error('[QY] fetch bridge failed', error);
}
return response;
};
}
// Override XHR
var origOpen = XMLHttpRequest.prototype.open;
var origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url) {
this.__hccQyUrl = url;
var rest = Array.prototype.slice.call(arguments, 2);
return origOpen.apply(this, [method, url].concat(rest));
};
XMLHttpRequest.prototype.send = function(body) {
this.__hccQyBody = body;
if (!this.__hccQyListenerBound) {
this.__hccQyListenerBound = true;
this.addEventListener('loadend', () => {
try {
if (!shouldObserveUrl(this.__hccQyUrl)) return;
publish({
url: String(this.__hccQyUrl || ''),
requestBody: serializeBody(this.__hccQyBody),
responseText: typeof this.responseText === 'string' ? this.responseText : '',
transport: 'xhr'
});
} catch (error) {
console.error('[QY] xhr bridge failed', error);
}
});
}
return origSend.apply(this, arguments);
};
})();
`;
};
const installApiInterceptor = () => {
if (window[QY_API_PATCH_FLAG]) return;
window[QY_API_PATCH_FLAG] = true;
installBridgeListener();
const existingScript = document.getElementById(QY_API_BRIDGE_SCRIPT_ID);
if (existingScript) return;
const script = document.createElement('script');
script.id = QY_API_BRIDGE_SCRIPT_ID;
script.textContent = buildBridgeSource();
const parent = document.head || document.documentElement || document.body;
parent?.appendChild(script);
script.remove();
logStep('qy', 'API 拦截器已注入');
};
// ============================================================
// QY(千易)API 驱动
// ============================================================
const createQyDriver = () => {
const TARGET_ORIGINS = ['https://gwmsth.best-inc.com', 'http://gwmsth.best-inc.com'];
const state = {
active: false,
// Current order context
currentOrderCode: null, // WMS order code
// Scan tracking for batch management
lastScanSku: null,
lastScanId: null,
lastScanAt: 0,
pendingScans: [],
scannedSkuList: [],
currentOrderStartTime: null,
lastOrderEndTime: null,
preparedOrderCode: null,
skuAliasMap: new Map(),
orderTrackingMap: new Map(),
skuTrackingMap: new Map(),
lastConfirmSignature: null
};
const match = () => isAiyacangOrigin(window.location.origin) || TARGET_ORIGINS.includes(window.location.origin);
const setText = (value) => normalizeText(value);
const buildSkuAliasMap = (skuList) => {
const map = new Map();
for (const sku of skuList || []) {
const aliases = [sku.skuCode, ...(sku.barcodes || [])]
.map((value) => normalizeText(value))
.filter(Boolean);
for (const alias of aliases) {
map.set(alias, sku);
}
}
return map;
};
const recordSkuScan = (scanCode, source = 'input') => {
const normalizedCode = normalizeText(scanCode);
if (!normalizedCode) return;
const sku = state.skuAliasMap.get(normalizedCode);
const skuCode = normalizedCode;
const scanTime = new Date();
if (!state.currentOrderStartTime) {
state.currentOrderStartTime = state.lastOrderEndTime || scanTime;
}
const entry = {
sku: skuCode,
scanId: normalizedCode,
time: scanTime,
orderedQty: sku?.orderedQty || 0,
packedQty: sku?.packedQty || 0
};
state.pendingScans.push(entry);
state.scannedSkuList.push({
skuCode: skuCode,
scanCode: normalizedCode,
time: scanTime,
source
});
state.lastScanSku = skuCode;
state.lastScanId = normalizedCode;
state.lastScanAt = scanTime.getTime();
logStep('qy', '记录 SKU 输入', {
sku: skuCode,
scanId: normalizedCode,
count: state.pendingScans.length,
skuListCount: state.scannedSkuList.length,
source
});
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: state.currentOrderCode || '',
sku: state.lastScanSku || '',
trackingNo: ''
});
};
const handleSkuInputKeydown = (event) => {
if (!state.active) return;
if (event.key !== 'Enter') return;
const target = event.target;
if (!target || target.id !== 'pack_sku_input') return;
const value = target.value;
window.setTimeout(() => recordSkuScan(value, 'input_enter'), 0);
};
const parseConfirmResponse = (responseText) => {
let data;
try {
data = JSON.parse(responseText);
} catch (e) {
return null;
}
const row = data?.row || {};
// Compatible with both direct nesting: row.orderHeaderVo
// and wrapped nesting: row.packReturnVo.orderHeaderVo (confirmAndApply endpoint)
const order = row.orderHeaderVo || row.packReturnVo?.orderHeaderVo || {};
const trackingNo = normalizeText(order.trackingNo || order.refCode);
if (!trackingNo) return null;
return {
orderCode: normalizeText(order.orderCode || state.currentOrderCode),
trackingNo,
packedTime: order.packedTime ? new Date(order.packedTime) : null,
signature: JSON.stringify({
orderHeaderId: order.id,
orderCode: order.orderCode,
trackingNo,
packedTime: order.packedTime
})
};
};
const responseIsSuccess = (responseText) => {
try {
const data = JSON.parse(responseText);
return data?.success !== false;
} catch (e) {
return true;
}
};
/**
* Parse the /service/ob/pack/prepare API response.
* Expected structure:
* row.orderHeaderVoList[0].orderCode - WMS order number
* row.orderHeaderVoList[0].trackingNo - tracking/shipping number
* row.orderDetailVoList[].skuCode - SKU identifier
* row.orderDetailVoList[].orderedQty - ordered quantity
* row.orderDetailVoList[].packedQty - packed quantity
* row.orderDetailVoList[].barCodeList[] - barcode alternatives
* row.barCodeAndPackingVoList[].scanCode - scan barcodes
*/
const parsePrepareResponse = (responseText) => {
let data;
try {
data = JSON.parse(responseText);
} catch (e) {
return null;
}
if (!data || typeof data !== 'object') return null;
// The response might be wrapped in a standard envelope
const row = data.row || data.data || data;
if (!row || typeof row !== 'object') return null;
// Extract all order headers
const headerList = row.orderHeaderVoList;
if (!Array.isArray(headerList) || headerList.length === 0) return null;
// Build a map: orderHeaderId -> header info
const headerMap = new Map();
for (const h of headerList) {
const orderCode = normalizeText(h.orderCode);
if (!orderCode) continue;
headerMap.set(h.id, {
orderCode,
orderHeaderId: h.id,
trackingNo: normalizeText(h.trackingNo || h.refCode || h.expressNo || h.waybillNo),
refCode: normalizeText(h.refCode)
});
}
// Extract SKU list from order details and group by orderHeaderId
const detailList = row.orderDetailVoList || [];
const detailsByOrder = new Map(); // orderHeaderId -> skuList
for (const detail of detailList) {
const orderHeaderId = detail.orderHeaderId;
if (!orderHeaderId) continue;
if (!detailsByOrder.has(orderHeaderId)) {
detailsByOrder.set(orderHeaderId, []);
}
const skuCode = normalizeText(detail.skuCode);
if (!skuCode) continue;
const orderedQty = Number(detail.orderedQty || detail.dueOutQty || detail.originExpectedQty) || 0;
const packedQty = Number(detail.packedQty) || 0;
const barcodes = [
detail.barCode,
...(Array.isArray(detail.barCodeList) ? detail.barCodeList : [])
].map((b) => normalizeText(b)).filter(Boolean);
detailsByOrder.get(orderHeaderId).push({
skuCode,
orderedQty,
packedQty,
trackingNo: normalizeText(detail.trackingNo || detail.refCode),
barcodes
});
}
// Extract scan barcodes from packing list
const scanCodes = [];
const packingList = row.barCodeAndPackingVoList || [];
for (const item of packingList) {
const sc = normalizeText(item.scanCode);
if (sc) scanCodes.push(sc);
}
// Build all orders
const allOrders = [];
for (const [orderHeaderId, headerInfo] of headerMap) {
const skuList = detailsByOrder.get(orderHeaderId) || [];
allOrders.push({
orderCode: headerInfo.orderCode,
orderHeaderId: headerInfo.orderHeaderId,
trackingNo: headerInfo.trackingNo || normalizeText(skuList[0]?.trackingNo),
skuList,
scanCodes
});
}
if (allOrders.length === 0) return null;
return { allOrders, scanCodes };
};
const handlePrepareResponse = (payload) => {
if (!state.active) return;
const prepareTime = new Date();
const parsed = parsePrepareResponse(payload.responseText);
if (!parsed || !parsed.allOrders || parsed.allOrders.length === 0) return;
const orderCodes = parsed.allOrders.map(o => o.orderCode).join(', ');
logStep('qy', '解析到 prepare 接口响应', {
orderCount: parsed.allOrders.length,
orderCodes,
scanCodeCount: parsed.scanCodes.length
});
// Clear previous order context
state.preparedOrderCode = null;
state.skuAliasMap.clear();
state.orderTrackingMap.clear();
state.skuTrackingMap.clear();
// Process all orders
const allSkuList = [];
for (const order of parsed.allOrders) {
state.orderTrackingMap.set(order.orderCode, order.trackingNo);
for (const sku of order.skuList) {
const skuTrackingNo = normalizeText(sku.trackingNo) || order.trackingNo;
// Build SKU -> tracking mapping
if (skuTrackingNo) {
state.skuTrackingMap.set(`${order.orderCode}||${sku.skuCode}`, skuTrackingNo);
if (!state.skuTrackingMap.has(sku.skuCode)) {
state.skuTrackingMap.set(sku.skuCode, skuTrackingNo);
}
}
allSkuList.push(sku);
}
}
// Build global SKU alias map from all orders
state.skuAliasMap = buildSkuAliasMap(allSkuList);
state.preparedOrderCode = orderCodes;
state.currentOrderCode = orderCodes;
state.currentOrderStartTime = prepareTime;
// NOTE: Do NOT call updateMonitorData here — OSD should only update on confirm / actual scan
};
const handleConfirmResponse = (payload) => {
if (!state.active) return;
if (!responseIsSuccess(payload.responseText)) {
logStep('qy', 'confirm 接口未成功,跳过上传');
return;
}
const parsed = parseConfirmResponse(payload.responseText);
if (!parsed || !parsed.trackingNo) {
logStep('qy', 'confirm 接口缺少最终运单号,跳过上传');
return;
}
if (parsed.signature && parsed.signature === state.lastConfirmSignature) {
logStep('qy', 'confirm 接口重复,跳过上传');
return;
}
state.currentOrderCode = parsed.orderCode || state.currentOrderCode;
const scans = state.scannedSkuList.length > 0
? state.scannedSkuList.map((item) => ({
sku: item.skuCode,
scanId: item.scanCode,
time: item.time
}))
: state.pendingScans.slice();
if (scans.length === 0) {
logStep('qy', 'confirm 接口无已记录扫描 SKU,跳过上传', {
orderCode: state.currentOrderCode,
trackingNo: parsed.trackingNo
});
return;
}
state.lastConfirmSignature = parsed.signature;
const endTime = parsed.packedTime && !Number.isNaN(parsed.packedTime.getTime())
? parsed.packedTime
: new Date();
const startTime = state.currentOrderStartTime || new Date(scans[0].time) || endTime;
state.lastScanSku = scans[scans.length - 1]?.sku || state.lastScanSku;
logStep('qy', 'confirm 接口触发结束订单', {
orderCode: state.currentOrderCode,
trackingNo: parsed.trackingNo,
count: scans.length
});
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: state.currentOrderCode || '',
sku: state.lastScanSku || '',
trackingNo: ''
});
void finalizeAndUploadScans({
trackingNo: parsed.trackingNo,
scans,
startTime,
endTime,
reason: 'qy_pack_confirm'
});
state.lastOrderEndTime = endTime;
state.currentOrderStartTime = endTime;
state.pendingScans = [];
state.scannedSkuList = [];
};
/**
* Handle intercepted QY API responses.
*/
const handleApiResponse = (payload) => {
let pathname = '';
try {
pathname = new URL(String(payload.url || ''), window.location.origin).pathname;
} catch (e) {
pathname = '';
}
if (pathname === '/service/ob/pack/prepare') {
handlePrepareResponse(payload);
return;
}
if (pathname === '/service/ob/pack/confirm' || pathname === '/service/ob/pack/confirmAndApply') {
handleConfirmResponse(payload);
}
};
const resetState = () => {
state.currentOrderCode = null;
state.lastScanSku = null;
state.lastScanId = null;
state.lastScanAt = 0;
state.pendingScans = [];
state.scannedSkuList = [];
state.currentOrderStartTime = null;
state.lastOrderEndTime = null;
state.preparedOrderCode = null;
state.skuAliasMap.clear();
state.orderTrackingMap.clear();
state.skuTrackingMap.clear();
state.lastConfirmSignature = null;
logStep('qy', '重置驱动状态');
};
const start = () => {
if (state.active) return;
logStep('qy', '启动 API 驱动');
state.active = true;
qyApiResponseHandler = handleApiResponse;
installApiInterceptor();
document.addEventListener('keydown', handleSkuInputKeydown, true);
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: state.currentOrderCode || '',
sku: state.lastScanSku || '',
trackingNo: ''
});
};
const stop = () => {
logStep('qy', '停止 API 驱动');
state.active = false;
if (qyApiResponseHandler === handleApiResponse) {
qyApiResponseHandler = null;
}
document.removeEventListener('keydown', handleSkuInputKeydown, true);
resetState();
};
const getStatus = () => ({
isTargetPage: true,
url: window.location.href,
batchNo: state.currentOrderCode || '',
sku: state.lastScanSku || '',
trackingNo: ''
});
return {
name: 'qy',
match,
start,
stop,
refresh: () => {
updateMonitorData({
isTargetPage: true,
url: window.location.href,
batchNo: state.currentOrderCode || '',
sku: state.lastScanSku || '',
trackingNo: ''
});
},
getStatus
};
};
// ============================================================
// 驱动管理与初始化
// ============================================================
const DRIVERS = [createQyDriver()];
const stopActiveDriver = () => {
if (activeDriver) {
logStep('core', '停止当前驱动', { name: activeDriver.name });
activeDriver.stop();
}
activeDriver = null;
activeDriverName = '';
};
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: ''
});
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: ''
});
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 });
};
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);
};
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();
}
const registerTampermonkeyMenu = () => {
if (!IS_TOP || typeof GM_registerMenuCommand !== 'function') return;
unregisterMenuCommands();
registerMenuCommand('HCC: 清空登录并刷新', async () => {
await Storage.remove([
'isLoggedIn', 'userId', 'jwtToken', 'userData', 'loginTime',
'currentStationId', 'currentStationData'
]);
location.reload();
});
registerMenuCommand('HCC: 清空工位并刷新', async () => {
await Storage.remove(['currentStationId', 'currentStationData']);
location.reload();
});
};
registerTampermonkeyMenu();
})();