// ==UserScript== // @name 安徽中职学籍-批量打印学生基本信息表 // @namespace https://scriptcat.org/ // @version 1.2.0 // @description 在全国中等职业学校学生管理信息系统(安徽)批量打印学生基本信息表 // @author you // @match http://zzxsgl.ahjygl.gov.cn/* // @match https://zzxsgl.ahjygl.gov.cn/* // @grant GM_addStyle // @run-at document-idle // ==/UserScript== (function () { 'use strict'; const PRINT_PATH = 'zxs/zzZxsJbxxAction!showDetail.action?zzZxsJbxxQB.id={id}&isPrint=0'; function isListPage() { const hasNameHeader = [...document.querySelectorAll('th, td')].some( (el) => el.textContent.trim() === '姓名' ); const hasStudentLink = [...document.querySelectorAll('a')].some((a) => /xsqb\.id=/i.test(a.getAttribute('href') || '') ); return hasNameHeader && hasStudentLink; } if (!isListPage()) return; GM_addStyle(` #bp-panel { position: fixed; right: 16px; bottom: 16px; z-index: 999999; 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; } #bp-panel .bp-head { padding: 10px 12px; background: #409eff; color: #fff; border-radius: 8px 8px 0 0; cursor: move; user-select: none; } #bp-panel .bp-body { padding: 12px; } #bp-panel label { display: block; margin-bottom: 8px; } #bp-panel input[type="number"] { width: 72px; margin-left: 6px; padding: 2px 6px; border: 1px solid #dcdfe6; border-radius: 4px; } #bp-panel .bp-btns { display: flex; gap: 8px; margin-top: 10px; } #bp-panel button { flex: 1; padding: 8px 0; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; } #bp-panel .bp-start { background: #409eff; color: #fff; } #bp-panel .bp-stop { background: #f56c6c; color: #fff; } #bp-panel .bp-start:disabled, #bp-panel .bp-stop:disabled { opacity: .55; cursor: not-allowed; } #bp-panel .bp-log { margin-top: 10px; max-height: 160px; overflow: auto; padding: 8px; background: #f5f7fa; border-radius: 4px; font-size: 12px; white-space: pre-wrap; word-break: break-all; } #bp-panel .bp-tip { margin-top: 8px; font-size: 11px; color: #909399; } `); const state = { running: false, stopRequested: false }; function buildPrintUrl(xsqbId) { const rel = PRINT_PATH.replace('{id}', xsqbId); return new URL(rel, location.href).href; } function parseStudentLink(link) { const href = link.getAttribute('href') || ''; const m = href.match(/xsqb\.id=([A-F0-9]+)/i); if (!m) return null; return { xsqbId: m[1], name: link.textContent.trim() }; } function collectJobs() { const jobs = []; const seen = new Set(); const checkedRows = [...document.querySelectorAll('table tr')].filter((tr) => { const cb = tr.querySelector('input[type="checkbox"]'); return cb && cb.checked && !cb.disabled; }); const rows = checkedRows.length ? checkedRows : [...document.querySelectorAll('table tbody tr, table tr')]; for (const row of rows) { const link = row.querySelector('a[href*="xsqb.id="]'); 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.body.appendChild(iframe); iframe.src = url; }); } async function runBatch() { if (state.running) return; 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() { document.getElementById('bp-start').disabled = state.running; document.getElementById('bp-stop').disabled = !state.running; } function createPanel() { const panel = document.createElement('div'); panel.id = 'bp-panel'; panel.innerHTML = `
--kiosk-printing