// ==UserScript== // @name 运盟调度一键复制 // @namespace http://tampermonkey.net/ // @license MIT // @version 2026-02-05-04 // @description 支持运盟系统内车签号和运单号查询复制,方便运盟调度制作每日进港车辆表;支持一键复制当天所有高栏车信息,以便运盟调度制作每日高栏车雨布监控表。 // @author 圆通华北中心一期调度室 // @match https://diaodu.yonmen.com/* // @grant GM_xmlhttpRequest // @grant GM_setClipboard // ==/UserScript== (function() { 'use strict'; // 缓存对象 const cache = { token: null, tokenExpiry: 0, transportInfo: new Map(), detailInfo: new Map() }; // DOM元素缓存 let uiElements = null; // 提取车签号码的函数 function extractCarSignNumbers(inputText) { // 车签号码的正则表达式:以AQ开头,后跟12位数字 const carSignPattern = /AQ\d{12}/g; const matches = inputText.match(carSignPattern); if (!matches) { return []; } // 去重并保持原始顺序 const uniqueCarSigns = []; const seen = new Set(); for (const match of matches) { if (!seen.has(match)) { seen.add(match); uniqueCarSigns.push(match); } } return uniqueCarSigns; } // 等待页面加载完成 function waitForElement(selector, timeout = 5000) { return new Promise((resolve, reject) => { const element = document.querySelector(selector); if (element) { resolve(element); return; } const observer = new MutationObserver((mutations, obs) => { const element = document.querySelector(selector); if (element) { obs.disconnect(); resolve(element); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); reject(new Error('Element not found within timeout')); }, timeout); }); } // 创建UI界面 function createUI() { const container = document.createElement('div'); container.style.cssText = ` position: fixed; bottom: 20px; left: 20px; background: white; border: 1px solid #007bff; border-radius: 6px; padding: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); z-index: 10000; font-family: Arial, sans-serif; width: 200px; font-size: 12px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); cursor: move; user-select: none; `; container.innerHTML = `
运盟调度复制工具
`; document.body.appendChild(container); // 缓存UI元素 uiElements = { container, button: container.querySelector('#copyButton'), clearButton: container.querySelector('#clearButton'), extractButton: container.querySelector('#extractHighSidedButton'), input: container.querySelector('#queryNumberInput'), queryTypeSelect: container.querySelector('#queryTypeSelect'), toggleButton: container.querySelector('#toggleButton'), contentArea: container.querySelector('#contentArea'), titleBar: container.querySelector('#titleBar') }; // 添加拖拽移动功能 let isDragging = false; let dragOffset = { x: 0, y: 0 }; uiElements.titleBar.addEventListener('mousedown', function(e) { // 如果点击的是切换按钮,不启动拖拽 if (e.target === uiElements.toggleButton) return; isDragging = true; const rect = uiElements.container.getBoundingClientRect(); dragOffset.x = e.clientX - rect.left; dragOffset.y = e.clientY - rect.top; // 暂时禁用过渡动画,避免拖拽时的延迟 uiElements.container.style.transition = 'none'; // 添加拖拽时的视觉反馈 uiElements.container.style.boxShadow = '0 4px 16px rgba(0,0,0,0.2)'; uiElements.container.style.transform = 'scale(1.02)'; e.preventDefault(); }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; const newX = e.clientX - dragOffset.x; const newY = e.clientY - dragOffset.y; // 限制窗口不超出屏幕边界 const maxX = window.innerWidth - uiElements.container.offsetWidth; const maxY = window.innerHeight - uiElements.container.offsetHeight; const constrainedX = Math.max(0, Math.min(newX, maxX)); const constrainedY = Math.max(0, Math.min(newY, maxY)); uiElements.container.style.left = constrainedX + 'px'; uiElements.container.style.top = constrainedY + 'px'; uiElements.container.style.bottom = 'auto'; uiElements.container.style.right = 'auto'; }); document.addEventListener('mouseup', function() { if (isDragging) { isDragging = false; // 恢复过渡动画和正常样式 uiElements.container.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; uiElements.container.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)'; uiElements.container.style.transform = 'scale(1)'; } }); // 添加显示隐藏功能 let isCollapsed = false; uiElements.toggleButton.addEventListener('click', function() { isCollapsed = !isCollapsed; if (isCollapsed) { // 收起状态 uiElements.contentArea.style.height = '0'; uiElements.contentArea.style.opacity = '0'; uiElements.contentArea.style.overflow = 'hidden'; uiElements.toggleButton.textContent = '+'; uiElements.toggleButton.title = '展开'; uiElements.container.style.width = '160px'; } else { // 展开状态 uiElements.contentArea.style.height = 'auto'; uiElements.contentArea.style.opacity = '1'; uiElements.contentArea.style.overflow = 'visible'; uiElements.toggleButton.textContent = '−'; uiElements.toggleButton.title = '收起'; uiElements.container.style.width = '200px'; } }); // 添加切换按钮悬停效果 uiElements.toggleButton.addEventListener('mouseenter', () => { uiElements.toggleButton.style.background = '#f8f9fa'; uiElements.toggleButton.style.color = '#0056b3'; }); uiElements.toggleButton.addEventListener('mouseleave', () => { uiElements.toggleButton.style.background = 'none'; uiElements.toggleButton.style.color = '#007bff'; }); // 添加清空按钮点击事件监听 uiElements.clearButton.addEventListener('click', function() { uiElements.input.value = ''; uiElements.input.focus(); showStatus('输入框已清空'); // 1.5秒后隐藏提示(由showStatus函数自动处理) }); // 添加清空按钮悬停效果 uiElements.clearButton.addEventListener('mouseenter', () => { uiElements.clearButton.style.background = '#5a6268'; }); uiElements.clearButton.addEventListener('mouseleave', () => { uiElements.clearButton.style.background = '#6c757d'; }); // 添加查询类型变化事件监听 uiElements.queryTypeSelect.addEventListener('change', function() { const queryType = this.value; if (queryType === 'cq') { uiElements.input.placeholder = '输入车签号码(支持单个或批量)\n批量格式示例:\nAQ771001106620\n2025-10-20 20:35:33\n上传成功\n5\nAQ701901051444\n...'; uiElements.input.style.height = '80px'; } else if (queryType === 'trans') { uiElements.input.placeholder = '输入运单号码'; uiElements.input.style.height = '30px'; } uiElements.input.value = ''; // 清空输入框 }); // 添加按钮悬停效果 uiElements.button.addEventListener('mouseenter', () => { uiElements.button.style.background = '#0056b3'; }); uiElements.button.addEventListener('mouseleave', () => { uiElements.button.style.background = '#007bff'; }); // 添加高栏车按钮悬停效果 uiElements.extractButton.addEventListener('mouseenter', () => { uiElements.extractButton.style.background = '#218838'; }); uiElements.extractButton.addEventListener('mouseleave', () => { uiElements.extractButton.style.background = '#28a745'; }); return container; } // 页面顶部提示函数 function showTopNotification(message, isError = false) { // 移除已存在的提示 const existingNotification = document.getElementById('top-notification'); if (existingNotification) { existingNotification.remove(); } // 创建提示元素 const notification = document.createElement('div'); notification.id = 'top-notification'; notification.textContent = message; // 设置样式 notification.style.cssText = ` position: fixed; top: 0; left: 50%; transform: translateX(-50%); z-index: 999999; padding: 12px 24px; border-radius: 0 0 8px 8px; font-size: 14px; font-weight: 500; color: white; background: ${isError ? 'linear-gradient(135deg, #ff6b6b, #ee5a52)' : 'linear-gradient(135deg, #4CAF50, #45a049)'}; box-shadow: 0 4px 12px rgba(0,0,0,0.15); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); opacity: 0; transform: translateX(-50%) translateY(-100%); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 400px; text-align: center; word-wrap: break-word; `; // 添加到页面 document.body.appendChild(notification); // 动画显示 requestAnimationFrame(() => { notification.style.opacity = '1'; notification.style.transform = 'translateX(-50%) translateY(0)'; }); // 3秒后渐渐消失 setTimeout(() => { if (notification && notification.parentNode) { notification.style.opacity = '0'; notification.style.transform = 'translateX(-50%) translateY(-100%)'; // 动画结束后移除元素 setTimeout(() => { if (notification && notification.parentNode) { notification.remove(); } }, 300); } }, 3000); } // 保持原有的showStatus函数用于兼容性,但改为调用新的顶部提示 function showStatus(message, isError = false) { showTopNotification(message, isError); } // 获取当前页面的token(带缓存) function getToken() { // 检查缓存 if (cache.token && Date.now() < cache.tokenExpiry) { return cache.token; } let token = null; // 尝试从localStorage获取token token = localStorage.getItem('token') || sessionStorage.getItem('token'); if (token) { token = token.replace(/['"]/g, ''); if (token.length === 32) { cache.token = token; cache.tokenExpiry = Date.now() + 300000; // 缓存5分钟 return token; } } // 尝试从cookie获取token const cookies = document.cookie.split(';'); for (let cookie of cookies) { const [name, value] = cookie.trim().split('='); if (name === 'token' && value && value.length === 32) { cache.token = value; cache.tokenExpiry = Date.now() + 300000; return value; } } // 尝试从页面的全局变量获取token if (window.localStorage) { for (let i = 0; i < window.localStorage.length; i++) { const key = window.localStorage.key(i); const value = window.localStorage.getItem(key); if (key && key.toLowerCase().includes('token') && value && value.length === 32) { token = value.replace(/['"]/g, ''); cache.token = token; cache.tokenExpiry = Date.now() + 300000; return token; } } } // 默认token token = '96926dd287ca485aabff7e9300453918'; cache.token = token; cache.tokenExpiry = Date.now() + 300000; return token; } // 发送API请求获取运输信息(带缓存和超时控制,支持车签号和运单号查询) function fetchTransportInfo(queryNumber, token, queryType = 'cq') { // 检查缓存 const cacheKey = `${queryType}_${queryNumber}_${token}`; if (cache.transportInfo.has(cacheKey)) { return Promise.resolve(cache.transportInfo.get(cacheKey)); } return new Promise((resolve, reject) => { let url; if (queryType === 'cq') { // 车签号查询 url = `https://diaodu.yonmen.com/v1/transports/web?date_search_type=1&group_status=&trans_number=&cq_number=${queryNumber}&plate=&vehicle_type=&start_code=&start_name=&run_mode=&end_code=&end_name=&is_bind_driver=&safety_status=&page_index=1&page_size=10&token=${token}`; } else if (queryType === 'trans') { // 运单号查询 url = `https://diaodu.yonmen.com/v1/transports/web?date_search_type=1&group_status=&trans_number=${queryNumber}&cq_number=&plate=&vehicle_type=&start_code=&start_name=&run_mode=&end_code=&end_name=&is_bind_driver=&safety_status=&page_index=1&page_size=10&token=${token}`; } else { reject(new Error('不支持的查询类型')); return; } // 设置10秒超时 const timeoutId = setTimeout(() => { reject(new Error('请求超时')); }, 10000); GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, timeout: 10000, onload: function(response) { clearTimeout(timeoutId); try { const data = JSON.parse(response.responseText); if (data.code === 1000 && data.data && data.data.trans && data.data.trans.length > 0) { const result = data.data.trans[0]; // 缓存结果 cache.transportInfo.set(cacheKey, result); resolve(result); } else { reject(new Error(`未找到${queryType === 'cq' ? '车签号' : '运单号'}为 ${queryNumber} 的运输信息`)); } } catch (e) { reject(new Error('解析响应数据失败')); } }, onerror: function() { clearTimeout(timeoutId); reject(new Error('请求失败')); }, ontimeout: function() { clearTimeout(timeoutId); reject(new Error('请求超时')); } }); }); } // 获取详情页面信息 function fetchDetailInfo(transNumber, token) { // 检查缓存 const cacheKey = `${transNumber}_${token}`; if (cache.detailInfo.has(cacheKey)) { return Promise.resolve(cache.detailInfo.get(cacheKey)); } return new Promise((resolve, reject) => { const detailApiUrl = `https://diaodu.yonmen.com/v1/transports/detail?trans_number=${transNumber}&token=${token}`; // 设置10秒超时 const timeoutId = setTimeout(() => { // API超时后直接尝试iframe方式,但减少等待时间 fetchDetailInfoByIframe(transNumber, 8000).then(result => { cache.detailInfo.set(cacheKey, result); resolve(result); }).catch(reject); }, 10000); GM_xmlhttpRequest({ method: 'GET', url: detailApiUrl, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, timeout: 10000, onload: function(response) { clearTimeout(timeoutId); try { const data = JSON.parse(response.responseText); if (data.code === 1000 && data.data) { let yjTime = ''; let sjTime = ''; const nodes = data.data.nodes || []; for (let node of nodes) { if (node.address && node.address.includes('河北省廊坊市永清县工业园区小良村一纬道')) { yjTime = node.plan_time || node.expect_time || ''; sjTime = node.actual_time || node.reach_time || ''; break; } } if (!yjTime || !sjTime) { const detail = data.data; yjTime = yjTime || detail.plan_reach_time || detail.expect_reach_time || ''; sjTime = sjTime || detail.actual_reach_time || detail.reach_time || ''; } const result = { yjTime, sjTime }; cache.detailInfo.set(cacheKey, result); resolve(result); } else { // API返回错误,尝试iframe fetchDetailInfoByIframe(transNumber, 8000).then(result => { cache.detailInfo.set(cacheKey, result); resolve(result); }).catch(reject); } } catch (e) { // API解析失败,尝试iframe fetchDetailInfoByIframe(transNumber, 8000).then(result => { cache.detailInfo.set(cacheKey, result); resolve(result); }).catch(reject); } }, onerror: function() { clearTimeout(timeoutId); fetchDetailInfoByIframe(transNumber, 8000).then(result => { cache.detailInfo.set(cacheKey, result); resolve(result); }).catch(reject); }, ontimeout: function() { clearTimeout(timeoutId); fetchDetailInfoByIframe(transNumber, 8000).then(result => { cache.detailInfo.set(cacheKey, result); resolve(result); }).catch(reject); } }); }); } // 通过iframe获取详情页面信息(减少等待时间) function fetchDetailInfoByIframe(transNumber, maxTimeout = 8000) { return new Promise((resolve, reject) => { const detailUrl = `https://diaodu.yonmen.com/#/app/orderDetail?trans_number=${transNumber}`; const iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = detailUrl; let timeoutId = setTimeout(() => { if (iframe.parentNode) { document.body.removeChild(iframe); } reject(new Error('获取详情信息超时')); }, maxTimeout); let checkCount = 0; const maxChecks = 8; // 最多检查8次 const checkContent = () => { checkCount++; try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; const elements = iframeDoc.querySelectorAll('.formItem-text'); if (elements.length > 0) { let yjTime = ''; let sjTime = ''; let foundTarget = false; for (let element of elements) { const text = element.textContent || element.innerText; if (text.includes('河北省廊坊市永清县工业园区小良村一纬道')) { foundTarget = true; const parent = element.closest('.k-row'); if (parent) { const timeElements = parent.querySelectorAll('.formItem-text'); for (let timeEl of timeElements) { const timeText = timeEl.textContent || timeEl.innerText; if (timeText.includes('预计到车时间:')) { yjTime = timeText.replace('预计到车时间:', '').trim(); } if (timeText.includes('实际到车时间:')) { sjTime = timeText.replace('实际到车时间:', '').trim(); } } } break; } } if (foundTarget && (yjTime || sjTime)) { clearTimeout(timeoutId); if (iframe.parentNode) { document.body.removeChild(iframe); } resolve({ yjTime, sjTime }); return; } } // 如果还没找到且未超过最大检查次数,继续检查 if (checkCount < maxChecks) { setTimeout(checkContent, 1000); } else { clearTimeout(timeoutId); if (iframe.parentNode) { document.body.removeChild(iframe); } reject(new Error('未找到目标地址或时间信息')); } } catch (e) { if (checkCount < maxChecks) { setTimeout(checkContent, 1000); } else { clearTimeout(timeoutId); if (iframe.parentNode) { document.body.removeChild(iframe); } reject(new Error('获取详情页面信息失败: ' + e.message)); } } }; iframe.onload = function() { // 页面加载完成后等待1秒再开始检查 setTimeout(checkContent, 1000); }; iframe.onerror = function() { clearTimeout(timeoutId); if (iframe.parentNode) { document.body.removeChild(iframe); } reject(new Error('加载详情页面失败')); }; document.body.appendChild(iframe); }); } // 获取时间范围 function getDateRange() { const now = new Date(); const hour = now.getHours(); const minute = now.getMinutes(); const second = now.getSeconds(); let targetDate = new Date(now); // 如果现在时间为00:00:00-08:00:00,需改范围为昨天00:00:00-23:59:59 if (hour < 8 || (hour === 8 && minute === 0 && second === 0)) { targetDate.setDate(targetDate.getDate() - 1); } const year = targetDate.getFullYear(); const month = String(targetDate.getMonth() + 1).padStart(2, '0'); const day = String(targetDate.getDate()).padStart(2, '0'); const dateStr = `${year}-${month}-${day}`; return { startDate: `${dateStr} 00:00`, endDate: `${dateStr} 23:59` }; } // 提取高栏车信息 async function extractHighSidedTrucks() { const token = getToken(); if (!token) { showStatus('获取Token失败,请刷新页面重试', true); return; } showStatus('正在获取订单列表...'); const { startDate, endDate } = getDateRange(); // 处理日期格式,确保URL编码正确 const encodedStart = encodeURIComponent(startDate).replace(/%20/g, '+'); const encodedEnd = encodeURIComponent(endDate).replace(/%20/g, '+'); const listUrl = `https://dingdan.yonmen.com/v1/orders/list?page_index=1&page_size=100&start_date=${encodedStart}&end_date=${encodedEnd}&line_name=&group_type=all&carrier_code=&order_number=&audit_status=0&group_send_flag=all&origin_code=&destination_code=&trans_number=&plate=&safety_status=`; try { const listResponse = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: listUrl, headers: { 'token': token, 'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/json', 'Cookie': `token=${token}` }, onload: (res) => { try { const data = JSON.parse(res.responseText); // 兼容 111000 和 1000 状态码 if ((data.code === 111000 || data.code === 1000) && data.data && data.data.orders) { resolve(data.data.orders); } else { console.error('API Error Response:', data); reject(new Error(`获取订单列表失败 (Code: ${data.code}): ${data.msgParams || '未知错误'}`)); } } catch (e) { console.error('Parse Error:', e, res.responseText); reject(new Error('解析响应失败: ' + e.message)); } }, onerror: (err) => { console.error('Network Error:', err); reject(new Error('网络请求失败')); } }); }); if (listResponse.length === 0) { showStatus('未找到订单', true); return; } showStatus(`获取到 ${listResponse.length} 个订单,正在提取详情...`); const orderNumbers = listResponse.map(o => o.order_number); // 批量处理,限制并发 const processBatch = async (items, batchSize, fn) => { let index = 0; const results = []; while (index < items.length) { const batch = items.slice(index, index + batchSize); const batchPromises = batch.map(fn); const batchResults = await Promise.all(batchPromises); results.push(...batchResults); index += batchSize; showStatus(`正在提取详情... (${Math.min(index, items.length)}/${items.length})`); } return results; }; const fetchDetail = (orderNumber) => { return new Promise((resolve) => { const detailUrl = `https://dingdan.yonmen.com/v1/orders/detail?order_number=${orderNumber}`; GM_xmlhttpRequest({ method: 'GET', url: detailUrl, headers: { 'token': token, 'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/json', 'Cookie': `token=${token}` }, onload: (res) => { try { const data = JSON.parse(res.responseText); // 兼容 111000 和 1000 状态码 if ((data.code === 111000 || data.code === 1000) && data.data) { const detail = data.data; const extracted = []; const lineName = detail.line_name || ''; const carrierName = detail.carrier_name || ''; if (detail.carrier_send_vehicles) { detail.carrier_send_vehicles.forEach(group => { if (group.vehicles) { group.vehicles.forEach(vehicle => { // 匹配高栏车 if (vehicle.carriage_type === '高栏车') { extracted.push({ line_name: lineName, carrier_name: carrierName, plate: vehicle.plate, carriage_plate: vehicle.carriage_plate, vehicle_type: vehicle.vehicle_type, trans_number: vehicle.trans_number || '' }); } }); } }); } resolve(extracted); } else { resolve([]); } } catch (e) { resolve([]); } }, onerror: () => resolve([]) }); }); }; const fetchTransDetail = (transNumber) => { return new Promise((resolve) => { if (!transNumber) { resolve(null); return; } const transDetailUrl = `https://diaodu.yonmen.com/v1/transports/id?trans_number=${transNumber}&token=${token}`; GM_xmlhttpRequest({ method: 'GET', url: transDetailUrl, headers: { 'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/json', 'Cookie': `token=${token}` }, onload: (res) => { try { const data = JSON.parse(res.responseText); if (data.code === 1000 && data.data) { resolve(data.data); } else { resolve(null); } } catch (e) { resolve(null); } }, onerror: () => resolve(null) }); }); }; const details = await processBatch(orderNumbers, 5, fetchDetail); const allVehicles = details.flat(); if (allVehicles.length === 0) { showStatus('未找到高栏车信息', true); return; } showStatus(`正在获取 ${allVehicles.length} 个高栏车的详细运输信息...`); // 批量获取运输详情 const enrichedVehicles = []; for (let i = 0; i < allVehicles.length; i += 5) { const batch = allVehicles.slice(i, i + 5); const batchPromises = batch.map(async (vehicle) => { const transDetail = await fetchTransDetail(vehicle.trans_number); let cq_number = ''; let depart_time = ''; let relay_leave_time = ''; if (transDetail) { if (transDetail.status == 301) { cq_number = '已废止,请手动查询'; } else { cq_number = transDetail.cq_number || ''; } if (transDetail.locations && transDetail.locations.length > 0) { // 通常第一个 location 是出发地 const firstLoc = transDetail.locations[0]; depart_time = firstLoc.depart_time || ''; relay_leave_time = firstLoc.relay_leave_time || ''; } } // 计算逻辑 let regulationDuration = 0; // 规定时长(分钟) let rainClothTime = ''; // 盖雨布用时 let overtime = ''; // 超时 let isOvertime = '空'; // 是否超时 let overtimeReason = '空'; // 超时原因 // 1. 规定时长 const vehicleType = vehicle.vehicle_type || ''; if (vehicleType) { // 尝试匹配 17.5(xxx) 格式 const match = vehicleType.match(/17\.5\((\d+)\)/); if (match) { const width = parseInt(match[1], 10); if (width >= 210) { // 17.5(210) 及以上 -> 40分钟 regulationDuration = 40; } else if (width >= 160) { // 17.5(160) - 17.5(200) -> 35分钟 regulationDuration = 35; } else { // 17.5(150) 及以下 -> 30分钟 regulationDuration = 30; } } else { // 其他情况 if (vehicleType.includes('9.6') || vehicleType.includes('13.5') || vehicleType.includes('17.5')) { // 9.6, 13.5, 纯 17.5 -> 30分钟 regulationDuration = 30; } } } // 2. 盖雨布用时 let rainClothDuration = 0; if (depart_time && relay_leave_time) { const depart = new Date(depart_time).getTime(); const relayLeave = new Date(relay_leave_time).getTime(); if (!isNaN(depart) && !isNaN(relayLeave)) { const diffMs = relayLeave - depart; // 值为"relay_leave_time"-"depart_time",且为非负整数精确到分钟,为负则为0 rainClothDuration = Math.max(0, Math.floor(diffMs / (1000 * 60))); rainClothTime = rainClothDuration.toString(); } } // 3. 超时 & 4. 是否超时 & 5. 超时原因 if (rainClothTime !== '') { const overtimeVal = Math.max(0, rainClothDuration - regulationDuration); overtime = overtimeVal.toString(); if (overtimeVal > 0) { isOvertime = '是'; overtimeReason = '司机业务不熟练'; } else { isOvertime = '否'; overtimeReason = '无'; } } return { ...vehicle, cq_number, depart_time, relay_leave_time, regulationDuration, rainClothTime, overtime, isOvertime, overtimeReason }; }); const batchResults = await Promise.all(batchPromises); enrichedVehicles.push(...batchResults); showStatus(`正在获取详细运输信息... (${Math.min(i + 5, allVehicles.length)}/${allVehicles.length})`); } const clipboardText = enrichedVehicles.map(v => { return `${v.line_name}\t${v.carrier_name}\t${v.plate}\t${v.carriage_plate}\t${v.vehicle_type}\t${v.trans_number}\t${v.cq_number}\t${v.depart_time}\t${v.relay_leave_time}\t${v.regulationDuration || ''}\t${v.rainClothTime}\t${v.overtime}\t\t\t${v.isOvertime}\t${v.overtimeReason}`; }).join('\n'); GM_setClipboard(clipboardText); showStatus(`已复制 ${enrichedVehicles.length} 条高栏车信息到剪贴板`); } catch (error) { console.error(error); showStatus('提取失败: ' + error.message, true); } } // 初始化 function init() { waitForElement('body').then(() => { createUI(); uiElements.extractButton.addEventListener('click', extractHighSidedTrucks); uiElements.button.addEventListener('click', async function() { const inputValue = uiElements.input.value.trim(); const queryType = uiElements.queryTypeSelect.value; if (!inputValue) { showStatus(`请输入${queryType === 'cq' ? '车签号码' : '运单号码'}`, true); return; } // 禁用按钮防止重复点击 uiElements.button.disabled = true; uiElements.button.style.opacity = '0.6'; uiElements.button.textContent = '查询中...'; try { const token = getToken(); console.log('当前获取的token值:', token); if (queryType === 'cq') { // 车签号查询模式 - 自动检测单个或批量 const carSignNumbers = extractCarSignNumbers(inputValue); if (carSignNumbers.length === 0) { // 没有找到车签号码,可能是单个车签号码输入 const cleanedInput = inputValue.replace(/[^\w\-]/g, ''); if (cleanedInput.match(/^AQ\d{12}$/)) { // 单个车签号码 showStatus('正在获取运输信息...'); const transportInfo = await fetchTransportInfo(cleanedInput, token, 'cq'); const { trans_number, line_name, create_date, vehicle_type, cq_number } = transportInfo; showStatus('正在获取详情信息...'); const detailInfo = await fetchDetailInfo(trans_number, token); const { yjTime, sjTime } = detailInfo; const cqNumberForClipboard = cq_number || cleanedInput; const result = `${cqNumberForClipboard}\t${create_date}\t${line_name}\t${vehicle_type}\t${yjTime}\t${sjTime}`; GM_setClipboard(result); showStatus('复制成功!信息已保存到剪贴板'); } else { showStatus('未找到有效的车签号码', true); return; } } else if (carSignNumbers.length === 1) { // 单个车签号码 const carSign = carSignNumbers[0]; showStatus('正在获取运输信息...'); const transportInfo = await fetchTransportInfo(carSign, token, 'cq'); const { trans_number, line_name, create_date, vehicle_type, cq_number } = transportInfo; showStatus('正在获取详情信息...'); const detailInfo = await fetchDetailInfo(trans_number, token); const { yjTime, sjTime } = detailInfo; const cqNumberForClipboard = cq_number || carSign; const result = `${cqNumberForClipboard}\t${create_date}\t${line_name}\t${vehicle_type}\t${yjTime}\t${sjTime}`; GM_setClipboard(result); showStatus('复制成功!信息已保存到剪贴板'); } else { // 批量车签号码 showStatus(`找到 ${carSignNumbers.length} 个车签号码,正在查询...`); const results = []; let successCount = 0; let failCount = 0; // 按倒序处理(最后输入的先处理) const reversedCarSigns = [...carSignNumbers].reverse(); for (let i = 0; i < reversedCarSigns.length; i++) { const carSign = reversedCarSigns[i]; showStatus(`正在查询第 ${i + 1}/${reversedCarSigns.length} 个车签: ${carSign}`); try { const transportInfo = await fetchTransportInfo(carSign, token, 'cq'); const { trans_number, line_name, create_date, vehicle_type, cq_number } = transportInfo; const detailInfo = await fetchDetailInfo(trans_number, token); const { yjTime, sjTime } = detailInfo; const cqNumberForClipboard = cq_number || carSign; const result = `${cqNumberForClipboard}\t${create_date}\t${line_name}\t${vehicle_type}\t${yjTime}\t${sjTime}`; results.push(result); successCount++; } catch (error) { console.error(`查询车签 ${carSign} 失败:`, error); // 即使某个车签查询失败,也添加一个错误行 results.push(`${carSign}\t查询失败\t${error.message}\t\t\t`); failCount++; } } // 将所有结果合并为多行格式 const finalResult = results.join('\n'); GM_setClipboard(finalResult); showStatus(`批量查询完成!成功: ${successCount}, 失败: ${failCount}。结果已复制到剪贴板`); } } else if (queryType === 'trans') { // 运单号查询模式 const queryNumber = inputValue.replace(/[^\w\-]/g, ''); // 去除空格和特殊符号 showStatus('正在获取数据...'); showStatus('正在获取运输信息...'); const transportInfo = await fetchTransportInfo(queryNumber, token, queryType); console.log('获取到的运输信息:', transportInfo); const { trans_number, line_name, create_date, vehicle_type, cq_number } = transportInfo; showStatus('正在获取详情信息...'); const detailInfo = await fetchDetailInfo(trans_number, token); const { yjTime, sjTime } = detailInfo; const cqNumberForClipboard = cq_number || queryNumber; const result = `${cqNumberForClipboard}\t${create_date}\t${line_name}\t${vehicle_type}\t${yjTime}\t${sjTime}`; GM_setClipboard(result); showStatus('复制成功!信息已保存到剪贴板'); } } catch (error) { console.error('操作失败:', error); showStatus(`错误: ${error.message}`, true); } finally { // 恢复按钮状态 if (uiElements.button) { uiElements.button.disabled = false; uiElements.button.style.opacity = '1'; uiElements.button.textContent = '一键复制'; } } }); uiElements.input.addEventListener('keypress', (e) => { if (e.key === 'Enter') { uiElements.button.click(); } }); console.log('永门调度一键复制工具已加载(性能优化版)'); }).catch(error => { console.error('初始化失败:', error); }); } // 页面加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();