您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为运单系统添加抢单倒计时和自动抢单功能
// ==UserScript== // @name 运单自动抢单助手 // @namespace // @version 1.0.4 // @description 为运单系统添加抢单倒计时和自动抢单功能 // @author wavever // @match https://carrier.okguanli.com/* // @grant none // @run-at document-end // ==/UserScript== (function () { 'use strict'; // 全局变量 let countdownIntervals = new Map(); // 存储倒计时任务 let autoGrabTasks = new Map(); // 存储自动抢单任务 let isProcessing = false; // 防止重复处理 let orderDataMap = new Map(); // 存储运单数据,key为运单ID,value为运单信息 let statusIndicator = null; // 状态指示器元素 // 设置相关变量 const SETTINGS_KEY = 'carrierAutoSettings'; const DESTINATION_LIST_KEY = 'carrierDestinationList'; const NOTE_CONFIG_KEY = 'carrierNoteConfig'; let settings = { enableAutoGrab: false, // 是否启用自动抢单 showCountdown: false, // 是否显示倒计时 enableAutoRefresh: false, // 是否启用自动刷新 refreshInterval: 10, // 自动刷新间隔(秒) skipGrabConfirm: false, // 是否跳过抢单确认弹框 enableDestinationMatch: false, // 是否启用目的地匹配自动抢单 }; // 目的地匹配列表 let destinationList = []; // 备注配置 let noteConfig = { mode: 'whitelist', // 'whitelist' | 'blacklist' whitelist: [], // 白名单关键字数组 blacklist: [] // 黑名单关键字数组 }; // 目的地匹配相关变量 let matchedOrders = new Map(); // 存储匹配到的订单 let matchInfoPanel = null; // 浮动信息面板 // 自动刷新相关变量 let autoRefreshTimer = null; let lastRefreshTime = null; let isRefreshPaused = false; // 是否暂停自动刷新 // 加载设置 function loadSettings() { console.log('开始加载设置...'); const savedSettings = localStorage.getItem(SETTINGS_KEY); console.log('从localStorage读取的原始数据:', savedSettings); if (savedSettings) { try { const parsed = JSON.parse(savedSettings); console.log('解析后的设置数据:', parsed); // 逐个字段更新,确保每个字段都被正确设置 Object.keys(parsed).forEach(key => { if (parsed[key] !== undefined && parsed[key] !== null) { settings[key] = parsed[key]; console.log(`已更新设置字段 ${key}:`, parsed[key]); } }); console.log('最终加载的设置:', settings); } catch (e) { console.error('加载设置失败:', e); } } else { console.log('没有找到已保存的设置,使用默认设置:', settings); } // 加载目的地列表 const savedDestinations = localStorage.getItem(DESTINATION_LIST_KEY); console.log('从localStorage读取的目的地数据:', savedDestinations); if (savedDestinations) { try { destinationList = JSON.parse(savedDestinations); console.log('已加载目的地列表:', destinationList); } catch (e) { console.error('加载目的地列表失败:', e); destinationList = []; } } else { console.log('没有找到已保存的目的地列表,使用空列表'); } // 加载备注配置 const savedNoteConfig = localStorage.getItem(NOTE_CONFIG_KEY); console.log('从localStorage读取的备注配置数据:', savedNoteConfig); if (savedNoteConfig) { try { noteConfig = { ...noteConfig, ...JSON.parse(savedNoteConfig) }; console.log('已加载备注配置:', noteConfig); } catch (e) { console.error('加载备注配置失败:', e); noteConfig = { mode: 'whitelist', whitelist: [], blacklist: [] }; } } else { console.log('没有找到已保存的备注配置,使用默认配置'); } } // 保存设置 function saveSettings() { try { console.log('准备保存设置到localStorage:', settings); const settingsStr = JSON.stringify(settings); console.log('序列化后的设置字符串:', settingsStr); localStorage.setItem(SETTINGS_KEY, settingsStr); console.log('设置已成功保存到localStorage'); // 验证保存是否成功 const verification = localStorage.getItem(SETTINGS_KEY); console.log('验证保存结果:', verification); console.log('保存验证是否成功:', verification === settingsStr); } catch (e) { console.error('保存设置到localStorage失败:', e); } } // 保存目的地列表 function saveDestinationList() { try { localStorage.setItem(DESTINATION_LIST_KEY, JSON.stringify(destinationList)); console.log('目的地列表已保存到localStorage:', destinationList); } catch (e) { console.error('保存目的地列表失败:', e); } } // 保存备注配置 function saveNoteConfig() { try { localStorage.setItem(NOTE_CONFIG_KEY, JSON.stringify(noteConfig)); console.log('备注配置已保存到localStorage:', noteConfig); } catch (e) { console.error('保存备注配置失败:', e); } } // 时间同步相关变量 let ntpTimeOffset = 0; // NTP时间与本地时间的差值(毫秒) let isNtpSynced = false; // 是否已同步NTP时间 let ntpSyncRetries = 0; // NTP同步重试次数 const MAX_NTP_RETRIES = 3; // 最大重试次数 const NTP_SERVERS = [ 'worldtimeapi.org/api/timezone/Asia/Shanghai', 'timeapi.io/api/Time/current/zone?timeZone=Asia/Shanghai', 'time.cloudflare.com', 'time.google.com' ]; // NTP服务器列表 // 创建设置按钮 function createSettingsButton() { const settingsIcon = document.querySelector("#root > div.layout___2fuYE > div.header___2Twvl > div:nth-child(2) > label"); if (!settingsIcon) { console.warn('未找到设置图标'); return; } const ourSettingsBtn = document.createElement('label'); ourSettingsBtn.className = 'ant-dropdown-trigger auto-grab-settings'; // 添加悬停样式 const style = document.createElement('style'); style.textContent = ` .auto-grab-settings:hover { background: rgba(255, 255, 255, 0.2) !important; transform: translateY(-1px); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); } .auto-grab-settings:active { transform: translateY(0); box-shadow: none; } `; document.head.appendChild(style); ourSettingsBtn.style.cssText = ` display: inline-flex; align-items: center; font-size: 16px; color: rgb(255, 255, 255); cursor: pointer; margin-left: 16px; background: rgba(255, 255, 255, 0.1); padding: 4px 12px; border-radius: 4px; transition: all 0.3s; `; ourSettingsBtn.innerHTML = ` <span role="img" aria-label="thunderbolt" class="anticon anticon-thunderbolt" style="font-size: 18px; color: #ffd700;"> <svg viewBox="64 64 896 896" focusable="false" data-icon="thunderbolt" width="1em" height="1em" fill="currentColor" aria-hidden="true"> <path d="M848 359.3H627.7L825.8 109c4.1-5.3.4-13-6.3-13H436c-2.8 0-5.5 1.5-6.9 4L170 547.5c-3.1 5.3.7 12 6.9 12h174.4l-89.4 357.6c-1.9 7.8 7.5 13.3 13.3 7.7L853.5 373c5.2-4.9 1.7-13.7-5.5-13.7zM378.2 732.5l60.3-241H281.1l189.6-327.4h224.6L487 427.4h211L378.2 732.5z"/> </svg> </span> <span style="margin-left: 6px; font-size: 14px; font-weight: 500;">运单助手</span> `; // 插入我们的设置按钮 settingsIcon.parentNode.insertBefore(ourSettingsBtn, settingsIcon.nextSibling); // 点击事件处理 ourSettingsBtn.addEventListener('click', showSettingsModal); } // 显示设置弹窗 function showSettingsModal() { console.log('=== 打开设置弹框 ==='); console.log('当前settings:', JSON.stringify(settings, null, 2)); // 创建模态框容器 const modalContainer = document.createElement('div'); modalContainer.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; `; // 创建模态框内容 const modalContent = document.createElement('div'); modalContent.style.cssText = ` background-color: white; padding: 24px; border-radius: 8px; min-width: 400px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); `; // 标题 const title = document.createElement('h3'); title.style.cssText = 'margin: 0 0 16px 0; font-size: 16px; font-weight: 500;'; title.textContent = '运单助手设置'; // 设置项 const settingsContent = document.createElement('div'); settingsContent.style.cssText = 'margin-bottom: 24px;'; let tempSettings = { ...settings }; // 临时设置副本 // 自动抢单开关 const autoGrabSwitch = createSettingSwitch( '启用自动抢单', settings.enableAutoGrab, (checked) => { tempSettings.enableAutoGrab = checked; console.log('临时设置更新 - 启用自动抢单:', checked); } ); // 倒计时显示开关 const countdownSwitch = createSettingSwitch( '显示倒计时', settings.showCountdown, (checked) => { tempSettings.showCountdown = checked; console.log('临时设置更新 - 显示倒计时:', checked); } ); // 跳过确认弹框开关 const skipConfirmSwitch = createSettingSwitch( '点击抢单按钮后,不再展示确认弹框 ⚠️', settings.skipGrabConfirm, (checked) => { tempSettings.skipGrabConfirm = checked; console.log('临时设置更新 - 跳过确认弹框:', checked); } ); // 目的地匹配自动抢单开关容器 const destinationMatchContainer = document.createElement('div'); destinationMatchContainer.style.cssText = 'margin-bottom: 16px;'; // 目的地匹配自动抢单开关 const destinationMatchSwitch = createSettingSwitch( '目的地匹配自动抢单 🎯', tempSettings.enableDestinationMatch, (checked) => { tempSettings.enableDestinationMatch = checked; console.log('临时设置更新 - 目的地匹配自动抢单:', checked); // 更新配置按钮的显示状态 configDestinationBtn.style.display = checked ? 'inline-block' : 'none'; configNoteBtn.style.display = checked ? 'inline-block' : 'none'; } ); // 配置目的地按钮 const configDestinationBtn = document.createElement('button'); configDestinationBtn.type = 'button'; configDestinationBtn.className = 'ant-btn ant-btn-link'; configDestinationBtn.style.cssText = ` margin-left: 8px; padding: 0 8px; font-size: 12px; color: #1890ff; display: ${tempSettings.enableDestinationMatch ? 'inline-block' : 'none'}; `; configDestinationBtn.innerHTML = '📝 配置目的地'; configDestinationBtn.onclick = () => { showDestinationConfigModal(tempSettings); }; // 配置备注按钮 const configNoteBtn = document.createElement('button'); configNoteBtn.type = 'button'; configNoteBtn.className = 'ant-btn ant-btn-link'; configNoteBtn.style.cssText = ` margin-left: 8px; padding: 0 8px; font-size: 12px; color: #1890ff; display: ${tempSettings.enableDestinationMatch ? 'inline-block' : 'none'}; `; configNoteBtn.innerHTML = '📝 配置备注'; configNoteBtn.onclick = () => { showNoteConfigModal(tempSettings); }; destinationMatchContainer.appendChild(destinationMatchSwitch); destinationMatchContainer.appendChild(configDestinationBtn); destinationMatchContainer.appendChild(configNoteBtn); // 自动刷新设置区域 const autoRefreshContainer = document.createElement('div'); autoRefreshContainer.style.cssText = 'margin-bottom: 16px;'; // 自动刷新开关 const autoRefreshSwitch = createSettingSwitch( '自动刷新运单', tempSettings.enableAutoRefresh, (checked) => { tempSettings.enableAutoRefresh = checked; console.log('临时设置更新 - 自动刷新运单:', checked); // 只更新UI显示,不实际应用设置 if (checked) { autoRefreshIntervalInput.style.display = 'block'; } else { autoRefreshIntervalInput.style.display = 'none'; } } ); // 刷新间隔输入框 const autoRefreshIntervalInput = document.createElement('div'); autoRefreshIntervalInput.style.cssText = ` margin-top: 8px; margin-left: 24px; display: ${tempSettings.enableAutoRefresh ? 'block' : 'none'}; `; autoRefreshIntervalInput.innerHTML = ` <div style="display: flex; align-items: center;"> <span style="margin-right: 8px;">刷新间隔:</span> <input type="number" class="ant-input" style="width: 80px;" min="1" max="3600" value="${tempSettings.refreshInterval}" > <span style="margin-left: 8px;">秒</span> </div> `; // 监听间隔时间变更 const intervalInput = autoRefreshIntervalInput.querySelector('input'); intervalInput.addEventListener('change', (e) => { let value = parseInt(e.target.value); if (isNaN(value) || value < 1) value = 1; if (value > 3600) value = 3600; tempSettings.refreshInterval = value; e.target.value = value; console.log('临时设置更新 - 刷新间隔:', value); }); autoRefreshContainer.appendChild(autoRefreshSwitch); autoRefreshContainer.appendChild(autoRefreshIntervalInput); // 按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = 'display: flex; justify-content: flex-end; gap: 8px;'; // 取消按钮 const cancelBtn = document.createElement('button'); cancelBtn.className = 'ant-btn'; cancelBtn.textContent = '取消'; cancelBtn.onclick = () => { console.log('用户取消设置修改,临时设置被丢弃'); document.body.removeChild(modalContainer); }; // 保存按钮 const saveBtn = document.createElement('button'); saveBtn.className = 'ant-btn ant-btn-primary'; saveBtn.textContent = '保存'; saveBtn.onclick = () => { console.log('=== 开始保存设置 ==='); console.log('保存前 - 当前settings:', JSON.stringify(settings, null, 2)); console.log('保存后 - 应用后的settings:', JSON.stringify(tempSettings, null, 2)); // 应用临时设置到实际设置 const oldSettings = { ...settings }; settings = { ...tempSettings }; // 立即保存设置 saveSettings(); // 验证保存是否成功 setTimeout(() => { console.log('验证保存结果:'); const currentStorageSettings = localStorage.getItem(SETTINGS_KEY); console.log('localStorage中的当前设置:', currentStorageSettings); console.log('=== 保存设置完成 ==='); }, 100); // 检查需要特殊处理的设置变更 if (oldSettings.skipGrabConfirm !== settings.skipGrabConfirm && isOnGrabOrderTab()) { setupGrabButtons(); } if (oldSettings.enableAutoRefresh !== settings.enableAutoRefresh) { const pauseBtn = document.querySelector('.refresh-pause-btn'); if (pauseBtn) { pauseBtn.style.display = settings.enableAutoRefresh ? 'inline-flex' : 'none'; } if (settings.enableAutoRefresh) { if (window.autoRefreshControl) { window.autoRefreshControl.start(); } } else { if (window.autoRefreshControl) { window.autoRefreshControl.stop(); } isRefreshPaused = false; } } if (oldSettings.refreshInterval !== settings.refreshInterval && settings.enableAutoRefresh && window.autoRefreshControl) { window.autoRefreshControl.start(); } // 处理目的地匹配功能的状态变化 if (oldSettings.enableDestinationMatch !== settings.enableDestinationMatch) { if (settings.enableDestinationMatch && destinationList.length > 0) { createMatchInfoPanel(); } else { // 关闭目的地匹配功能,清理相关状态 matchedOrders.clear(); if (matchInfoPanel) { matchInfoPanel.style.display = 'none'; } } } document.body.removeChild(modalContainer); console.log('设置已保存并应用,无需刷新页面'); // 显示保存成功提示 const toast = document.createElement('div'); toast.style.cssText = ` position: fixed; top: 20px; right: 20px; background: #52c41a; color: white; padding: 12px 20px; border-radius: 6px; z-index: 10000; font-size: 14px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); `; toast.textContent = '✅ 设置已保存'; document.body.appendChild(toast); setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 3000); }; // 组装模态框 buttonContainer.appendChild(cancelBtn); buttonContainer.appendChild(saveBtn); settingsContent.appendChild(autoGrabSwitch); settingsContent.appendChild(countdownSwitch); settingsContent.appendChild(skipConfirmSwitch); settingsContent.appendChild(destinationMatchContainer); settingsContent.appendChild(autoRefreshContainer); modalContent.appendChild(title); modalContent.appendChild(settingsContent); modalContent.appendChild(buttonContainer); modalContainer.appendChild(modalContent); // 显示模态框 document.body.appendChild(modalContainer); console.log('设置弹窗已打开,当前设置:', settings); } // 显示目的地配置弹窗 function showDestinationConfigModal(parentSettings) { // 创建模态框容器 const modalContainer = document.createElement('div'); modalContainer.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 10000; `; // 创建模态框内容 const modalContent = document.createElement('div'); modalContent.style.cssText = ` background-color: white; padding: 24px; border-radius: 8px; min-width: 500px; max-width: 80%; max-height: 80vh; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); display: flex; flex-direction: column; `; // 标题 const title = document.createElement('h3'); title.style.cssText = 'margin: 0 0 16px 0; font-size: 16px; font-weight: 500;'; title.textContent = '配置目的地匹配列表'; // 列表容器 const listContainer = document.createElement('div'); listContainer.style.cssText = ` flex: 1; overflow-y: auto; max-height: 400px; border: 1px solid #d9d9d9; border-radius: 6px; margin-bottom: 16px; `; // 添加目的地输入框 const inputContainer = document.createElement('div'); inputContainer.style.cssText = 'display: flex; gap: 8px; margin-bottom: 16px; align-items: center;'; const destinationInput = document.createElement('input'); destinationInput.type = 'text'; destinationInput.className = 'ant-input'; destinationInput.placeholder = '输入目的地名称(支持模糊匹配)'; destinationInput.style.cssText = 'flex: 1;'; const addBtn = document.createElement('button'); addBtn.type = 'button'; addBtn.className = 'ant-btn ant-btn-primary'; addBtn.textContent = '添加'; inputContainer.appendChild(destinationInput); inputContainer.appendChild(addBtn); // 渲染目的地列表 function renderDestinationList() { listContainer.innerHTML = ''; if (destinationList.length === 0) { const emptyDiv = document.createElement('div'); emptyDiv.style.cssText = ` padding: 40px 20px; text-align: center; color: #999; font-size: 14px; `; emptyDiv.innerHTML = ` <div style="font-size: 32px; margin-bottom: 12px;">📍</div> <div>暂无配置的目的地</div> <div style="font-size: 12px; margin-top: 8px;">请在上方输入框中添加目的地</div> `; listContainer.appendChild(emptyDiv); return; } destinationList.forEach((destination, index) => { const item = document.createElement('div'); item.style.cssText = ` display: flex; align-items: center; padding: 12px 16px; border-bottom: 1px solid #f0f0f0; transition: background-color 0.2s; `; item.addEventListener('mouseenter', () => item.style.backgroundColor = '#f5f5f5'); item.addEventListener('mouseleave', () => item.style.backgroundColor = 'transparent'); // 序号 const indexDiv = document.createElement('div'); indexDiv.style.cssText = ` width: 30px; height: 30px; border-radius: 50%; background: #1890ff; color: white; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; margin-right: 12px; `; indexDiv.textContent = index + 1; // 目的地名称 const nameDiv = document.createElement('div'); nameDiv.style.cssText = ` flex: 1; font-size: 14px; color: #262626; `; nameDiv.textContent = destination; // 删除按钮 const deleteBtn = document.createElement('button'); deleteBtn.type = 'button'; deleteBtn.className = 'ant-btn ant-btn-text ant-btn-sm'; deleteBtn.style.cssText = ` color: #ff4d4f; border: none; padding: 4px 8px; font-size: 12px; `; deleteBtn.innerHTML = '🗑️ 删除'; deleteBtn.onclick = () => { destinationList.splice(index, 1); renderDestinationList(); }; item.appendChild(indexDiv); item.appendChild(nameDiv); item.appendChild(deleteBtn); listContainer.appendChild(item); }); } // 添加目的地功能 addBtn.onclick = () => { const destination = destinationInput.value.trim(); if (!destination) { alert('请输入目的地名称'); return; } if (destinationList.includes(destination)) { alert('该目的地已存在'); return; } destinationList.push(destination); destinationInput.value = ''; renderDestinationList(); }; // 支持回车添加 destinationInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { addBtn.click(); } }); // 按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = 'display: flex; justify-content: space-between; align-items: center; gap: 8px;'; const leftButtons = document.createElement('div'); leftButtons.style.cssText = 'display: flex; gap: 8px;'; const rightButtons = document.createElement('div'); rightButtons.style.cssText = 'display: flex; gap: 8px;'; // 取消按钮 const cancelBtn = document.createElement('button'); cancelBtn.className = 'ant-btn'; cancelBtn.textContent = '取消'; cancelBtn.onclick = () => { // 关闭当前设置弹窗并打开目的地配置弹窗 document.body.removeChild(modalContainer); }; // 清空按钮 const clearBtn = document.createElement('button'); clearBtn.className = 'ant-btn'; clearBtn.style.color = '#ff4d4f'; clearBtn.textContent = '清空列表'; clearBtn.onclick = () => { if (confirm('确定要清空所有目的地吗?')) { destinationList.length = 0; renderDestinationList(); } }; // 保存按钮 const saveBtn = document.createElement('button'); saveBtn.className = 'ant-btn ant-btn-primary'; saveBtn.textContent = '保存并应用设置'; saveBtn.onclick = () => { // 先保存目的地列表 saveDestinationList(); // 应用父级临时设置到实际设置 console.log('应用临时设置:', parentSettings); const oldSettings = { ...settings }; settings = { ...parentSettings }; saveSettings(); // 检查需要特殊处理的设置变更 if (oldSettings.skipGrabConfirm !== settings.skipGrabConfirm && isOnGrabOrderTab()) { setupGrabButtons(); } if (oldSettings.enableAutoRefresh !== settings.enableAutoRefresh) { const pauseBtn = document.querySelector('.refresh-pause-btn'); if (pauseBtn) { pauseBtn.style.display = settings.enableAutoRefresh ? 'inline-flex' : 'none'; } if (settings.enableAutoRefresh) { if (window.autoRefreshControl) { window.autoRefreshControl.start(); } } else { if (window.autoRefreshControl) { window.autoRefreshControl.stop(); } isRefreshPaused = false; } } if (oldSettings.refreshInterval !== settings.refreshInterval && settings.enableAutoRefresh && window.autoRefreshControl) { window.autoRefreshControl.start(); } // 如果启用了目的地匹配,初始化面板 if (settings.enableDestinationMatch && destinationList.length > 0) { createMatchInfoPanel(); } document.body.removeChild(modalContainer); console.log('目的地配置和设置已保存:', destinationList, settings); // 显示保存成功提示 const toast = document.createElement('div'); toast.style.cssText = ` position: fixed; top: 20px; right: 20px; background: #52c41a; color: white; padding: 12px 20px; border-radius: 6px; z-index: 10000; font-size: 14px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); `; if (settings.enableDestinationMatch && destinationList.length > 0) { toast.innerHTML = ` <div style="font-weight: 500; margin-bottom: 4px;">✅ 设置已保存</div> <div style="font-size: 12px; opacity: 0.9;">已配置 ${destinationList.length} 个目的地,将自动匹配抢单</div> `; } else { toast.textContent = '✅ 设置已保存'; } document.body.appendChild(toast); setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 3000); // 刷新页面以完全应用新设置 console.log('设置已保存并应用,页面将刷新'); setTimeout(() => location.reload(), 1000); }; leftButtons.appendChild(cancelBtn); rightButtons.appendChild(clearBtn); rightButtons.appendChild(saveBtn); buttonContainer.appendChild(leftButtons); buttonContainer.appendChild(rightButtons); // 组装模态框 modalContent.appendChild(title); modalContent.appendChild(inputContainer); modalContent.appendChild(listContainer); modalContent.appendChild(buttonContainer); modalContainer.appendChild(modalContent); // 初始渲染列表 renderDestinationList(); // 点击遮罩层关闭弹窗 modalContainer.addEventListener('click', (e) => { if (e.target === modalContainer) { backBtn.click(); // 相当于返回设置 } }); // 显示模态框 document.body.appendChild(modalContainer); // 聚焦到输入框 setTimeout(() => destinationInput.focus(), 100); } // 显示备注配置弹窗 function showNoteConfigModal(parentSettings) { // 创建模态框容器 const modalContainer = document.createElement('div'); modalContainer.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 10000; `; // 创建模态框内容 const modalContent = document.createElement('div'); modalContent.style.cssText = ` background-color: white; padding: 24px; border-radius: 8px; min-width: 500px; max-width: 600px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); max-height: 70vh; overflow-y: auto; `; // 标题 const title = document.createElement('h3'); title.style.cssText = 'margin: 0 0 16px 0; font-size: 16px; font-weight: 500;'; title.textContent = '备注配置'; // 创建临时配置副本 let tempNoteConfig = { mode: noteConfig.mode || 'whitelist', whitelist: [...(noteConfig.whitelist || [])], blacklist: [...(noteConfig.blacklist || [])] }; // 模式选择区域 const modeContainer = document.createElement('div'); modeContainer.style.cssText = 'margin-bottom: 16px;'; const modeLabel = document.createElement('div'); modeLabel.style.cssText = 'margin-bottom: 8px; font-weight: 500; color: #262626;'; modeLabel.textContent = '备注匹配模式:'; const modeRadioContainer = document.createElement('div'); modeRadioContainer.style.cssText = 'display: flex; gap: 16px;'; // 白名单选项 const whitelistRadioContainer = document.createElement('label'); whitelistRadioContainer.style.cssText = 'display: flex; align-items: center; cursor: pointer;'; const whitelistRadio = document.createElement('input'); whitelistRadio.type = 'radio'; whitelistRadio.name = 'noteMode'; whitelistRadio.value = 'whitelist'; whitelistRadio.checked = tempNoteConfig.mode === 'whitelist'; whitelistRadio.style.cssText = 'margin-right: 6px;'; const whitelistLabel = document.createElement('span'); whitelistLabel.textContent = '白名单(命中则自动抢单)'; whitelistLabel.style.color = '#52c41a'; whitelistRadioContainer.appendChild(whitelistRadio); whitelistRadioContainer.appendChild(whitelistLabel); // 黑名单选项 const blacklistRadioContainer = document.createElement('label'); blacklistRadioContainer.style.cssText = 'display: flex; align-items: center; cursor: pointer;'; const blacklistRadio = document.createElement('input'); blacklistRadio.type = 'radio'; blacklistRadio.name = 'noteMode'; blacklistRadio.value = 'blacklist'; blacklistRadio.checked = tempNoteConfig.mode === 'blacklist'; blacklistRadio.style.cssText = 'margin-right: 6px;'; const blacklistLabel = document.createElement('span'); blacklistLabel.textContent = '黑名单(命中则不抢单)'; blacklistLabel.style.color = '#ff4d4f'; blacklistRadioContainer.appendChild(blacklistRadio); blacklistRadioContainer.appendChild(blacklistLabel); modeRadioContainer.appendChild(whitelistRadioContainer); modeRadioContainer.appendChild(blacklistRadioContainer); modeContainer.appendChild(modeLabel); modeContainer.appendChild(modeRadioContainer); // 关键字输入区域 const inputContainer = document.createElement('div'); inputContainer.style.cssText = 'margin-bottom: 16px;'; const inputLabel = document.createElement('div'); inputLabel.style.cssText = 'margin-bottom: 8px; font-weight: 500; color: #262626;'; inputLabel.textContent = '添加关键字:'; const inputGroup = document.createElement('div'); inputGroup.style.cssText = 'display: flex; gap: 8px;'; const keywordInput = document.createElement('input'); keywordInput.type = 'text'; keywordInput.className = 'ant-input'; keywordInput.placeholder = '输入备注关键字'; keywordInput.style.cssText = 'flex: 1; padding: 8px 12px;'; const addBtn = document.createElement('button'); addBtn.type = 'button'; addBtn.className = 'ant-btn ant-btn-primary'; addBtn.textContent = '添加'; addBtn.style.cssText = 'padding: 8px 16px;'; inputGroup.appendChild(keywordInput); inputGroup.appendChild(addBtn); inputContainer.appendChild(inputLabel); inputContainer.appendChild(inputGroup); // 关键字列表显示区域 const listContainer = document.createElement('div'); listContainer.style.cssText = 'margin-bottom: 20px;'; const listLabel = document.createElement('div'); listLabel.style.cssText = 'margin-bottom: 8px; font-weight: 500; color: #262626;'; const keywordListContainer = document.createElement('div'); keywordListContainer.style.cssText = ` min-height: 60px; border: 1px solid #d9d9d9; border-radius: 6px; padding: 12px; background: #fafafa; display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; `; listContainer.appendChild(listLabel); listContainer.appendChild(keywordListContainer); // 渲染关键字列表 function renderKeywordList() { const currentList = tempNoteConfig.mode === 'whitelist' ? tempNoteConfig.whitelist : tempNoteConfig.blacklist; const modeColor = tempNoteConfig.mode === 'whitelist' ? '#52c41a' : '#ff4d4f'; listLabel.textContent = `${tempNoteConfig.mode === 'whitelist' ? '白名单' : '黑名单'}关键字 (${currentList.length}):`; listLabel.style.color = modeColor; keywordListContainer.innerHTML = ''; if (currentList.length === 0) { const emptyTip = document.createElement('div'); emptyTip.style.cssText = 'color: #999; font-size: 14px; width: 100%; text-align: center; padding: 20px;'; emptyTip.textContent = `暂无${tempNoteConfig.mode === 'whitelist' ? '白名单' : '黑名单'}关键字`; keywordListContainer.appendChild(emptyTip); return; } currentList.forEach((keyword, index) => { const keywordTag = document.createElement('div'); keywordTag.style.cssText = ` display: inline-flex; align-items: center; background: ${tempNoteConfig.mode === 'whitelist' ? '#f6ffed' : '#fff2f0'}; border: 1px solid ${tempNoteConfig.mode === 'whitelist' ? '#b7eb8f' : '#ffccc7'}; color: ${modeColor}; padding: 4px 8px; border-radius: 4px; font-size: 12px; gap: 6px; `; const keywordText = document.createElement('span'); keywordText.textContent = keyword; const removeBtn = document.createElement('button'); removeBtn.style.cssText = ` background: none; border: none; color: ${modeColor}; cursor: pointer; padding: 0; font-size: 14px; line-height: 1; opacity: 0.7; `; removeBtn.innerHTML = '✕'; removeBtn.title = '移除关键字'; removeBtn.onclick = () => { if (tempNoteConfig.mode === 'whitelist') { tempNoteConfig.whitelist.splice(index, 1); } else { tempNoteConfig.blacklist.splice(index, 1); } renderKeywordList(); }; keywordTag.appendChild(keywordText); keywordTag.appendChild(removeBtn); keywordListContainer.appendChild(keywordTag); }); } // 添加关键字功能 function addKeyword() { const keyword = keywordInput.value.trim(); if (!keyword) return; const currentList = tempNoteConfig.mode === 'whitelist' ? tempNoteConfig.whitelist : tempNoteConfig.blacklist; if (currentList.includes(keyword)) { keywordInput.style.borderColor = '#ff4d4f'; setTimeout(() => { keywordInput.style.borderColor = ''; }, 2000); return; } currentList.push(keyword); keywordInput.value = ''; renderKeywordList(); } // 事件绑定 addBtn.onclick = addKeyword; keywordInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); addKeyword(); } }); // 模式切换事件 whitelistRadio.addEventListener('change', () => { if (whitelistRadio.checked) { tempNoteConfig.mode = 'whitelist'; renderKeywordList(); } }); blacklistRadio.addEventListener('change', () => { if (blacklistRadio.checked) { tempNoteConfig.mode = 'blacklist'; renderKeywordList(); } }); // 按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = 'display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px;'; // 返回按钮 const backBtn = document.createElement('button'); backBtn.className = 'ant-btn'; backBtn.textContent = '返回'; backBtn.onclick = () => { document.body.removeChild(modalContainer); }; // 保存按钮 const saveBtn = document.createElement('button'); saveBtn.className = 'ant-btn ant-btn-primary'; saveBtn.textContent = '保存'; saveBtn.onclick = () => { console.log('保存备注配置:', tempNoteConfig); // 应用临时配置到全局配置 noteConfig = { ...tempNoteConfig }; saveNoteConfig(); // 显示保存成功提示 const toast = document.createElement('div'); toast.style.cssText = ` position: fixed; top: 20px; right: 20px; background: #52c41a; color: white; padding: 12px 20px; border-radius: 6px; z-index: 10001; font-size: 14px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); `; toast.textContent = '✅ 备注配置已保存'; document.body.appendChild(toast); setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 3000); // 关闭弹窗并返回设置 backBtn.click(); }; // 组装模态框 buttonContainer.appendChild(backBtn); buttonContainer.appendChild(saveBtn); modalContent.appendChild(title); modalContent.appendChild(modeContainer); modalContent.appendChild(inputContainer); modalContent.appendChild(listContainer); modalContent.appendChild(buttonContainer); modalContainer.appendChild(modalContent); // 初始渲染列表 renderKeywordList(); // 点击遮罩层关闭弹窗 modalContainer.addEventListener('click', (e) => { if (e.target === modalContainer) { backBtn.click(); // 相当于返回设置 } }); // 显示模态框 document.body.appendChild(modalContainer); // 聚焦到输入框 setTimeout(() => keywordInput.focus(), 100); } // 创建浮动信息展示面板 function createMatchInfoPanel() { // 如果已存在面板则先移除 if (matchInfoPanel && matchInfoPanel.parentNode) { matchInfoPanel.parentNode.removeChild(matchInfoPanel); } // 创建浮动面板 matchInfoPanel = document.createElement('div'); matchInfoPanel.style.cssText = ` position: fixed; top: 80px; right: 20px; width: 320px; max-height: 500px; background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(8px); border: 1px solid rgba(217, 217, 217, 0.6); border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 9000; overflow: hidden; display: none; transition: all 0.3s ease; `; // 面板标题 const header = document.createElement('div'); header.style.cssText = ` padding: 12px 16px; background: rgba(24, 144, 255, 0.1); border-bottom: 1px solid rgba(217, 217, 217, 0.3); font-size: 14px; font-weight: 500; color: #1890ff; display: flex; align-items: center; justify-content: space-between; `; header.innerHTML = ` <span>🎯 目的地匹配抢单</span> <span class="match-count">0</span> `; // 面板内容 const content = document.createElement('div'); content.className = 'match-panel-content'; content.style.cssText = ` max-height: 400px; overflow-y: auto; `; matchInfoPanel.appendChild(header); matchInfoPanel.appendChild(content); document.body.appendChild(matchInfoPanel); console.log('浮动信息面板已创建'); return matchInfoPanel; } // 更新浮动面板内容 function updateMatchInfoPanel() { if (!settings.enableDestinationMatch || destinationList.length === 0) { if (matchInfoPanel) { matchInfoPanel.style.display = 'none'; } return; } if (!matchInfoPanel) { createMatchInfoPanel(); } const content = matchInfoPanel.querySelector('.match-panel-content'); const countElement = matchInfoPanel.querySelector('.match-count'); if (matchedOrders.size === 0) { matchInfoPanel.style.display = 'none'; return; } // 显示面板 matchInfoPanel.style.display = 'block'; countElement.textContent = matchedOrders.size; // 清空内容 content.innerHTML = ''; // 渲染匹配的订单 matchedOrders.forEach((orderInfo, orderId) => { const item = document.createElement('div'); item.style.cssText = ` padding: 12px 16px; border-bottom: 1px solid rgba(240, 240, 240, 0.5); transition: background-color 0.2s; `; item.addEventListener('mouseenter', () => item.style.backgroundColor = 'rgba(245, 245, 245, 0.3)'); item.addEventListener('mouseleave', () => item.style.backgroundColor = 'transparent'); // 订单信息 const orderDiv = document.createElement('div'); orderDiv.style.cssText = 'margin-bottom: 8px;'; orderDiv.innerHTML = ` <div style="font-size: 14px; font-weight: 500; color: #262626; margin-bottom: 4px;"> ${orderInfo.code || orderId} </div> <div style="font-size: 12px; color: #666; margin-bottom: 2px;"> 📍 目的地: ${orderInfo.destinationName || '未知'} </div> ${orderInfo.note ? `<div style="font-size: 12px; color: #999;">💬 ${orderInfo.note}</div>` : ''} `; // 状态显示 const statusDiv = document.createElement('div'); statusDiv.className = `match-status-${orderId}`; statusDiv.style.cssText = ` font-size: 12px; font-weight: 500; padding: 2px 8px; border-radius: 4px; text-align: center; `; // 根据订单状态显示不同内容 if (orderInfo.grabStatus === 'pending') { statusDiv.textContent = '⏳ 等待抢单时间'; statusDiv.style.background = 'rgba(250, 173, 20, 0.1)'; statusDiv.style.color = '#faad14'; } else if (orderInfo.grabStatus === 'grabbing') { statusDiv.textContent = '🚀 抢单中...'; statusDiv.style.background = 'rgba(24, 144, 255, 0.1)'; statusDiv.style.color = '#1890ff'; } else if (orderInfo.grabStatus === 'success') { statusDiv.textContent = '✅ 抢单成功'; statusDiv.style.background = 'rgba(82, 196, 26, 0.1)'; statusDiv.style.color = '#52c41a'; } else if (orderInfo.grabStatus === 'failed') { statusDiv.textContent = '❌ 抢单失败'; statusDiv.style.background = 'rgba(245, 34, 45, 0.1)'; statusDiv.style.color = '#f5222d'; } item.appendChild(orderDiv); item.appendChild(statusDiv); content.appendChild(item); }); } // 更新匹配订单状态 function updateMatchOrderStatus(orderId, status) { if (matchedOrders.has(orderId)) { const orderInfo = matchedOrders.get(orderId); orderInfo.grabStatus = status; matchedOrders.set(orderId, orderInfo); updateMatchInfoPanel(); } } // 更新暂停按钮状态 function updatePauseButton(button) { button.innerHTML = ` <span role="img" aria-label="${isRefreshPaused ? 'play-circle' : 'pause-circle'}" class="anticon anticon-${isRefreshPaused ? 'play-circle' : 'pause-circle'}" style="margin-right: 4px;"> <svg viewBox="64 64 896 896" focusable="false" data-icon="${isRefreshPaused ? 'play-circle' : 'pause-circle'}" width="1em" height="1em" fill="currentColor" aria-hidden="true"> ${isRefreshPaused ? '<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm-88-532h-48c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8zm224 0h-48c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8z"></path>' : '<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm-80-544c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8h208c4.4 0 8-3.6 8-8V348c0-4.4-3.6-8-8-8H432z"></path>'} </svg> </span> <span>${isRefreshPaused ? '继续' : '暂停'}</span> `; button.title = isRefreshPaused ? '继续自动刷新' : '暂停自动刷新'; button.style.color = isRefreshPaused ? '#1890ff' : '#ff4d4f'; } // 自动刷新功能 function setupAutoRefresh() { // 查找刷新按钮 const refreshBtn = document.querySelector("#\\35 6x3i7oYVrcCd393 > div > div > div > div.dispatch___3GhUt > div.container___1d0D1 > div > div.ant-card-head > div > div > div > button"); if (!refreshBtn) { console.warn('未找到刷新按钮'); return; } // 创建显示和控制区域 const statusContainer = document.createElement('div'); statusContainer.style.cssText = ` display: inline-flex; align-items: center; margin-right: 12px; font-size: 12px; color: rgba(0, 0, 0, 0.45); `; // 状态显示 const statusDisplay = document.createElement('div'); statusDisplay.style.cssText = 'display: inline-flex; align-items: center; margin-right: 8px;'; statusDisplay.innerHTML = ` <span class="last-refresh" style="margin-right: 12px;"></span> <span class="next-refresh"></span> `; // 暂停/继续按钮 const pauseBtn = document.createElement('button'); pauseBtn.type = 'button'; pauseBtn.className = 'ant-btn ant-btn-link refresh-pause-btn'; pauseBtn.style.cssText = ` padding: 0 8px; height: 24px; font-size: 14px; display: ${settings.enableAutoRefresh ? 'inline-flex' : 'none'}; align-items: center; color: #1890ff; `; updatePauseButton(pauseBtn); pauseBtn.onclick = () => { isRefreshPaused = !isRefreshPaused; updatePauseButton(pauseBtn); if (isRefreshPaused) { if (autoRefreshTimer) { clearInterval(autoRefreshTimer); autoRefreshTimer = null; } } else { startAutoRefresh(); } }; statusContainer.appendChild(statusDisplay); statusContainer.appendChild(pauseBtn); // 插入状态显示 refreshBtn.parentNode.insertBefore(statusContainer, refreshBtn); // 更新显示函数 function updateRefreshStatus() { const lastRefreshEl = statusContainer.querySelector('.last-refresh'); const nextRefreshEl = statusContainer.querySelector('.next-refresh'); if (lastRefreshTime) { lastRefreshEl.textContent = `上次刷新: ${lastRefreshTime.toLocaleTimeString()}`; } if (settings.enableAutoRefresh) { const now = Date.now(); const nextRefreshTime = lastRefreshTime ? lastRefreshTime.getTime() + settings.refreshInterval * 1000 : now + settings.refreshInterval * 1000; const timeLeft = Math.max(0, Math.floor((nextRefreshTime - now) / 1000)); if (isRefreshPaused) { nextRefreshEl.textContent = '自动刷新已暂停'; nextRefreshEl.style.color = '#ff4d4f'; } else { nextRefreshEl.textContent = `下次刷新: ${timeLeft}秒`; nextRefreshEl.style.color = 'inherit'; } } else { nextRefreshEl.textContent = ''; nextRefreshEl.style.color = 'inherit'; } } // 执行刷新 function doRefresh() { lastRefreshTime = new Date(); refreshBtn.click(); updateRefreshStatus(); } // 设置自动刷新定时器 function startAutoRefresh() { if (autoRefreshTimer) { clearInterval(autoRefreshTimer); } if (settings.enableAutoRefresh) { autoRefreshTimer = setInterval(() => { doRefresh(); }, settings.refreshInterval * 1000); // 更新显示的定时器 setInterval(updateRefreshStatus, 1000); } } // 停止自动刷新 function stopAutoRefresh() { if (autoRefreshTimer) { clearInterval(autoRefreshTimer); autoRefreshTimer = null; } lastRefreshTime = null; updateRefreshStatus(); } // 根据设置状态初始化 if (settings.enableAutoRefresh) { startAutoRefresh(); } // 导出函数供设置变更时使用 window.autoRefreshControl = { start: startAutoRefresh, stop: stopAutoRefresh }; } // 创建设置开关 function createSettingSwitch(label, checked, onChange) { const container = document.createElement('div'); container.style.cssText = 'display: flex; align-items: center; margin-bottom: 16px;'; const switchLabel = document.createElement('span'); switchLabel.style.cssText = 'margin-right: 8px;'; switchLabel.textContent = label; const switchContainer = document.createElement('button'); switchContainer.className = `ant-switch${checked ? ' ant-switch-checked' : ''}`; switchContainer.style.cssText = 'position: relative; display: inline-block; width: 44px; height: 22px;'; const switchHandle = document.createElement('span'); switchHandle.className = 'ant-switch-handle'; switchContainer.appendChild(switchHandle); // 维护内部状态以确保准确性 let currentState = checked; switchContainer.onclick = () => { // 切换状态 currentState = !currentState; // 更新UI if (currentState) { switchContainer.classList.add('ant-switch-checked'); } else { switchContainer.classList.remove('ant-switch-checked'); } // 调用回调函数 onChange(currentState); console.log(`开关 "${label}" 状态变更为: ${currentState}`); }; container.appendChild(switchLabel); container.appendChild(switchContainer); return container; } // NTP时间同步功能 async function syncNtpTime() { if (isNtpSynced || ntpSyncRetries >= MAX_NTP_RETRIES) { return; } console.log(`开始NTP时间同步,第 ${ntpSyncRetries + 1} 次尝试...`); for (const server of NTP_SERVERS) { try { const localTime = Date.now(); let response, serverTime; if (server.includes('worldtimeapi.org')) { // 使用WorldTimeAPI response = await fetch(`https://${server}`, { method: 'GET', cache: 'no-cache' }); if (response.ok) { const data = await response.json(); serverTime = new Date(data.datetime).getTime(); } } else if (server.includes('timeapi.io')) { // 使用TimeAPI response = await fetch(`https://${server}`, { method: 'GET', cache: 'no-cache' }); if (response.ok) { const data = await response.json(); serverTime = new Date(data.dateTime).getTime(); } } else { // 使用HTTP HEAD请求获取Date头 response = await fetch(`https://${server}`, { method: 'HEAD', cache: 'no-cache' }); if (response.ok) { const serverTimeStr = response.headers.get('Date'); if (serverTimeStr) { serverTime = new Date(serverTimeStr).getTime(); } } } if (serverTime) { const networkDelay = (Date.now() - localTime) / 2; // 估算网络延迟 const adjustedServerTime = serverTime + networkDelay; ntpTimeOffset = adjustedServerTime - Date.now(); isNtpSynced = true; console.log(`NTP时间同步成功 - 服务器: ${server}`); console.log(`本地时间: ${new Date().toISOString()}`); console.log(`服务器时间: ${new Date(adjustedServerTime).toISOString()}`); console.log(`时间偏移: ${ntpTimeOffset}ms (${ntpTimeOffset > 0 ? '服务器时间较快' : '本地时间较快'})`); return; } } catch (error) { console.warn(`NTP同步失败 - 服务器: ${server}, 错误:`, error.message); } } ntpSyncRetries++; if (ntpSyncRetries < MAX_NTP_RETRIES) { setTimeout(() => syncNtpTime(), 5000); // 5秒后重试 } else { console.warn('NTP时间同步失败,将使用本地时间'); } } // 获取本地时间 function getLocalTime() { return new Date(); } // 获取NTP同步时间 function getNtpTime() { return new Date(Date.now() + ntpTimeOffset); } // 工具函数:解析时间字符串为Date对象 function parseTimeString(timeStr) { if (!timeStr || timeStr.trim() === '') { console.warn('时间字符串为空:', timeStr); return null; } const cleanTimeStr = timeStr.replace(/\s+/g, ' ').trim(); console.log(`解析时间字符串: "${cleanTimeStr}"`); try { // 尝试多种时间格式 let date = null; // 格式1: "2025-09-07 18:07:00" if (cleanTimeStr.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)) { date = new Date(cleanTimeStr); } // 格式2: "2025-09-07 18:07" (没有秒) else if (cleanTimeStr.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/)) { date = new Date(cleanTimeStr + ':00'); } // 格式3: ISO 时间格式 else if (cleanTimeStr.includes('T')) { date = new Date(cleanTimeStr); } // 格式4: 多行时间格式 "2025-09-07 18:07:00\n时间错误" else if (cleanTimeStr.includes('\n')) { const firstLine = cleanTimeStr.split('\n')[0].trim(); if (firstLine.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)) { date = new Date(firstLine); } else if (firstLine.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/)) { date = new Date(firstLine + ':00'); } } // 其他格式尝试直接解析 else { date = new Date(cleanTimeStr); } // 检查解析结果 if (!date || isNaN(date.getTime())) { console.error('时间解析结果无效:', cleanTimeStr, date); return null; } console.log(`时间解析成功: "${cleanTimeStr}" -> ${date.toISOString()}`); return date; } catch (e) { console.error('解析时间失败:', cleanTimeStr, e); return null; } } // 工具函数:格式化倒计时显示 function formatCountdown(seconds) { // 检查输入有效性 if (!isFinite(seconds) || isNaN(seconds)) { console.error('formatCountdown 接收到无效数字:', seconds); return '时间错误'; } if (seconds <= 0) return '已到时间'; const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; if (hours > 0) { return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } else { return `${minutes}:${secs.toString().padStart(2, '0')}`; } } // 检查是否在抢单页面 function isOnGrabOrderTab() { const activeTab = document.querySelector('.ant-radio-button-wrapper-checked input[value="CAN_GRAB_ORD"]'); return activeTab !== null; } // 确保操作列有足够宽度容纳自动抢单按钮 function ensureOperationColumnWidth() { const headers = document.querySelectorAll('.ant-table-header thead th'); const headerColgroup = document.querySelector('.ant-table-header colgroup'); const bodyColgroup = document.querySelector('.ant-table-body colgroup'); if (!headerColgroup || !bodyColgroup) return; const cols = headerColgroup.children; let operationHeaderIndex = -1; headers.forEach((header, index) => { if (header.textContent && header.textContent.includes('操作')) { operationHeaderIndex = index; } }); if (operationHeaderIndex >= 0 && cols[operationHeaderIndex]) { const operationCol = cols[operationHeaderIndex]; const currentWidth = parseInt(operationCol.style.width) || 120; // 为操作列增加额外宽度以容纳自动抢单按钮 const newWidth = Math.max(currentWidth, 200); operationCol.style.cssText = `width: ${newWidth}px; min-width: ${newWidth}px;`; // 同步更新body colgroup中对应的列 const bodyOperationCol = bodyColgroup.children[operationHeaderIndex]; if (bodyOperationCol) { bodyOperationCol.style.cssText = `width: ${newWidth}px; min-width: ${newWidth}px;`; } // 更新对应的表头宽度 const operationHeader = headers[operationHeaderIndex]; if (operationHeader) { operationHeader.style.width = `${newWidth}px`; operationHeader.style.minWidth = `${newWidth}px`; } console.log(`操作列宽度已更新为: ${newWidth}px`); } } // 为表格行添加倒计时和自动抢单功能 function processTableRows() { const tableBody = document.querySelector('.ant-table-body tbody'); if (!tableBody) return; const rows = tableBody.querySelectorAll('tr[data-row-key]'); if (isProcessing || !settings.showCountdown) return; isProcessing = true; try { rows.forEach((row) => { const rowKey = row.getAttribute('data-row-key'); if (!rowKey) return; const cells = row.querySelectorAll('td'); if (cells.length < 6) return; // 找到"可抢单时间"列 let grabTimeCell = null; cells.forEach((cell, index) => { // 通过查找表头来确定可抢单时间列的位置 const headerCells = document.querySelectorAll('.ant-table-header thead th'); if (headerCells[index] && headerCells[index].textContent.includes('可抢单时间')) { grabTimeCell = cell; console.log(`运单 ${rowKey} 找到可抢单时间列,索引: ${index}, 内容: "${cell.textContent.trim()}"`); } }); if (!grabTimeCell) { console.warn(`运单 ${rowKey} 未找到可抢单时间列,跳过行处理。可用列数: ${cells.length}`); // 打印所有表头帮助调试 const headerCells = document.querySelectorAll('.ant-table-header thead th'); headerCells.forEach((header, i) => { console.log(`表头 ${i}: "${header.textContent.trim()}"`); }); return; } const grabTimeText = grabTimeCell.textContent.trim(); // 检查是否已经处理过这一行 if (grabTimeCell.querySelector('.countdown-display')) { updateExistingRow(row, rowKey, grabTimeText); return; } // 直接在可抢单时间列中添加倒计时显示 console.log(`运单 ${rowKey} 开始添加倒计时到时间列`); addCountdownToTimeCell(grabTimeCell, rowKey, grabTimeText); // 在操作列添加自动抢单按钮 addAutoGrabButton(row, rowKey); }); } finally { isProcessing = false; } } // 在可抢单时间列中添加倒计时显示 function addCountdownToTimeCell(grabTimeCell, rowKey, grabTimeText) { console.log(`运单 ${rowKey} addCountdownToTimeCell 被调用,原始时间: "${grabTimeText}"`); if (!grabTimeCell) { console.error(`运单 ${rowKey} grabTimeCell 为空,无法添加倒计时`); return; } // 从时间字符串中提取第一行作为原始时间 let originalTime = grabTimeText; if (grabTimeText.includes('\n')) { originalTime = grabTimeText.split('\n')[0].trim(); } console.log(`运单 ${rowKey} 开始重构时间列内容,提取的时间: "${originalTime}"`); // 清空原内容,重新构建 grabTimeCell.innerHTML = ''; grabTimeCell.style.cssText = 'text-align: center; overflow: hidden; padding: 8px 4px;'; // 原始时间显示 const timeDiv = document.createElement('div'); timeDiv.className = 'grab-time-original'; timeDiv.style.cssText = 'font-size: 12px; color: #666; line-height: 1.2; margin-bottom: 2px;'; timeDiv.textContent = originalTime; // 倒计时显示 const countdownDiv = document.createElement('div'); countdownDiv.className = 'countdown-display'; countdownDiv.style.cssText = 'font-size: 13px; font-weight: bold; color: #ff4d4f; line-height: 1.2;'; countdownDiv.textContent = '--'; grabTimeCell.appendChild(timeDiv); grabTimeCell.appendChild(countdownDiv); console.log(`运单 ${rowKey} 时间列内容重构完成,开始倒计时`); // 开始倒计时 startCountdown(rowKey, originalTime, grabTimeCell); } // 更新已存在的行 function updateExistingRow(row, rowKey, grabTimeText) { // 从时间字符串中提取第一行作为原始时间 let originalTime = grabTimeText; if (grabTimeText.includes('\n')) { originalTime = grabTimeText.split('\n')[0].trim(); } // 找到可抢单时间列 let updateGrabTimeCell = null; row.querySelectorAll('td').forEach((cell, index) => { const headerCells = document.querySelectorAll('.ant-table-header thead th'); if (headerCells[index] && headerCells[index].textContent.includes('可抢单时间')) { updateGrabTimeCell = cell; } }); if (updateGrabTimeCell && updateGrabTimeCell.querySelector('.countdown-display') && !autoGrabTasks.has(rowKey)) { // 重新开始倒计时(处理数据更新的情况) startCountdown(rowKey, originalTime, updateGrabTimeCell); } // 检查自动抢单按钮状态 const existingAutoBtn = row.querySelector('.auto-grab-btn'); const grabTime = parseTimeString(originalTime); if (grabTime && !isNaN(grabTime.getTime())) { const now = new Date(); const timeDiff = grabTime.getTime() - now.getTime(); console.log(`运单 ${rowKey} 更新检查: 时间差=${Math.floor(timeDiff / 1000)}秒, 有按钮=${!!existingAutoBtn}, 有任务=${autoGrabTasks.has(rowKey)}`); // 如果时间已到,移除自动抢单按钮 if (timeDiff <= 0 && existingAutoBtn && !autoGrabTasks.has(rowKey)) { existingAutoBtn.remove(); console.log(`运单 ${rowKey} 时间已到,移除自动抢单按钮`); } // 如果时间未到且没有按钮,添加按钮 else if (timeDiff > 0 && !existingAutoBtn && !autoGrabTasks.has(rowKey)) { console.log(`运单 ${rowKey} 时间未到且无按钮,添加自动抢单按钮`); addAutoGrabButton(row, rowKey); } } else { console.warn(`运单 ${rowKey} 时间解析失败,无法检查按钮状态`); } } // 开始双重倒计时(本地时间和NTP时间) function startCountdown(rowKey, grabTimeText, grabTimeCell) { // 清除已存在的倒计时 if (countdownIntervals.has(rowKey)) { const existing = countdownIntervals.get(rowKey); if (existing.local) clearInterval(existing.local); if (existing.ntp) clearInterval(existing.ntp); } const grabTime = parseTimeString(grabTimeText); if (!grabTime) { const displayElement = grabTimeCell.querySelector('.countdown-display'); if (displayElement) { displayElement.textContent = '时间错误'; } return; } // 创建双重显示元素 const displayElement = grabTimeCell.querySelector('.countdown-display'); if (!displayElement) { console.warn(`运单 ${rowKey} 找不到倒计时显示元素`); return; } // 重构显示元素,支持双重时间显示 displayElement.innerHTML = ''; displayElement.style.cssText = 'font-size: 12px; font-weight: bold; line-height: 1.2;'; // 本地时间倒计时显示 const localDisplay = document.createElement('div'); localDisplay.className = 'local-countdown'; localDisplay.style.cssText = 'color: #ff4d4f; margin-bottom: 2px;'; localDisplay.title = '基于本地时间的倒计时'; // NTP时间倒计时显示 const ntpDisplay = document.createElement('div'); ntpDisplay.className = 'ntp-countdown'; ntpDisplay.style.cssText = 'color: #1890ff; font-size: 11px;'; ntpDisplay.title = '基于NTP同步时间的倒计时'; displayElement.appendChild(localDisplay); if (isNtpSynced && Math.abs(ntpTimeOffset) > 1000) { // 时间差超过1秒才显示NTP倒计时 displayElement.appendChild(ntpDisplay); } // 本地时间倒计时更新函数 const updateLocalCountdown = () => { const now = getLocalTime(); const timeDiff = grabTime.getTime() - now.getTime(); const secondsLeft = Math.floor(timeDiff / 1000); if (secondsLeft <= 0) { localDisplay.textContent = '已到时间(本地)'; localDisplay.style.color = '#52c41a'; // 清除本地倒计时 const intervals = countdownIntervals.get(rowKey); if (intervals && intervals.local) { clearInterval(intervals.local); intervals.local = null; } // 检查是否需要移除自动抢单按钮 checkAndRemoveAutoGrabButton(rowKey, grabTimeCell); } else if (!isNaN(secondsLeft) && isFinite(secondsLeft)) { localDisplay.textContent = `本地: ${formatCountdown(secondsLeft)}`; localDisplay.style.color = '#ff4d4f'; } else { localDisplay.textContent = '本地: 时间错误'; localDisplay.style.color = '#999'; } }; // NTP时间倒计时更新函数 const updateNtpCountdown = () => { if (!isNtpSynced) return; const now = getNtpTime(); const timeDiff = grabTime.getTime() - now.getTime(); const secondsLeft = Math.floor(timeDiff / 1000); if (secondsLeft <= 0) { ntpDisplay.textContent = '已到时间(NTP)'; ntpDisplay.style.color = '#52c41a'; // 清除NTP倒计时 const intervals = countdownIntervals.get(rowKey); if (intervals && intervals.ntp) { clearInterval(intervals.ntp); intervals.ntp = null; } // 检查是否需要移除自动抢单按钮 checkAndRemoveAutoGrabButton(rowKey, grabTimeCell); } else if (!isNaN(secondsLeft) && isFinite(secondsLeft)) { ntpDisplay.textContent = `NTP: ${formatCountdown(secondsLeft)}`; ntpDisplay.style.color = '#1890ff'; } else { ntpDisplay.textContent = 'NTP: 时间错误'; ntpDisplay.style.color = '#999'; } }; // 立即执行一次 updateLocalCountdown(); if (isNtpSynced) { updateNtpCountdown(); } // 设置双重定时器 const localIntervalId = setInterval(updateLocalCountdown, 1000); let ntpIntervalId = null; if (isNtpSynced && Math.abs(ntpTimeOffset) > 1000) { ntpIntervalId = setInterval(updateNtpCountdown, 1000); } countdownIntervals.set(rowKey, { local: localIntervalId, ntp: ntpIntervalId }); console.log(`运单 ${rowKey} 开始双重倒计时 - 本地时间差值: 0ms, NTP时间差值: ${ntpTimeOffset}ms`); } // 检查并移除自动抢单按钮 function checkAndRemoveAutoGrabButton(rowKey, grabTimeCell) { // 只有两个倒计时都结束了才移除按钮 const intervals = countdownIntervals.get(rowKey); if (intervals && !intervals.local && !intervals.ntp) { countdownIntervals.delete(rowKey); // 时间到了,移除未使用的自动抢单按钮 if (!autoGrabTasks.has(rowKey)) { const row = grabTimeCell.closest('tr'); if (row) { const autoBtn = row.querySelector('.auto-grab-btn'); if (autoBtn) { autoBtn.remove(); console.log(`运单 ${rowKey} 双重倒计时结束,移除自动抢单按钮`); } } } } } // 添加自动抢单按钮 function addAutoGrabButton(row, rowKey) { const operationCell = row.querySelector('.ant-table-cell-fix-right:not(.ant-table-cell-scrollbar)'); if (!operationCell) return; // 检查是否已经添加过自动抢单按钮 if (operationCell.querySelector('.auto-grab-btn')) return; // 获取可抢单时间,验证这是一个有效的运单行 const cells = row.querySelectorAll('td'); if (cells.length < 6) return; // 找到"可抢单时间"列 let grabTimeCell = null; cells.forEach((cell, index) => { const headerCells = document.querySelectorAll('.ant-table-header thead th'); if (headerCells[index] && headerCells[index].textContent.includes('可抢单时间')) { grabTimeCell = cell; } }); if (!grabTimeCell) return; const grabTimeText = grabTimeCell.textContent.trim(); // 从时间字符串中提取第一行作为原始时间 let originalTime = grabTimeText; if (grabTimeText.includes('\n')) { originalTime = grabTimeText.split('\n')[0].trim(); } const grabTime = parseTimeString(originalTime); if (!grabTime) return; // 时间格式错误,不添加按钮 // 检查是否已到抢单时间(使用本地时间和NTP时间中较早的一个) const localTime = getLocalTime(); const ntpTime = isNtpSynced ? getNtpTime() : localTime; const localTimeDiff = grabTime.getTime() - localTime.getTime(); const ntpTimeDiff = grabTime.getTime() - ntpTime.getTime(); // 使用较小的时间差(即较早到达的时间) const minTimeDiff = Math.min(localTimeDiff, ntpTimeDiff); // 只有时间未到时才显示自动抢单按钮 if (minTimeDiff <= 0) { console.log(`运单 ${rowKey} 时间已到,不显示自动抢单按钮 (本地: ${Math.floor(localTimeDiff / 1000)}s, NTP: ${Math.floor(ntpTimeDiff / 1000)}s)`); return; } console.log(`运单 ${rowKey} 时间检查 - 本地时间差: ${Math.floor(localTimeDiff / 1000)}s, NTP时间差: ${Math.floor(ntpTimeDiff / 1000)}s`); // 创建自动抢单按钮 const autoGrabBtn = document.createElement('button'); autoGrabBtn.type = 'button'; autoGrabBtn.className = 'ant-btn ant-btn-primary auto-grab-btn'; autoGrabBtn.style.cssText = 'margin-left: 5px; background-color: #1890ff; border-color: #1890ff;'; autoGrabBtn.innerHTML = '<span>自动抢单</span>'; // 点击事件 autoGrabBtn.addEventListener('click', function () { handleAutoGrab(rowKey, autoGrabBtn, row); }); // 将按钮添加到操作列中 const operationDiv = operationCell.querySelector('div') || operationCell; operationDiv.appendChild(autoGrabBtn); } // 处理自动抢单逻辑 function handleAutoGrab(rowKey, autoGrabBtn, row) { // 如果已经在自动抢单状态,取消 if (autoGrabTasks.has(rowKey)) { cancelAutoGrab(rowKey, autoGrabBtn); return; } // 获取可抢单时间 const cells = row.querySelectorAll('td'); let grabTimeCell = null; cells.forEach((cell, index) => { const headerCells = document.querySelectorAll('.ant-table-header thead th'); if (headerCells[index] && headerCells[index].textContent.includes('可抢单时间')) { grabTimeCell = cell; } }); if (!grabTimeCell) { alert('无法找到可抢单时间列'); return; } const grabTimeText = grabTimeCell.textContent.trim(); // 从时间字符串中提取第一行作为原始时间 let originalTime = grabTimeText; if (grabTimeText.includes('\n')) { originalTime = grabTimeText.split('\n')[0].trim(); } const grabTime = parseTimeString(originalTime); if (!grabTime) { alert('获取可抢单时间失败,请检查时间格式'); return; } // 计算本地时间和NTP时间的延迟 const localTime = getLocalTime(); const ntpTime = isNtpSynced ? getNtpTime() : localTime; const localDelay = grabTime.getTime() - localTime.getTime(); const ntpDelay = grabTime.getTime() - ntpTime.getTime(); // 如果任何一个时间已到,立即执行抢单 if (localDelay <= 0 || ntpDelay <= 0) { console.log(`运单 ${rowKey} 时间已到,立即执行API抢单 (本地延迟: ${Math.floor(localDelay / 1000)}s, NTP延迟: ${Math.floor(ntpDelay / 1000)}s)`); grabOrderByApi(parseInt(rowKey)).then(success => { if (success) { console.log(`运单 ${rowKey} API抢单成功`); } else { console.warn(`运单 ${rowKey} API抢单失败`); } }); return; } console.log(`运单 ${rowKey} 创建双重自动抢单任务 - 本地延迟: ${Math.floor(localDelay / 1000)}s, NTP延迟: ${Math.floor(ntpDelay / 1000)}s`); // 创建抢单执行函数 const executeGrab = async (timeType) => { try { console.log(`运单 ${rowKey} ${timeType}定时器触发,执行API抢单`); // 检查是否已经被其他定时器执行过 if (!autoGrabTasks.has(rowKey)) { console.log(`运单 ${rowKey} 已被其他定时器处理,跳过执行`); return; } const success = await grabOrderByApi(parseInt(rowKey)); if (success) { console.log(`运单 ${rowKey} ${timeType}自动API抢单成功`); } else { console.warn(`运单 ${rowKey} ${timeType}自动API抢单失败`); } } catch (e) { console.error(`运单 ${rowKey} ${timeType}自动抢单异常:`, e); } finally { // 清理所有相关定时器 const tasks = autoGrabTasks.get(rowKey); if (tasks) { if (tasks.local) { clearTimeout(tasks.local); console.log(`清理运单 ${rowKey} 本地定时器`); } if (tasks.ntp) { clearTimeout(tasks.ntp); console.log(`清理运单 ${rowKey} NTP定时器`); } } autoGrabTasks.delete(rowKey); resetAutoGrabButton(autoGrabBtn); updateStatusIndicator(); } }; // 设置双重自动抢单任务 const localTimeoutId = setTimeout(() => executeGrab('本地时间'), localDelay); let ntpTimeoutId = null; // 只有在NTP同步且时间差异显著时才创建NTP定时器 if (isNtpSynced && Math.abs(ntpDelay - localDelay) > 1000) { ntpTimeoutId = setTimeout(() => executeGrab('NTP时间'), ntpDelay); } // 保存双重任务 autoGrabTasks.set(rowKey, { local: localTimeoutId, ntp: ntpTimeoutId }); updateStatusIndicator(); // 更新按钮状态(使用较小的延迟) const minDelay = Math.min(localDelay, ntpDelay); updateAutoGrabButton(autoGrabBtn, Math.floor(minDelay / 1000)); // 淡化倒计时显示(表示正在自动抢单) const autoGrabTimeCell = row.querySelectorAll('td').find((_, index) => { const headerCells = document.querySelectorAll('.ant-table-header thead th'); return headerCells[index] && headerCells[index].textContent.includes('可抢单时间'); }); if (autoGrabTimeCell) { const countdownDisplay = autoGrabTimeCell.querySelector('.countdown-display'); if (countdownDisplay) { countdownDisplay.style.opacity = '0.5'; countdownDisplay.textContent = '自动抢单中...'; } } } // 更新自动抢单按钮显示 function updateAutoGrabButton(button, secondsLeft) { const updateDisplay = () => { if (secondsLeft <= 0) { button.innerHTML = '<span>执行中...</span>'; button.style.backgroundColor = '#52c41a'; return; } button.innerHTML = `<span>等待抢单: ${formatCountdown(secondsLeft)}</span>`; button.style.backgroundColor = '#faad14'; button.style.borderColor = '#faad14'; secondsLeft--; if (secondsLeft >= 0) { setTimeout(updateDisplay, 1000); } }; updateDisplay(); } // 取消自动抢单(支持双重任务) function cancelAutoGrab(rowKey, button) { if (autoGrabTasks.has(rowKey)) { const tasks = autoGrabTasks.get(rowKey); // 支持旧格式和新格式 if (typeof tasks === 'number') { // 旧格式:单个定时器ID clearTimeout(tasks); console.log(`取消运单 ${rowKey} 的自动抢单任务`); } else if (tasks && typeof tasks === 'object') { // 新格式:双重定时器对象 if (tasks.local) { clearTimeout(tasks.local); console.log(`取消运单 ${rowKey} 的本地时间抢单任务`); } if (tasks.ntp) { clearTimeout(tasks.ntp); console.log(`取消运单 ${rowKey} 的NTP时间抢单任务`); } } autoGrabTasks.delete(rowKey); updateStatusIndicator(); } resetAutoGrabButton(button); } // 重置自动抢单按钮 function resetAutoGrabButton(button) { button.innerHTML = '<span>自动抢单</span>'; button.style.backgroundColor = '#1890ff'; button.style.borderColor = '#1890ff'; } // 处理所有行的调试点击功能 function processDebugClickHandlers() { const tableBody = document.querySelector('.ant-table-body tbody'); if (!tableBody) return; const rows = tableBody.querySelectorAll('tr[data-row-key]'); rows.forEach((row) => { const rowKey = row.getAttribute('data-row-key'); if (rowKey) { addDebugClickHandler(row, rowKey); } }); } // 添加调试点击功能 function addDebugClickHandler(row, rowKey) { // 找到序号列(第2列,索引1) const sequenceCell = row.querySelector('td.ant-table-cell-fix-left:not(.ant-table-selection-column)'); if (sequenceCell && !sequenceCell.hasAttribute('data-debug-added')) { // 标记已添加,避免重复绑定 sequenceCell.setAttribute('data-debug-added', 'true'); // 添加点击样式提示 sequenceCell.style.cursor = 'pointer'; sequenceCell.style.transition = 'background-color 0.2s'; sequenceCell.title = '点击查看运单详细信息'; // 添加鼠标悬停效果 sequenceCell.addEventListener('mouseenter', function () { this.style.backgroundColor = '#f0f0f0'; }); sequenceCell.addEventListener('mouseleave', function () { this.style.backgroundColor = ''; }); // 添加点击事件 sequenceCell.addEventListener('click', function (e) { e.stopPropagation(); // 防止事件冒泡 const orderId = parseInt(rowKey); const orderInfo = orderDataMap.get(orderId); console.log('='.repeat(60)); console.log(`🔍 运单调试信息 - 序号: ${this.textContent.trim()}`); console.log('='.repeat(60)); if (orderInfo) { console.log('📋 运单基本信息:'); console.log(` ID: ${orderInfo.id}`); console.log(` 名称: ${orderInfo.name}`); console.log(` 编号: ${orderInfo.code}`); console.log(` 目的地: ${orderInfo.destinationName}`); console.log(` 备注: ${orderInfo.note}`); if (orderInfo.planAllocateTime) { const allocateTime = new Date(orderInfo.planAllocateTime * 1000); const now = new Date(); const timeDiff = allocateTime.getTime() - now.getTime(); const secondsLeft = Math.floor(timeDiff / 1000); console.log(` 可抢单时间: ${allocateTime.toLocaleString()}`); console.log(` 距离抢单时间: ${secondsLeft > 0 ? secondsLeft + '秒' : '已到时间'}`); } console.log('🚚 Carrier信息:'); console.log(` ID: ${orderInfo.carrier.id}`); console.log(` 名称: ${orderInfo.carrier.name}`); console.log(` 编号: ${orderInfo.carrier.code}`); console.log(` 公司拥有: ${orderInfo.carrier.corporationOwned}`); console.log('🎯 抢单状态:'); const hasAutoTask = autoGrabTasks.has(rowKey); const hasCountdown = countdownIntervals.has(rowKey); console.log(` 自动抢单任务: ${hasAutoTask ? '已设置' : '未设置'}`); console.log(` 倒计时任务: ${hasCountdown ? '运行中' : '未运行'}`); // 打印完整的orderInfo对象 console.log('📄 完整运单数据:'); console.log(JSON.stringify(orderInfo, null, 2)); } else { console.log('❌ 未找到运单数据'); console.log(` 运单ID: ${orderId}`); console.log(` 数据映射表大小: ${orderDataMap.size}`); console.log(' 可用运单ID列表:', Array.from(orderDataMap.keys())); } console.log('='.repeat(60)); // 在页面上也显示一个简单的提示 const toast = document.createElement('div'); toast.style.cssText = ` position: fixed; top: 20px; right: 20px; background: #1890ff; color: white; padding: 12px 20px; border-radius: 6px; z-index: 10000; font-size: 14px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); `; toast.textContent = `运单 ${orderInfo ? orderInfo.code : orderId} 信息已输出到控制台`; document.body.appendChild(toast); setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 3000); }); } } // 创建自动抢单状态指示器 function createStatusIndicator() { // 查找标签栏容器 const tabContainer = document.querySelector('.ant-radio-group'); if (!tabContainer) { console.warn('未找到标签栏容器,无法添加状态指示器'); return; } // 检查是否已经添加过 if (statusIndicator && statusIndicator.parentNode) { return; } // 创建状态指示器 statusIndicator = document.createElement('div'); statusIndicator.className = 'auto-grab-status-indicator'; statusIndicator.style.cssText = ` display: inline-flex; align-items: center; margin-left: 20px; padding: 4px 12px; background: #f0f0f0; border: 1px solid #d9d9d9; border-radius: 6px; cursor: pointer; font-size: 12px; color: #666; transition: all 0.2s; min-width: 80px; justify-content: center; `; // 初始内容 updateStatusIndicator(); // 添加点击事件 statusIndicator.addEventListener('click', showAutoGrabModal); // 添加悬停效果 statusIndicator.addEventListener('mouseenter', function () { this.style.backgroundColor = '#e6f7ff'; this.style.borderColor = '#91d5ff'; this.style.color = '#1890ff'; }); statusIndicator.addEventListener('mouseleave', function () { const count = autoGrabTasks.size; if (count > 0) { this.style.backgroundColor = '#fff2f0'; this.style.borderColor = '#ffccc7'; this.style.color = '#f5222d'; } else { this.style.backgroundColor = '#f0f0f0'; this.style.borderColor = '#d9d9d9'; this.style.color = '#666'; } }); // 添加到标签栏 tabContainer.parentNode.style.display = 'flex'; tabContainer.parentNode.style.alignItems = 'center'; tabContainer.parentNode.appendChild(statusIndicator); console.log('自动抢单状态指示器已添加'); } // 更新状态指示器显示 function updateStatusIndicator() { if (!statusIndicator) return; const count = autoGrabTasks.size; if (count === 0) { statusIndicator.innerHTML = ` <span style="margin-right: 4px;">⚪</span> <span>无自动抢单</span> `; statusIndicator.style.backgroundColor = '#f0f0f0'; statusIndicator.style.borderColor = '#d9d9d9'; statusIndicator.style.color = '#666'; statusIndicator.title = '当前没有运单在自动抢单'; } else { statusIndicator.innerHTML = ` <span style="margin-right: 4px;">🔄</span> <span>自动抢单: ${count}</span> `; statusIndicator.style.backgroundColor = '#fff2f0'; statusIndicator.style.borderColor = '#ffccc7'; statusIndicator.style.color = '#f5222d'; statusIndicator.title = `当前有 ${count} 个运单正在自动抢单,点击查看详情`; } } // 显示自动抢单详情弹窗 function showAutoGrabModal() { // 如果弹窗已存在则先移除 const existingModal = document.getElementById('auto-grab-modal'); if (existingModal) { existingModal.remove(); } // 创建遮罩层 const overlay = document.createElement('div'); overlay.id = 'auto-grab-modal'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.45); z-index: 9999; display: flex; justify-content: center; align-items: flex-start; padding-top: 100px; `; // 创建弹窗主体 const modal = document.createElement('div'); modal.style.cssText = ` background: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); max-width: 600px; width: 90%; max-height: 70vh; overflow: hidden; display: flex; flex-direction: column; `; // 创建标题栏 const header = document.createElement('div'); header.style.cssText = ` padding: 16px 24px; border-bottom: 1px solid #f0f0f0; display: flex; justify-content: space-between; align-items: center; background: #fafafa; `; const title = document.createElement('h3'); title.style.cssText = ` margin: 0; font-size: 16px; font-weight: 600; color: #262626; `; title.textContent = `正在自动抢单的运单 (${autoGrabTasks.size})`; const closeBtn = document.createElement('button'); closeBtn.style.cssText = ` background: none; border: none; font-size: 18px; cursor: pointer; padding: 4px; color: #999; transition: color 0.2s; `; closeBtn.innerHTML = '✕'; closeBtn.addEventListener('click', () => overlay.remove()); closeBtn.addEventListener('mouseenter', () => closeBtn.style.color = '#f5222d'); closeBtn.addEventListener('mouseleave', () => closeBtn.style.color = '#999'); header.appendChild(title); header.appendChild(closeBtn); // 创建内容区域 const content = document.createElement('div'); content.style.cssText = ` padding: 0; overflow-y: auto; flex: 1; `; if (autoGrabTasks.size === 0) { content.innerHTML = ` <div style="padding: 40px; text-align: center; color: #999;"> <div style="font-size: 48px; margin-bottom: 16px;">⚪</div> <div style="font-size: 16px;">当前没有运单在自动抢单</div> </div> `; } else { // 创建运单列表 const list = document.createElement('div'); list.style.cssText = 'padding: 0;'; autoGrabTasks.forEach((_, rowKey) => { const orderInfo = orderDataMap.get(parseInt(rowKey)); if (!orderInfo) return; const item = document.createElement('div'); item.style.cssText = ` padding: 16px 24px; border-bottom: 1px solid #f0f0f0; display: flex; justify-content: space-between; align-items: center; transition: background-color 0.2s; `; item.addEventListener('mouseenter', () => item.style.backgroundColor = '#f5f5f5'); item.addEventListener('mouseleave', () => item.style.backgroundColor = 'transparent'); const leftInfo = document.createElement('div'); leftInfo.style.cssText = 'flex: 1;'; // 计算剩余时间 const now = new Date().getTime(); let remainingText = ''; if (orderInfo.planAllocateTime) { const targetTime = new Date(orderInfo.planAllocateTime).getTime(); const remaining = targetTime - now; if (remaining > 0) { const minutes = Math.floor(remaining / (1000 * 60)); const seconds = Math.floor((remaining % (1000 * 60)) / 1000); remainingText = `${minutes}分${seconds}秒后执行`; } else { remainingText = '即将执行'; } } leftInfo.innerHTML = ` <div style="font-weight: 500; font-size: 14px; color: #262626; margin-bottom: 4px;"> ${orderInfo.code || rowKey} </div> <div style="font-size: 12px; color: #666; margin-bottom: 2px;"> 目的地: ${orderInfo.destination || '未知'} </div> <div style="font-size: 12px; color: #1890ff;"> ${remainingText} </div> `; const rightBtn = document.createElement('button'); rightBtn.style.cssText = ` background: #fff2f0; border: 1px solid #ffccc7; border-radius: 4px; padding: 4px 8px; font-size: 12px; color: #f5222d; cursor: pointer; transition: all 0.2s; `; rightBtn.textContent = '取消'; rightBtn.addEventListener('click', () => { // 取消自动抢单 if (autoGrabTasks.has(rowKey)) { clearTimeout(autoGrabTasks.get(rowKey)); autoGrabTasks.delete(rowKey); updateStatusIndicator(); console.log(`已取消运单 ${rowKey} 的自动抢单`); // 更新按钮状态 const autoGrabBtn = document.querySelector(`tr[data-row-key="${rowKey}"] .auto-grab-btn`); if (autoGrabBtn) { autoGrabBtn.textContent = '自动抢单'; autoGrabBtn.style.backgroundColor = '#fff'; autoGrabBtn.style.borderColor = '#d9d9d9'; autoGrabBtn.style.color = '#666'; } // 刷新弹窗内容 showAutoGrabModal(); } }); rightBtn.addEventListener('mouseenter', () => { rightBtn.style.backgroundColor = '#f5222d'; rightBtn.style.color = 'white'; }); rightBtn.addEventListener('mouseleave', () => { rightBtn.style.backgroundColor = '#fff2f0'; rightBtn.style.color = '#f5222d'; }); item.appendChild(leftInfo); item.appendChild(rightBtn); list.appendChild(item); }); content.appendChild(list); } // 点击遮罩层关闭弹窗 overlay.addEventListener('click', (e) => { if (e.target === overlay) { overlay.remove(); } }); // 组装弹窗 modal.appendChild(header); modal.appendChild(content); overlay.appendChild(modal); document.body.appendChild(overlay); } // 处理抢单按钮 function setupGrabButtons() { const buttons = document.querySelectorAll('.ant-btn-dangerous'); buttons.forEach(button => { if (!button.hasAttribute('data-grab-handler')) { button.setAttribute('data-grab-handler', 'true'); // 根据设置修改按钮文本 button.innerHTML = `<span>${settings.skipGrabConfirm ? '直接抢单' : '抢单'}</span>`; // 添加点击事件处理 button.addEventListener('click', function (event) { if (!settings.skipGrabConfirm) return; // 如果未开启跳过确认,不做任何处理 event.stopPropagation(); // 阻止事件冒泡 event.preventDefault(); // 阻止默认行为 // 获取运单行 const row = button.closest('tr[data-row-key]'); if (!row) return; const rowKey = row.getAttribute('data-row-key'); if (!rowKey) return; // 调用API直接抢单 console.log(`直接抢单:运单 ${rowKey}`); grabOrderByApi(parseInt(rowKey)).then(success => { if (success) { console.log(`运单 ${rowKey} API抢单成功`); // 可以在这里添加成功提示 } else { console.warn(`运单 ${rowKey} API抢单失败`); // 可以在这里添加失败提示 } }); }); } }); } // 观察DOM变化 function setupObserver() { const observer = new MutationObserver(function (mutations) { let shouldUpdate = false; let shouldUpdateGrabButtons = false; mutations.forEach(function (mutation) { if (mutation.type === 'childList') { // 检查是否有表格相关的变化 mutation.addedNodes.forEach(function (node) { if (node.nodeType === Node.ELEMENT_NODE) { if (node.matches && ( node.matches('tr[data-row-key]') || node.querySelector && node.querySelector('tr[data-row-key]') )) { shouldUpdate = true; } // 检查是否有抢单按钮相关的变化 if (node.matches && ( node.matches('.ant-btn-dangerous') || node.querySelector && node.querySelector('.ant-btn-dangerous') )) { shouldUpdateGrabButtons = true; } } }); } }); if (shouldUpdate && isOnGrabOrderTab()) { setTimeout(() => { processTableRows(); // 确保操作列宽度足够 ensureOperationColumnWidth(); // 处理调试点击功能 processDebugClickHandlers(); }, 100); } if (shouldUpdateGrabButtons && isOnGrabOrderTab()) { setTimeout(() => { setupGrabButtons(); }, 100); } }); observer.observe(document.body, { childList: true, subtree: true }); } // 监听页面标签切换 function setupTabListener() { document.addEventListener('click', function (e) { const target = e.target; if (target.matches('.ant-radio-button-input') || target.closest('.ant-radio-button-wrapper')) { setTimeout(() => { console.log('检测到标签切换,当前标签:', isOnGrabOrderTab() ? '抢单' : '其他'); if (isOnGrabOrderTab()) { processTableRows(); ensureOperationColumnWidth(); setupGrabButtons(); // 处理抢单按钮 processDebugClickHandlers(); // 处理调试点击功能 } else { // 切换到其他标签时清理所有任务 console.log('切换离开抢单标签,清理所有任务'); // 清理倒计时任务(支持双重倒计时) countdownIntervals.forEach((intervals, rowKey) => { if (typeof intervals === 'number') { // 旧格式:单个interval ID clearInterval(intervals); } else if (intervals && typeof intervals === 'object') { // 新格式:双重interval对象 if (intervals.local) clearInterval(intervals.local); if (intervals.ntp) clearInterval(intervals.ntp); } console.log(`清理运单 ${rowKey} 的倒计时任务`); }); countdownIntervals.clear(); // 清理自动抢单任务(支持双重任务) autoGrabTasks.forEach((tasks, rowKey) => { if (typeof tasks === 'number') { // 旧格式:单个timeout ID clearTimeout(tasks); } else if (tasks && typeof tasks === 'object') { // 新格式:双重timeout对象 if (tasks.local) clearTimeout(tasks.local); if (tasks.ntp) clearTimeout(tasks.ntp); } console.log(`清理运单 ${rowKey} 的自动抢单任务`); }); autoGrabTasks.clear(); updateStatusIndicator(); } }, 500); } }); } // 初始化脚本 function init() { console.log('运单自动抢单助手已加载'); // 加载设置 loadSettings(); // 等待页面完全加载 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { setTimeout(() => justDoIt(), 1000); }); } else { setTimeout(() => justDoIt(), 1000); } } function justDoIt() { createSettingsButton(); // 直接启动脚本功能 if (settings.showCountdown || settings.enableAutoGrab || settings.enableDestinationMatch) { start(); } // 初始化自动刷新功能 setupAutoRefresh(); } function start() { if (settings.showCountdown || settings.enableAutoGrab) { // 启动NTP时间同步 syncNtpTime(); createStatusIndicator(); // 创建状态指示器 } setupApiInterceptor(); // 设置API拦截器 setupObserver(); setupTabListener(); // 初始化目的地匹配面板 if (settings.enableDestinationMatch && destinationList.length > 0) { createMatchInfoPanel(); } // 如果当前就在抢单页面,立即处理 if (isOnGrabOrderTab()) { processTableRows(); ensureOperationColumnWidth(); setupGrabButtons(); // 处理抢单按钮 processDebugClickHandlers(); // 处理调试点击功能 } console.log('运单自动抢单助手启动完成'); console.log(`时间同步状态: ${isNtpSynced ? 'NTP已同步' : '使用本地时间'}`); } // 页面卸载时清理资源 window.addEventListener('beforeunload', function () { // 清理倒计时任务 countdownIntervals.forEach((intervals) => { if (typeof intervals === 'number') { clearInterval(intervals); } else if (intervals && typeof intervals === 'object') { if (intervals.local) clearInterval(intervals.local); if (intervals.ntp) clearInterval(intervals.ntp); } }); // 清理自动抢单任务 autoGrabTasks.forEach((tasks) => { if (typeof tasks === 'number') { clearTimeout(tasks); } else if (tasks && typeof tasks === 'object') { if (tasks.local) clearTimeout(tasks.local); if (tasks.ntp) clearTimeout(tasks.ntp); } }); }); // 监听API请求,拦截查询接口获取运单数据 function setupApiInterceptor() { const originalFetch = window.fetch; const originalXHR = XMLHttpRequest.prototype.open; // 拦截fetch请求 window.fetch = function (url) { const promise = originalFetch.apply(this, arguments); console.log('拦截到fetch请求:', url); // 检查是否是查询接口 if (url && url.includes('/api/data_query/query')) { promise.then(response => { console.log('拦截到查询接口请求:', url); if (response.ok) { // 克隆响应以避免消费原始流 return response.clone().json().then(data => { handleQueryResponse(data); }).catch(err => { console.error('解析查询接口响应失败:', err); }); } }).catch(err => { console.error('查询接口请求失败:', err); }); } return promise; }; // 拦截XMLHttpRequest请求 XMLHttpRequest.prototype.open = function (_, url) { console.log('拦截到XHR请求:', url); if (url && url.includes('/api/data_query/query')) { const xhr = this; xhr.addEventListener('load', function () { try { if (xhr.status === 200 && xhr.responseText) { const data = JSON.parse(xhr.responseText); handleQueryResponse(data); } } catch (err) { console.error('解析XHR查询接口响应失败:', err); } }); } return originalXHR.apply(this, arguments); }; console.log('API拦截器已设置'); } // 处理查询接口响应数据 function handleQueryResponse(data) { if (!data || !data.data || !Array.isArray(data.data.results)) { console.warn('查询接口响应数据格式不正确:', data); return; } console.log(`收到查询接口响应,运单数量: ${data.data.results.length}`); orderDataMap.clear(); // 从页面获取当前用户的carrier信息 let defaultCarrier = { corporationOwned: false, code: "000207", name: "鑫利物流30", id: 76 }; // 尝试从页面元素中获取carrier信息 try { const userInfoElement = document.querySelector('[data-carrier-info]'); if (userInfoElement) { const carrierInfo = JSON.parse(userInfoElement.getAttribute('data-carrier-info')); if (carrierInfo) { defaultCarrier = carrierInfo; } } } catch (e) { console.warn('无法从页面获取carrier信息,使用默认值'); } // 更新运单数据映射 data.data.results.forEach(order => { if (order.id && order.code) { // 解析备注信息 let note = order.customer.site.extJSON?.match(/\"xqbz\":\"([^\"]*)\"/)?.[1] || order.extJSON?.match(/\"xqbz\":\"([^\"]*)\"/)?.[1] || ''; const orderInfo = { id: order.id, name: order.name || '', // 运单名称 code: order.code, // 运单编号 planAllocateTime: order.planAllocateTime, // 可抢单时间 destinationName: order.destinationPoint.formatName || '', // 目的地名称 note: note, // 备注信息,从extJSON中的xqbz字段解析 // 使用默认carrier信息或从order中获取 carrier: order.carrier || defaultCarrier }; orderDataMap.set(order.id, orderInfo); console.log(`更新运单数据: ID=${orderInfo.id}, Code=${orderInfo.code}, 目的地=${orderInfo.destinationName}, 可抢单时间=${orderInfo.planAllocateTime}, 备注=${orderInfo.note}`); } }); console.log(`运单数据映射已更新,当前存储 ${orderDataMap.size} 个运单`); // 处理目的地匹配逻辑 processDestinationMatching(); // 数据更新后立即处理调试点击功能 if (isOnGrabOrderTab()) { processDebugClickHandlers(); } } // 处理目的地匹配逻辑 function processDestinationMatching() { // 如果未启用目的地匹配或没有配置目的地,直接返回 if (!settings.enableDestinationMatch || destinationList.length === 0) { matchedOrders.clear(); updateMatchInfoPanel(); return; } console.log('开始处理目的地匹配,配置的目的地:', destinationList); // 清空之前匹配的订单 matchedOrders.clear(); // 遍历所有订单进行匹配 orderDataMap.forEach(order => { if (!order.id || !order.destinationName) { return; } const destinationName = order.destinationName.trim(); // 检查是否匹配配置的目的地(支持模糊匹配) const matchedDestination = destinationList.find(configDestination => destinationName.includes(configDestination) || configDestination.includes(destinationName) ); if (matchedDestination) { // 判断备注是否符合条件 let shouldGrab = false; const orderNote = order.note.trim(); if (orderNote === '') { // 备注为空,直接抢单 shouldGrab = true; console.log(`发现匹配订单: ${order.code}, 目的地: ${destinationName}, 匹配配置: ${matchedDestination}, 备注为空,允许抢单`); } else { // 备注不为空,检查白名单和黑名单 if (noteConfig.mode === 'whitelist' && noteConfig.whitelist.length > 0) { // 白名单模式:检查是否命中白名单 const hitWhitelist = noteConfig.whitelist.some(keyword => orderNote.includes(keyword)); shouldGrab = hitWhitelist; console.log(`发现匹配订单: ${order.code}, 目的地: ${destinationName}, 匹配配置: ${matchedDestination}, 备注: "${orderNote}", 白名单模式: ${hitWhitelist ? '命中白名单,允许抢单' : '未命中白名单,不抢单'}`); } else if (noteConfig.mode === 'blacklist' && noteConfig.blacklist.length > 0) { // 黑名单模式:检查是否命中黑名单 const hitBlacklist = noteConfig.blacklist.some(keyword => orderNote.includes(keyword)); shouldGrab = !hitBlacklist; console.log(`发现匹配订单: ${order.code}, 目的地: ${destinationName}, 匹配配置: ${matchedDestination}, 备注: "${orderNote}", 黑名单模式: ${hitBlacklist ? '命中黑名单,不抢单' : '未命中黑名单,允许抢单'}`); } else { // 没有配置备注过滤规则,有备注的情况下不抢单(保持原逻辑) shouldGrab = false; console.log(`发现匹配订单: ${order.code}, 目的地: ${destinationName}, 匹配配置: ${matchedDestination}, 备注: "${orderNote}", 未配置备注过滤规则,不抢单`); } } if (shouldGrab) { // 创建匹配订单信息 const matchedOrderInfo = { ...order }; matchedOrders.set(order.id, matchedOrderInfo); // 检查是否到了抢单时间,如果是则立即抢单 if (order.planAllocateTime) { const grabTime = new Date(order.planAllocateTime * 1000); const now = new Date(); const timeDiff = grabTime.getTime() - now.getTime(); if (timeDiff <= 0) { // 时间已到,立即抢单 console.log(`匹配订单 ${order.code} 时间已到,立即执行抢单`); executeDestinationMatchGrab(order.id, matchedOrderInfo); } else { // 设置定时抢单 console.log(`匹配订单 ${order.code} 设置定时抢单,${Math.floor(timeDiff / 1000)}秒后执行`); setTimeout(() => { executeDestinationMatchGrab(order.id, matchedOrderInfo); }, timeDiff); } } else { executeDestinationMatchGrab(order.id, matchedOrderInfo); } } } }); console.log(`目的地匹配完成,匹配到 ${matchedOrders.size} 个订单`); // 更新浮动面板显示 updateMatchInfoPanel(); } // 执行目的地匹配抢单 async function executeDestinationMatchGrab(orderId, orderInfo) { // 检查订单是否还在匹配列表中(可能已被手动取消) if (!matchedOrders.has(orderId)) { console.log(`订单 ${orderId} 不在匹配列表中,跳过抢单`); return; } console.log(`开始执行目的地匹配抢单: ${orderInfo.code}`); // 更新状态为抢单中 updateMatchOrderStatus(orderId, 'grabbing'); try { const success = await grabOrderByApi(orderId); if (success) { console.log(`目的地匹配抢单成功: ${orderInfo.code}`); updateMatchOrderStatus(orderId, 'success'); // 显示成功提示 showMatchGrabToast(orderInfo, 'success'); // 成功后延迟移除(让用户看到成功状态) setTimeout(() => { matchedOrders.delete(orderId); updateMatchInfoPanel(); }, 3000); } else { console.log(`目的地匹配抢单失败: ${orderInfo.code}`); updateMatchOrderStatus(orderId, 'failed'); showMatchGrabToast(orderInfo, 'failed'); // 失败后也延迟移除 setTimeout(() => { matchedOrders.delete(orderId); updateMatchInfoPanel(); }, 5000); } } catch (error) { console.error(`目的地匹配抢单异常: ${orderInfo.code}`, error); updateMatchOrderStatus(orderId, 'failed'); showMatchGrabToast(orderInfo, 'error'); setTimeout(() => { matchedOrders.delete(orderId); updateMatchInfoPanel(); }, 5000); } } // 显示匹配抢单结果提示 function showMatchGrabToast(orderInfo, status) { const toast = document.createElement('div'); toast.style.cssText = ` position: fixed; top: 20px; right: 20px; min-width: 280px; padding: 12px 16px; border-radius: 6px; z-index: 10001; font-size: 14px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); animation: slideInFromRight 0.3s ease; `; // 根据状态设置样式和内容 if (status === 'success') { toast.style.background = '#f6ffed'; toast.style.border = '1px solid #b7eb8f'; toast.style.color = '#389e0d'; toast.innerHTML = ` <div style="font-weight: 500; margin-bottom: 4px;">✅ 匹配抢单成功</div> <div style="font-size: 12px; opacity: 0.8;">运单: ${orderInfo.code}</div> <div style="font-size: 12px; opacity: 0.8;">目的地: ${orderInfo.destinationName}</div> `; } else { toast.style.background = '#fff2f0'; toast.style.border = '1px solid #ffccc7'; toast.style.color = '#cf1322'; toast.innerHTML = ` <div style="font-weight: 500; margin-bottom: 4px;">❌ 匹配抢单失败</div> <div style="font-size: 12px; opacity: 0.8;">运单: ${orderInfo.code}</div> <div style="font-size: 12px; opacity: 0.8;">目的地: ${orderInfo.destinationName}</div> `; } // 添加动画样式 const style = document.createElement('style'); style.textContent = ` @keyframes slideInFromRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } `; if (!document.head.querySelector('style[data-match-toast]')) { style.setAttribute('data-match-toast', 'true'); document.head.appendChild(style); } document.body.appendChild(toast); // 自动移除提示 setTimeout(() => { if (toast.parentNode) { toast.style.animation = 'slideInFromRight 0.3s ease reverse'; setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 300); } }, status === 'success' ? 3000 : 5000); } // 直接调用抢单API async function grabOrderByApi(orderId) { const orderInfo = orderDataMap.get(orderId); if (!orderInfo) { console.error(`运单 ${orderId} 信息不存在,无法抢单`); return false; } const requestBody = { id: orderInfo.id, name: orderInfo.name, code: orderInfo.code, version: 4, // 固定值,根据API文档 carrier: orderInfo.carrier }; console.log(`开始API抢单,运单 ${orderId}:`, requestBody); try { const response = await fetch('/api/way_bill/dispatch/carrier/grab/order', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json;charset=UTF-8', 'Origin': window.location.origin, 'Referer': window.location.origin + '/', 'DNT': '1', 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-origin' }, body: JSON.stringify(requestBody), credentials: 'same-origin' // 包含cookies }); if (response.ok) { const result = await response.json(); if (result.status) { console.error(`运单 ${orderId} 抢单失败:`, result); return false; } else { console.log(`运单 ${orderId} 抢单成功!`); return true; } } else { console.error(`运单 ${orderId} 抢单请求失败,状态码:`, response.status); return false; } } catch (error) { console.error(`运单 ${orderId} 抢单API调用异常:`, error); return false; } } console.log('已添加测试函数 window.testSettings(),可在控制台运行测试'); // 启动脚本 init(); })();