// ==UserScript==
// @name JD内容列表批量删除助手-修复删除按钮
// @namespace http://tampermonkey.net/
// @version 1.7
// @match https://dr.jd.com/n/content-list.html*
// @grant none
// ==/UserScript==
(function () {
'use strict';
let running = false;
let stopFlag = false;
let doneCount = 0;
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function setStatus(text) {
const status = document.getElementById('jd-click-status');
if (status) status.innerText = text;
console.log('[JD删除助手]', text);
}
function realClick(el) {
if (!el) return false;
el.scrollIntoView({ block: 'center', inline: 'center' });
const rect = el.getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
['mouseover', 'mousedown', 'mouseup', 'click'].forEach(type => {
el.dispatchEvent(new MouseEvent(type, {
bubbles: true,
cancelable: true,
view: window,
clientX: x,
clientY: y
}));
});
return true;
}
async function waitFor(getter, timeout = 15000) {
const start = Date.now();
while (Date.now() - start < timeout) {
const el = getter();
if (el) return el;
await sleep(300);
}
return null;
}
function findDeleteButton() {
const items = [...document.querySelectorAll('span[class*="_manageAction_"]')];
return items.find(el => el.textContent.trim() === '删除');
}
function findConfirmButton() {
const popovers = [...document.querySelectorAll('.jd-popover')];
for (const popover of popovers) {
if (!popover.innerText.includes('确定删除吗')) continue;
const buttons = [...popover.querySelectorAll('button')];
const confirmBtn = buttons.find(btn =>
btn.textContent.trim() === '确定' ||
btn.classList.contains('jd-btn-primary')
);
if (confirmBtn) return confirmBtn;
}
return [...document.querySelectorAll('button')].find(btn =>
btn.textContent.trim() === '确定' &&
btn.className.includes('jd-btn-primary')
);
}
function getConfig() {
let times = parseInt(document.getElementById('jd-delete-times')?.value, 10);
let interval = parseInt(document.getElementById('jd-delete-interval')?.value, 10);
if (!times || times < 1) times = 1;
if (!interval || interval < 1000) interval = 2000;
return { times, interval };
}
async function deleteOnce(index, total) {
setStatus(`第 ${index}/${total} 次:查找删除按钮`);
const deleteBtn = await waitFor(findDeleteButton, 15000);
if (!deleteBtn) {
setStatus('删除按钮没找到');
return false;
}
realClick(deleteBtn);
setStatus(`第 ${index}/${total} 次:已点击删除`);
await sleep(500);
const confirmBtn = await waitFor(findConfirmButton, 15000);
if (!confirmBtn) {
setStatus('确定按钮没找到');
return false;
}
realClick(confirmBtn);
doneCount++;
setStatus(`第 ${index}/${total} 次:已点击确定`);
await sleep(800);
return true;
}
async function startLoop() {
if (running) {
setStatus('正在执行中');
return;
}
const { times, interval } = getConfig();
running = true;
stopFlag = false;
doneCount = 0;
setStatus(`开始执行:${times} 次,间隔 ${interval}ms`);
for (let i = 1; i <= times; i++) {
if (stopFlag) {
setStatus(`已停止,完成 ${doneCount} 次`);
break;
}
const ok = await deleteOnce(i, times);
if (!ok) {
setStatus(`第 ${i} 次失败,停止执行`);
break;
}
if (i < times) {
setStatus(`等待 ${interval}ms 后继续`);
await sleep(interval);
}
}
running = false;
if (!stopFlag) {
setStatus(`执行结束,共完成 ${doneCount} 次`);
}
}
async function runOnce() {
if (running) {
setStatus('正在执行中');
return;
}
running = true;
stopFlag = false;
doneCount = 0;
await deleteOnce(1, 1);
running = false;
setStatus(`单次执行完成:${doneCount} 次`);
}
function stopRun() {
stopFlag = true;
setStatus('正在停止,当前操作结束后停止');
}
function createUI() {
if (document.getElementById('jd-click-panel')) return;
const panel = document.createElement('div');
panel.id = 'jd-click-panel';
panel.style.cssText = `
position: fixed;
right: 20px;
bottom: 80px;
z-index: 999999999;
width: 210px;
background: #fff;
border: 1px solid #ddd;
border-radius: 10px;
padding: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,.25);
font-size: 14px;
`;
panel.innerHTML = `
<div style="font-weight:bold;text-align:center;margin-bottom:8px;">
JD批量删除助手
</div>
<div style="font-size:12px;">执行次数</div>
<input id="jd-delete-times" type="number" value="5" min="1"
style="width:100%;box-sizing:border-box;margin:4px 0 8px;padding:6px;border:1px solid #ccc;border-radius:5px;">
<div style="font-size:12px;">删除间隔 / 毫秒</div>
<input id="jd-delete-interval" type="number" value="2000" min="1000"
style="width:100%;box-sizing:border-box;margin:4px 0 8px;padding:6px;border:1px solid #ccc;border-radius:5px;">
<button id="jd-start-btn" style="width:100%;padding:8px;margin-bottom:6px;background:#e1251b;color:#fff;border:none;border-radius:6px;cursor:pointer;">
开始循环删除
</button>
<button id="jd-once-btn" style="width:100%;padding:8px;margin-bottom:6px;background:#333;color:#fff;border:none;border-radius:6px;cursor:pointer;">
执行一次
</button>
<button id="jd-stop-btn" style="width:100%;padding:8px;background:#999;color:#fff;border:none;border-radius:6px;cursor:pointer;">
停止
</button>
<div id="jd-click-status" style="margin-top:8px;font-size:12px;color:#666;text-align:center;">
等待执行
</div>
`;
document.body.appendChild(panel);
document.getElementById('jd-start-btn').onclick = startLoop;
document.getElementById('jd-once-btn').onclick = runOnce;
document.getElementById('jd-stop-btn').onclick = stopRun;
}
const initTimer = setInterval(() => {
if (document.body) {
createUI();
clearInterval(initTimer);
}
}, 500);
})();
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。