// ==UserScript==
// @name 内容营销三级单查询助手
// @namespace https://kol-edt.netease.com/
// @version 0.1.1
// @description 左下角输入6位单号 -> 调用接口1查 mpc_code_one -> 调用接口2取第一个 promotion id -> 直接跳转到详情页
// @match https://kol-edt.netease.com/*
// @run-at document-idle
// @grant GM_addStyle
// @grant GM_getClipboard
// ==/UserScript==
(() => {
'use strict';
/** ------------------ 工具 & 样式 ------------------ */
const css = `
.edt-helper-wrap{position:fixed;left:16px;bottom:16px;z-index:999999;
background:#fff;border:1px solid #e5e7eb;border-radius:10px;box-shadow:0 6px 16px rgba(0,0,0,.1);
padding:10px 12px;font:14px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial;}
.edt-helper-title{font-weight:600;margin-bottom:6px;}
.edt-helper-row{display:flex;align-items:center;gap:8px}
.edt-helper-input{width:160px;padding:6px 8px;border:1px solid #d1d5db;border-radius:8px;outline:none}
.edt-helper-input:focus{border-color:#2563eb;box-shadow:0 0 0 3px rgba(37,99,235,.15)}
.edt-helper-status{min-width:120px;color:#6b7280}
.edt-helper-badge{font-size:12px;padding:2px 6px;border-radius:6px;border:1px solid #e5e7eb;background:#f9fafb;color:#374151}
.edt-helper-ok{color:#059669}
.edt-helper-err{color:#dc2626}
.edt-helper-dim{opacity:.7}
.edt-helper-actions{margin-top:6px;display:flex;gap:8px}
.edt-helper-btn{padding:4px 8px;border:1px solid #e5e7eb;border-radius:8px;background:#f9fafb;cursor:pointer}
.edt-helper-btn:hover{background:#eef2ff;border-color:#c7d2fe}
`;
try { typeof GM_addStyle === 'function' ? GM_addStyle(css) : addStyle(css); }
catch { addStyle(css); }
function addStyle(text){
const s = document.createElement('style');
s.textContent = text;
document.head.appendChild(s);
}
/** ------------------ UI ------------------ */
const wrap = document.createElement('div');
wrap.className = 'edt-helper-wrap';
wrap.innerHTML = `
<div class="edt-helper-title">单号跳转助手</div>
<div class="edt-helper-row">
<span class="edt-helper-badge">三级单号:</span>
<input type="text" inputmode="numeric" pattern="\\d{6}" maxlength="6"
placeholder="输入三级单号"
class="edt-helper-input" id="edtHelperInput"/>
</div>
<div class="edt-helper-actions">
<div id="edtHelperStatus" class="edt-helper-status edt-helper-dim">待输入...</div>
<button id="edtHelperClear" class="edt-helper-btn" title="清空">清空</button>
<button id="edtHelperCollapse" class="edt-helper-btn" title="折叠/展开">折叠</button>
</div>
`;
document.body.appendChild(wrap);
const $input = wrap.querySelector('#edtHelperInput');
const $status = wrap.querySelector('#edtHelperStatus');
const $clear = wrap.querySelector('#edtHelperClear');
const $collapse = wrap.querySelector('#edtHelperCollapse');
// 判断是否为“恰好 6 位数字”(会剔除非数字字符)
function parseClipboardTo6(txt) {
const onlyDigits = String(txt || '').replace(/\D+/g, '');
return /^\d{6}$/.test(onlyDigits) ? onlyDigits : null;
}
// 展开面板并聚焦输入框
function ensurePanelOpenAndFocus() {
if (collapsed) {
collapsed = false;
wrap.style.height = '';
wrap.style.overflow = 'visible';
$collapse.textContent = '折叠';
}
setStatus('请在此输入 6 位单号', 'dim');
$input.focus();
$input.select?.();
}
// 用 6 位码直接触发查询
function runWithCode(code6) {
if (!/^\d{6}$/.test(code6)) return;
// 写入输入框,仅做展示;为了避免去重逻辑拦截,重置 lastQuery
$input.value = code6;
lastQuery = '';
setStatus(`检测到剪贴板单号:${code6},开始查询...`);
runFlow(code6).catch(err => {
console.error('[EDT Helper] Uncaught error:', err);
setStatus(`异常:${err?.message || err}`, 'err');
});
}
$clear.addEventListener('click', () => {
$input.value = '';
setStatus('已清空,待输入...', 'dim');
$input.focus();
});
let collapsed = false;
$collapse.addEventListener('click', () => {
collapsed = !collapsed;
wrap.style.height = collapsed ? '38px' : '';
wrap.style.overflow = collapsed ? 'hidden' : 'visible';
$collapse.textContent = collapsed ? '展开' : '折叠';
});
/** ------------------ 业务逻辑 ------------------ */
const API1 = '/demand/overview/total_price_dashboard/'; // POST
const API2 = '/promotions/'; // GET
const API3 = '/demand_live/overview/total_price_dashboard/'; // POST
const DETAIL_URL = (id) =>
`https://kol-edt.netease.com/admin_manager/supplier_manager/detail?promotionId=${encodeURIComponent(String(id))}&dept=1`;
let lastQuery = '';
let debounceTimer = null;
let runAbort = null;
$input.addEventListener('input', () => {
const v = ($input.value || '').trim();
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
if (/^\d{6}$/.test(v)) {
if (v === lastQuery) return;
lastQuery = v;
runFlow(v).catch(err => {
console.error('[EDT Helper] Uncaught error:', err);
setStatus(`异常:${err?.message || err}`, 'err');
});
} else {
setStatus('请输入 6 位数字单号', 'dim');
}
}, 350);
});
$input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
const v = ($input.value || '').trim();
if (/^\d{6}$/.test(v)) {
lastQuery = v;
runFlow(v).catch(err => {
console.error('[EDT Helper] Uncaught error:', err);
setStatus(`异常:${err?.message || err}`, 'err');
});
}
}
});
function setStatus(text, type = 'dim') {
$status.textContent = text;
$status.classList.remove('edt-helper-ok', 'edt-helper-err', 'edt-helper-dim');
if (type === 'ok') $status.classList.add('edt-helper-ok');
else if (type === 'err') $status.classList.add('edt-helper-err');
else $status.classList.add('edt-helper-dim');
}
async function queryFirstFrom(apiPath, code6, signal) {
const r = await fetch(apiPath, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/plain, */*',
'x-dept-id': '1',
},
body: JSON.stringify({
filters: [{
names: ['demand_title', 'demand_id', 'mpc3_id'], // 和你给的 cURL 一致
condition: 'contains',
filter_value: code6
}],
sort_cols: ['coop_time'],
sort_types: ['desc'],
current_page: 1,
page_size: 20
}),
signal
});
if (!r.ok) throw new Error(`请求失败(${r.status}):${apiPath}`);
const j = await r.json().catch(() => ({}));
return (Array.isArray(j?.data) && j.data.length ? j.data[0] : null);
}
async function runFlow(code6) {
// 取消上一次在途请求
if (runAbort) runAbort.abort();
runAbort = new AbortController();
setStatus(`查询中(单号:${code6})...`);
// 先查接口1;查不到则回退到接口3(直播库)
let first = await queryFirstFrom(API1, code6, runAbort.signal);
if (!first) {
setStatus('接口1未找到匹配数据,尝试直播库...', 'dim');
first = await queryFirstFrom(API3, code6, runAbort.signal);
if (!first) {
setStatus('接口1/接口3均未找到匹配数据', 'err');
return;
}
}
// 两个接口的返回都包含 mpc_code_one(你给的结构里有),直接共用
const mpcOne = first.mpc_code_one || '';
if (!mpcOne) {
setStatus('返回缺少 mpc_code_one', 'err');
return;
}
setStatus(`已获取一级单:${mpcOne},继续查询...`);
// —— 步骤二:接口2(GET)用一级单搜索,取第一个元素的 id ——
// 注意:参考你的 cURL,q 参数前会有一个 \t(%09),这里也加上,最大化兼容后端搜索逻辑
const qParam = '\t' + String(mpcOne).trim();
const url2 = new URL(API2, location.origin);
url2.searchParams.set('sort_col', 'id');
url2.searchParams.set('current_page', '1');
url2.searchParams.set('page_size', '20');
url2.searchParams.set('q', qParam);
const r2 = await fetch(url2.toString(), {
method: 'GET',
credentials: 'same-origin',
headers: {
'Accept': '*/*',
'x-dept-id': '1',
},
signal: runAbort.signal
});
if (!r2.ok) {
throw new Error(`接口2请求失败(${r2.status})`);
}
const j2 = await r2.json().catch(() => ({}));
const first2 = Array.isArray(j2?.data) && j2.data.length ? j2.data[0] : null;
if (!first2 || !first2.id) {
setStatus('接口2未找到匹配数据或缺少 id', 'err');
return;
}
const targetId = first2.id;
setStatus(`即将跳转到 ID=${targetId} 的详情页...`, 'ok');
// —— 步骤三:跳转详情页 ——
const target = DETAIL_URL(targetId);
// 立即跳转
window.location.assign(target);
}
})();
// 全站快捷键:按下“v”→ 读剪贴板 → 6 位则直查,否则展开输入框
document.addEventListener('keydown', async (e) => {
// 只处理裸按 v:不干扰 Ctrl/⌘/Alt 组合和正在输入的场景
if (e.isComposing || e.repeat) return;
const isKeyV = (e.key === 'v' || e.key === 'V' || e.code === 'KeyV');
if (!isKeyV || e.ctrlKey || e.metaKey || e.altKey) return;
// 若焦点在输入控件(含 contentEditable),不拦截,以免影响正常输入
const el = e.target;
const tag = (el && el.tagName || '').toLowerCase();
if (tag === 'input' || tag === 'textarea' || tag === 'select' || (el && el.isContentEditable)) return;
// 尝试读取剪贴板:优先 GM_getClipboard,失败回退到 navigator.clipboard
let clipText = '';
try {
if (typeof GM_getClipboard === 'function') {
clipText = GM_getClipboard() || '';
}
if (!clipText && navigator.clipboard?.readText) {
clipText = await navigator.clipboard.readText();
}
} catch (err) {
// 读取失败也不报错,转入输入模式
}
const code6 = parseClipboardTo6(clipText);
// 我们要接管该按键的行为(避免站内其他“v”快捷键冲突)
e.preventDefault();
e.stopPropagation();
if (code6) {
runWithCode(code6);
} else {
ensurePanelOpenAndFocus();
if (clipText) {
setStatus('剪贴板内容不是 6 位数字,请输入...', 'dim');
}
}
}, true); // capture=true,尽量早地接管