// ==UserScript==
// @name 运盟调度一键复制
// @namespace http://tampermonkey.net/
// @license MIT
// @version 2025-10-15-02
// @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 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: 150px;
font-size: 12px;
`;
container.innerHTML = `
运盟调度复制工具
`;
document.body.appendChild(container);
// 缓存UI元素
uiElements = {
container,
button: container.querySelector('#copyButton'),
clearButton: container.querySelector('#clearButton'),
input: container.querySelector('#queryNumberInput'),
status: container.querySelector('#statusDiv'),
queryTypeSelect: container.querySelector('#queryTypeSelect')
};
// 添加清空按钮点击事件监听
uiElements.clearButton.addEventListener('click', function() {
uiElements.input.value = '';
uiElements.input.focus();
showStatus('输入框已清空', 'info');
// 清空按钮悬停效果
setTimeout(() => {
if (uiElements.status) {
uiElements.status.style.display = 'none';
}
}, 1500);
});
// 添加清空按钮悬停效果
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 = '输入车签号码';
} else if (queryType === 'trans') {
uiElements.input.placeholder = '输入运单号码';
}
uiElements.input.value = ''; // 清空输入框
});
// 添加按钮悬停效果
uiElements.button.addEventListener('mouseenter', () => {
uiElements.button.style.background = '#0056b3';
});
uiElements.button.addEventListener('mouseleave', () => {
uiElements.button.style.background = '#007bff';
});
return container;
}
// 显示状态信息(优化版)
function showStatus(message, isError = false) {
if (!uiElements) return;
const statusDiv = uiElements.status;
statusDiv.style.display = 'block';
statusDiv.style.background = isError ? '#f8d7da' : '#d4edda';
statusDiv.style.color = isError ? '#721c24' : '#155724';
statusDiv.style.border = isError ? '1px solid #f5c6cb' : '1px solid #c3e6cb';
statusDiv.textContent = message;
// 3秒后自动隐藏
setTimeout(() => {
if (statusDiv) statusDiv.style.display = 'none';
}, 3000);
}
// 获取当前页面的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 init() {
waitForElement('body').then(() => {
createUI();
uiElements.button.addEventListener('click', async function() {
const queryNumber = uiElements.input.value.trim().replace(/[^\w\-]/g, ''); // 去除空格和特殊符号,只保留字母、数字、下划线和连字符
const queryType = uiElements.queryTypeSelect.value;
if (!queryNumber) {
showStatus(`请输入${queryType === 'cq' ? '车签号码' : '运单号码'}`, true);
return;
}
// 禁用按钮防止重复点击
uiElements.button.disabled = true;
uiElements.button.style.opacity = '0.6';
uiElements.button.textContent = '查询中...';
try {
showStatus('正在获取数据...');
const token = getToken();
console.log('当前获取的token值:', token);
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; // 优先使用API返回的车签号,如果没有则使用查询号码
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();
}
})();