// ==UserScript== // @name 安徽中职学籍-批量打印学生基本信息表 // @namespace https://scriptcat.org/ // @version 1.2.4 // @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 }; 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)); } function printInIframe(url) { return new Promise((resolve, reject) => { const iframe = document.createElement('iframe'); iframe.style.cssText = 'position:fixed;left:-9999px;top:0;width:900px;height:900px;border:0;'; let done = false; const finish = (err) => { if (done) return; done = true; clearTimeout(timer); setTimeout(() => iframe.remove(), 2000); err ? reject(err) : resolve(); }; const timer = setTimeout(() => finish(new Error('加载打印页超时')), 35000); iframe.onload = () => { try { const win = iframe.contentWindow; const doc = iframe.contentDocument; if (!win || !doc) { finish(new Error('无法访问打印页,请确认仍在本系统且已登录')); return; } const bodyText = doc.body?.innerText || ''; if (/登录|session|超时|错误/i.test(bodyText.slice(0, 300))) { finish(new Error('登录已失效,请刷新页面重新登录')); return; } setTimeout(() => { try { win.focus(); win.print(); finish(); } catch (e) { finish(e); } }, 1500); } catch (e) { finish(e); } }; iframe.onerror = () => finish(new Error('iframe 加载失败')); document.documentElement.appendChild(iframe); iframe.src = url; }); } async function runBatch() { if (state.running) return; syncListContext(); const jobs = collectJobs(); state.stopRequested = false; state.running = true; updateButtons(); if (!jobs.length) { log('未找到学生。请先点「查询」出列表;勾选行则只打勾选的。'); state.running = false; updateButtons(); return; } const delay = Math.max(3000, Number(document.getElementById('bp-delay').value) || 6000); log(`共 ${jobs.length} 人,间隔 ${delay / 1000} 秒`); 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 printInIframe(job.printUrl); log(` ✓ 已唤起打印`); } catch (e) { log(` ✗ 失败:${e.message}`); } if (i < jobs.length - 1 && !state.stopRequested) await sleep(delay); } 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 = `
批量打印 · 学生基本信息表
面板在含「查询结果」表格的区域内(常在 iframe 内)
先点「查询」出学生;免弹窗加 --kiosk-printing
`; 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', runBatch); panel.querySelector('#bp-stop').addEventListener('click', () => { state.stopRequested = true; log('正在停止…'); }); log('脚本就绪。请先查询出学生列表。'); } function tryInit() { if (!isListPageHere()) return; syncListContext(); createPanel(); } tryInit(); const observer = new MutationObserver(() => tryInit()); observer.observe(document.documentElement, { childList: true, subtree: true }); })();