// ==UserScript==
// @name 京东订单发票批量获取
// @namespace http://tampermonkey.net/
// @version 0.2
// @description 获取京东我的订单页面中的所有 发票PDF链接
// @author Your name
// @license MIT
// @match https://order.jd.com/center/list.action*
// @connect jd.com
// @connect *
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant GM_notification
// @grant GM_setClipboard
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// 存储所有发票链接
let invoiceUrls = [];
// 添加样式
GM_addStyle(`
#downloadInvoiceBtn {
position: fixed;
right: 20px;
top: 20px;
padding: 10px 20px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
z-index: 9999;
}
#downloadInvoiceBtn:hover {
background-color: #1976D2;
}
.confirm-dialog {
position: fixed;
right: 20px;
top: 80px;
padding: 20px;
background-color: white;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 9999;
display: none;
}
.confirm-dialog button {
margin: 10px 5px 0;
padding: 5px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.confirm-dialog .confirm, .confirm-dialog .cancel {
background-color: #2196F3;
color: white;
}
.confirm-dialog .confirm:hover, .confirm-dialog .cancel:hover {
background-color: #1976D2;
}
#downloadProgress {
position: fixed;
right: 20px;
top: 80px;
padding: 20px;
background-color: white;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 9999;
display: none;
min-width: 300px;
}
`);
// 创建下载按钮
const downloadBtn = document.createElement('button');
downloadBtn.id = 'downloadInvoiceBtn';
downloadBtn.textContent = '获取发票链接';
document.body.appendChild(downloadBtn);
// 创建确认对话框
const confirmDialog = document.createElement('div');
confirmDialog.className = 'confirm-dialog';
confirmDialog.innerHTML = `
<p>开始获取所有发票链接,期间请不要进行任何操作,确定开始吗?</p>
<button class="confirm">确定</button>
<button class="cancel">取消</button>
`;
document.body.appendChild(confirmDialog);
// 创建进度提示
const progressDiv = document.createElement('div');
progressDiv.id = 'downloadProgress';
document.body.appendChild(progressDiv);
// 延时函数
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// 更新进度
function updateProgress(text) {
progressDiv.style.display = 'block';
progressDiv.innerHTML = `<div>${text}</div>`;
}
// 等待元素出现
function waitForElement(selectors, timeout = 30000) {
return new Promise((resolve, reject) => {
const startTime = Date.now();
function checkElement() {
// 尝试多个选择器
for (const selector of Array.isArray(selectors) ? selectors : [selectors]) {
const element = document.querySelector(selector);
if (element) {
resolve(element);
return;
}
}
if (Date.now() - startTime > timeout) {
reject(new Error(`等待元素超时: ${selectors}`));
} else {
setTimeout(checkElement, 500);
}
}
checkElement();
});
}
// 检查是否没有订单
function checkNoOrders() {
// 检查是否有空订单提示
const emptySelectors = [
'.empty-box',
'.order-empty',
'#order-list:empty',
'.order-list:empty',
'.orderList:empty',
'.tb-void'
];
if (emptySelectors.some(selector => document.querySelector(selector))) {
return true;
}
// 检查页面文本
if (document.body.textContent.includes('最近没有下过订单')) {
return true;
}
// 检查订单列表是否为空
const orderItems = getOrderItems();
return orderItems.length === 0;
}
// 等待页面加载完成
async function waitForPageLoad() {
const orderSelectors = [
'table.td-void', // 京东订单表格
'#tb-order', // 京东订单表格ID
'.order-tb', // 京东订单表格类
'.tr-th', // 京东订单表头
'#orderList', // 京东订单列表ID
'.order-list', // 订单列表类
'.order-tb tbody tr' // 订单行
];
try {
console.log('等待页面加载...');
// 首先等待任意一个选择器出现
await waitForElement(orderSelectors);
// 额外等待以确保内容完全加载
await sleep(3000);
// 再次检查是否有订单项
const items = getOrderItems();
if (items.length > 0) {
console.log('页面加载完成,找到', items.length, '个订单');
return true;
}
console.log('页面已加载,但未找到订单项');
return false;
} catch (error) {
console.error('页面加载超时:', error);
return false;
}
}
// 获取订单列表
function getOrderItems() {
// 直接使用京东订单页面的主要选择器
const items = Array.from(document.querySelectorAll('.order-tb tbody tr:not(.tr-th)')).filter(item => {
return !item.classList.contains('tr-th') &&
item.textContent.trim() !== '' &&
!item.classList.contains('thead');
});
console.log('找到订单数量:', items.length);
return items;
}
// 获取订单号
function getOrderNumber(orderItem) {
const numberElement = orderItem.querySelector('.number a');
if (numberElement) {
const text = numberElement.textContent.trim();
console.log('订单号文本:', text);
return text;
}
return '未知订单';
}
// 获取发票链接
async function getInvoiceUrl(href, orderNumber) {
try {
updateProgress(`正在获取订单 ${orderNumber} 的发票链接...`);
console.log('处理发票链接:', href);
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: href.startsWith('http') ? href : 'https:' + href,
headers: {
'Referer': 'https://order.jd.com/',
'User-Agent': navigator.userAgent,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
},
timeout: 30000,
onload: function(response) {
try {
console.log('获取到发票页面,正在解析...');
const parser = new DOMParser();
const doc = parser.parseFromString(response.responseText, 'text/html');
// 查找下载链接
const downloadLink = doc.querySelector('a.download-trigger.mr10');
if (downloadLink) {
const pdfUrl = downloadLink.href || downloadLink.getAttribute('href');
if (pdfUrl) {
console.log('找到PDF下载链接:', pdfUrl);
// 保存订单号和PDF链接
invoiceUrls.push({
orderNumber: orderNumber,
url: pdfUrl.startsWith('http') ? pdfUrl : 'https:' + pdfUrl
});
updateProgress(`已获取订单 ${orderNumber} 的发票链接`);
} else {
console.log('下载链接为空');
}
} else {
console.log('未找到下载链接元素');
// 输出页面内容以便调试
console.log('页面内容:', response.responseText.substring(0, 1000));
}
resolve();
} catch (error) {
console.error('解析发票页面失败:', error);
resolve();
}
},
onerror: function(error) {
console.error('请求失败:', error);
resolve();
}
});
});
} catch (error) {
console.error('获取发票链接出错:', error);
}
}
// 处理单个页面
async function processPage() {
try {
updateProgress('正在处理当前页面的订单...');
console.log('处理页面:', window.location.href);
// 等待3秒确保页面完全加载
await sleep(3000);
// 获取所有订单项
const orderItems = getOrderItems();
if (orderItems.length === 0) {
alert('未找到订单项');
return;
}
// 处理每个订单
for (const orderItem of orderItems) {
try {
const orderNumber = getOrderNumber(orderItem);
console.log('处理订单:', orderNumber);
// 查找发票链接
const links = orderItem.querySelectorAll('a');
let invoiceLink = null;
for (const link of links) {
const text = link.textContent.trim();
const href = link.getAttribute('href');
if (text.includes('发票') && href) {
console.log('找到发票页面链接:', href);
invoiceLink = href;
break;
}
}
if (invoiceLink) {
await getInvoiceUrl(invoiceLink, orderNumber);
// 每个请求之间等待2秒,避免请求过快
await sleep(2000);
} else {
console.log(`订单 ${orderNumber} 未找到发票链接`);
}
} catch (error) {
console.error('处理订单出错:', error);
continue;
}
}
// 生成CSV格式的内容
if (invoiceUrls.length > 0) {
const csvContent = ['订单号,发票PDF下载链接'];
invoiceUrls.forEach(item => {
csvContent.push(`${item.orderNumber},${item.url}`);
});
GM_setClipboard(csvContent.join('\n'));
alert(`成功获取 ${invoiceUrls.length} 个发票PDF下载链接!\n\n链接已复制到剪贴板。`);
} else {
alert('未找到任何发票下载链接,请确保订单中包含可下载的发票。');
}
} catch (error) {
console.error('处理页面失败:', error);
alert('处理过程中出现错误,请查看控制台获取详细信息。');
} finally {
progressDiv.style.display = 'none';
}
}
// 主函数
async function startCollection() {
invoiceUrls = []; // 清空链接列表
confirmDialog.style.display = 'none';
progressDiv.style.display = 'block';
await processPage();
}
// 事件监听
downloadBtn.addEventListener('click', () => {
confirmDialog.style.display = 'block';
});
confirmDialog.querySelector('.confirm').addEventListener('click', () => {
startCollection();
});
confirmDialog.querySelector('.cancel').addEventListener('click', () => {
confirmDialog.style.display = 'none';
});
})();