// ==UserScript== // @name 安徽中职学籍-批量打印学生基本信息表 // @namespace https://scriptcat.org/ // @version 1.3.0 // @description 在全国中等职业学校学生管理信息系统(安徽)批量打印学生基本信息表 // @author you // @match http://zzxsgl.ahjygl.gov.cn/* // @match https://zzxsgl.ahjygl.gov.cn/* // @match http://zzxsgl.ahjyt.gov.cn/* // @match https://zzxsgl.ahjyt.gov.cn/* // @grant none // @run-at document-idle // ==/UserScript== (function () { 'use strict'; const PRINT_PATH = 'zxs/zzZxsJbxxAction!showDetail.action?zzZxsJbxxQB.id={id}&isPrint=0'; let listDoc = null; let listBaseUrl = location.href; function linkHasXsqbId(link) { const text = (link.getAttribute('href') || '') + (link.getAttribute('onclick') || ''); return /xsqb\.id=([A-F0-9]+)/i.test(text); } function isListPageIn(doc) { if (!doc || !doc.body) return false; const hasNameHeader = [...doc.querySelectorAll('th, td')].some( (el) => el.textContent.trim() === '姓名' ); const hasStudentLink = [...doc.querySelectorAll('a')].some(linkHasXsqbId); return hasNameHeader && hasStudentLink; } /** 与 batch-print-students 相同思路:在当前 frame 判断是否列表页 */ function isListPageHere() { const u = location.href; if (/goStuDetail/i.test(u)) return false; if (/showDetail\.action/i.test(u) && /isPrint=0/i.test(u)) return false; if (/studInfoManage/i.test(u) && !/goStuDetail/i.test(u)) return true; if (isListPageIn(document)) return true; const bodyTxt = document.body?.innerText || ''; return ( /查询结果/.test(bodyTxt) || (/学籍号/.test(bodyTxt) && /姓名/.test(bodyTxt)) ); } function syncListContext() { listDoc = document; listBaseUrl = location.href; } const state = { running: false, stopRequested: false }; /** 独立打印窗口(顶层 print 才能配合 Edge --kiosk-printing) */ let printWin = null; function openPrintWindow() { if (printWin && !printWin.closed) return printWin; printWin = window.open( 'about:blank', 'bp_print_window', 'width=960,height=800,left=60,top=40' ); return printWin; } function closePrintWindow() { try { if (printWin && !printWin.closed) printWin.close(); } catch { /* ignore */ } printWin = null; } function waitForWindowReady(win, timeoutMs) { return new Promise((resolve, reject) => { const start = Date.now(); const tick = () => { try { if (win.closed) { reject(new Error('打印窗口被关闭')); return; } const doc = win.document; if (doc && doc.readyState === 'complete') { const bodyText = doc.body?.innerText || ''; if (/登录|session|超时/.test(bodyText.slice(0, 300))) { reject(new Error('登录已失效,请刷新后重试')); return; } resolve(win); return; } } catch { /* 导航中暂时不可访问 */ } if (Date.now() - start > timeoutMs) { reject(new Error('加载打印页超时')); return; } setTimeout(tick, 150); }; tick(); }); } /** 在独立顶层窗口打印(兼容 Edge kiosk-printing) */ function printInWindow(url, waitAfterPrintSec) { return new Promise(async (resolve, reject) => { const win = openPrintWindow(); if (!win) { reject(new Error('无法打开打印窗口,请允许本站弹窗')); return; } let done = false; const finish = (err) => { if (done) return; done = true; clearTimeout(printWaitTimer); err ? reject(err) : resolve(); }; const printWaitTimer = setTimeout( () => finish(new Error('等待打印超时(手动模式请点「打印」;全自动请检查 Edge 启动参数)')), Math.max(60, waitAfterPrintSec) * 1000 ); try { win.location.href = url; await waitForWindowReady(win, 35000); await sleep(1500); const onDone = () => { win.removeEventListener('afterprint', onDone); finish(); }; win.addEventListener('afterprint', onDone); win.focus(); win.print(); } catch (e) { finish(e); } }); } function buildPrintUrl(xsqbId) { const rel = PRINT_PATH.replace('{id}', xsqbId); return new URL(rel, listBaseUrl).href; } function parseStudentLink(link) { const text = (link.getAttribute('href') || '') + (link.getAttribute('onclick') || ''); const m = text.match(/xsqb\.id=([A-F0-9]+)/i); if (!m) return null; return { xsqbId: m[1], name: link.textContent.trim() }; } function collectJobs() { if (!listDoc) return []; const jobs = []; const seen = new Set(); const checkedRows = [...listDoc.querySelectorAll('table tr')].filter((tr) => { const cb = tr.querySelector('input[type="checkbox"]'); return cb && cb.checked && !cb.disabled; }); const rows = checkedRows.length ? checkedRows : [...listDoc.querySelectorAll('table tbody tr, table tr')]; for (const row of rows) { const link = [...row.querySelectorAll('a')].find(linkHasXsqbId); if (!link) continue; const info = parseStudentLink(link); if (!info || !info.name) continue; if (seen.has(info.xsqbId)) continue; seen.add(info.xsqbId); jobs.push({ name: info.name, xsqbId: info.xsqbId, printUrl: buildPrintUrl(info.xsqbId), }); } return jobs; } function log(msg) { const box = document.getElementById('bp-log'); if (!box) return; box.textContent += (box.textContent ? '\n' : '') + `[${new Date().toLocaleTimeString()}] ${msg}`; box.scrollTop = box.scrollHeight; } function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } async function runBatch() { if (state.running) return; syncListContext(); const jobs = collectJobs(); if (!jobs.length) { log('未找到学生。请先点「查询」出列表;勾选行则只打勾选的。'); return; } if (!openPrintWindow()) { log('无法打开打印窗口,请允许弹窗后重试'); return; } state.stopRequested = false; state.running = true; updateButtons(); const delaySec = Math.max(3, Number(document.getElementById('bp-delay').value) || 6); const delay = delaySec * 1000; const printWaitSec = Math.max(60, delaySec * 10); log(`共 ${jobs.length} 人,间隔 ${delaySec} 秒`); for (let i = 0; i < jobs.length; i++) { if (state.stopRequested) { log('已停止'); break; } const job = jobs[i]; log(`(${i + 1}/${jobs.length}) ${job.name}`); try { await printInWindow(job.printUrl, printWaitSec); log(` ✓ 已完成打印`); } catch (e) { log(` ✗ 失败:${e.message}`); } if (i < jobs.length - 1 && !state.stopRequested) await sleep(delay); } closePrintWindow(); if (!state.stopRequested) log('全部完成'); state.running = false; updateButtons(); } function updateButtons() { const start = document.getElementById('bp-start'); const stop = document.getElementById('bp-stop'); if (!start || !stop) return; start.disabled = state.running; stop.disabled = !state.running; } function createPanel() { if (document.getElementById('bp-panel')) return; const panel = document.createElement('div'); panel.id = 'bp-panel'; panel.style.cssText = [ 'position:fixed', 'right:16px', 'bottom:16px', 'z-index:2147483647', 'width:340px', 'background:#fff', 'border:1px solid #dcdfe6', 'border-radius:8px', 'box-shadow:0 4px 20px rgba(0,0,0,.15)', 'font:13px/1.5 "Microsoft YaHei",sans-serif', 'color:#303133', ].join(';'); panel.innerHTML = `
批量打印 · 学生基本信息表
全自动:Edge 目标加 --kiosk-printing --disable-print-preview
须先关掉所有 Edge,再用改过的快捷方式打开
`; document.documentElement.appendChild(panel); const head = panel.querySelector('.bp-head'); let sx = 0, sy = 0, sl = 0, st = 0, dragging = false; head.addEventListener('mousedown', (e) => { dragging = true; sx = e.clientX; sy = e.clientY; const r = panel.getBoundingClientRect(); sl = r.left; st = r.top; panel.style.right = panel.style.bottom = 'auto'; panel.style.left = sl + 'px'; panel.style.top = st + 'px'; }); document.addEventListener('mousemove', (e) => { if (!dragging) return; panel.style.left = sl + e.clientX - sx + 'px'; panel.style.top = st + e.clientY - sy + 'px'; }); document.addEventListener('mouseup', () => { dragging = false; }); panel.querySelector('#bp-start').addEventListener('click', () => { openPrintWindow(); runBatch(); }); panel.querySelector('#bp-stop').addEventListener('click', () => { state.stopRequested = true; closePrintWindow(); log('正在停止…'); }); log('脚本就绪。请先查询出学生列表。'); } function tryInit() { if (!isListPageHere()) return; syncListContext(); createPanel(); } tryInit(); const observer = new MutationObserver(() => tryInit()); observer.observe(document.documentElement, { childList: true, subtree: true }); })();