// ==UserScript== // @name 吉林成考报名全流程自动填写(Excel导入版) // @namespace https://crgk.jleea.com.cn/ // @version 3.5 // @description 会员授权后可导入“报考信息表.xlsx”自动填写全部5个页面;支持半自动/全自动模式,全自动自动下一步到确认页;出生日期等日期字段通过日期控件自动选择 // @match https://crgk.jleea.com.cn/pagepc.html* // @require https://cdn.bootcdn.net/ajax/libs/xlsx/0.18.5/xlsx.full.min.js // @grant none // @run-at document-idle // ==/UserScript== (function () { 'use strict'; // 只在主页面运行,不在 iframe 里重复创建 UI(表单页 iframe 的 URL 也匹配 @match) try { if (window.top !== window.self) return; } catch (e) { return; } // 防止脚本被重复加载(用户同时装了多个版本时只保留一个) if (window.__ckScriptLoaded) return; window.__ckScriptLoaded = true; /* ============ Excel 里没有的字段,在此补充填写 ============ */ var EXTRA = { qtlxfs: "", // 其他联系方式(必填,Excel无此列):如 微信号 / QQ / 邮箱 yzbm: "133000", // 邮政编码(必填,Excel无此列):延吉市=133000,请按实际地址修改 gzrq: "", // 何时参加工作(选填,Excel无此列):如 2008-09-01,不需要可留空 xzqhdm: "" // 报考报名点-考区(选填):如 延边;留空则自动从“确认点名称”推断 }; /* ===================================================== */ /* ============ 授权码校验(会员功能,支持时效性) ============ */ var AUTH_SALT = 'JL_CK_2026_SECRET_KEY'; // 密钥:生成和校验授权码共用,请勿泄露 var AUTH_STORAGE_KEY = 'jl_ck_auth_v3'; var AUTH_BASE_DATE = new Date(2026, 0, 1, 0, 0, 0); // 时间编码基准(2026-01-01 00:00) // 已停用授权码列表(卖家在生成器中点"停用"后,复制撤销列表粘贴到此处) var REVOKED_CODES = [ // 示例:'JL-XXXXXX-XXXXX-XXXX', ]; // 设备指纹:基于浏览器和硬件信息生成,用于绑定授权码到设备 function getDeviceFingerprint() { var parts = [ navigator.userAgent || '', (screen.width || 0) + 'x' + (screen.height || 0), screen.colorDepth || 0, navigator.language || '', new Date().getTimezoneOffset(), navigator.hardwareConcurrency || 0, navigator.platform || '', (navigator.deviceMemory || 0) ]; var str = parts.join('|'); var h = 0; for (var i = 0; i < str.length; i++) { h = ((h << 5) - h) + str.charCodeAt(i); h |= 0; } return Math.abs(h).toString(36).toUpperCase().padStart(6, '0'); } // 读取授权记录(兼容旧格式纯字符串) function getAuthRecord() { try { var raw = localStorage.getItem(AUTH_STORAGE_KEY); if (!raw) return null; try { var obj = JSON.parse(raw); if (obj && obj.code) return obj; } catch (e) {} // 旧格式:纯字符串,转为对象(无设备绑定) return { code: raw, device: null, activatedAt: null }; } catch (e) { return null; } } // 保存授权记录(绑定当前设备) function saveAuthRecord(code) { try { var record = { code: code.trim().toUpperCase(), device: getDeviceFingerprint(), activatedAt: Date.now() }; localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(record)); } catch (e) {} } // 检查设备是否匹配 function isDeviceMatch(record) { if (!record || !record.device) return true; // 旧格式无设备绑定,放行 return record.device === getDeviceFingerprint(); } // 简单哈希(非加密级,仅用于授权码校验) function authHash(str) { var h = 0; for (var i = 0; i < str.length; i++) { h = ((h << 5) - h) + str.charCodeAt(i); h |= 0; } return Math.abs(h).toString(36).toUpperCase().padStart(4, '0').slice(0, 4); } // 日期时间 -> 5位base36编码(从2026-01-01 00:00算起的分钟数,精确到分钟) function encodeDateTime(d) { var minutes = Math.floor((d.getTime() - AUTH_BASE_DATE.getTime()) / 60000); return minutes.toString(36).toUpperCase().padStart(5, '0'); } // 5位base36编码 -> 日期时间 function decodeDateTime(code) { var minutes = parseInt(code, 36); return new Date(AUTH_BASE_DATE.getTime() + minutes * 60000); } function formatDateTime(d) { function p(n) { return n < 10 ? '0' + n : '' + n; } return d.getFullYear() + '-' + p(d.getMonth()+1) + '-' + p(d.getDate()) + ' ' + p(d.getHours()) + ':' + p(d.getMinutes()); } // 校验授权码,返回 {valid, expired, expiry} // 新格式:JL-XXXXXX-DDD-YYYY(带时效,DDD为到期日编码) // 旧格式:JL-XXXXXX-YYYY(永久有效,兼容) function verifyAuthCode(code) { if (!code) return {valid: false, expired: false, expiry: null, revoked: false}; var s = String(code).trim().toUpperCase().replace(/\s/g, ''); // 检查是否在撤销列表中 if (REVOKED_CODES.indexOf(s) !== -1) { return {valid: false, expired: false, expiry: null, revoked: true}; } // 新格式(带时效,精确到分钟):JL-XXXXXX-MMMMM-YYYY var m = s.match(/^JL-([A-Z2-9]{6})-([A-Z0-9]{5})-([A-Z0-9]{4})$/); if (m) { var seed = m[1], timeCode = m[2], checksum = m[3]; if (authHash(seed + timeCode + AUTH_SALT) !== checksum) return {valid: false, expired: false, expiry: null}; var expiry = decodeDateTime(timeCode); var expired = new Date().getTime() > expiry.getTime(); return {valid: true, expired: expired, expiry: expiry}; } // 旧格式(永久有效) var m2 = s.match(/^JL-([A-Z2-9]{6})-([A-Z0-9]{4})$/); if (m2) { if (authHash(m2[1] + AUTH_SALT) !== m2[2]) return {valid: false, expired: false, expiry: null}; return {valid: true, expired: false, expiry: null}; } return {valid: false, expired: false, expiry: null}; } function isAuthorized() { try { var record = getAuthRecord(); if (!record || !record.code) return false; var r = verifyAuthCode(record.code); if (!r.valid || r.expired || r.revoked) return false; // 设备绑定检查 if (!isDeviceMatch(record)) return false; return true; } catch (e) { return false; } } function getAuthInfo() { try { var record = getAuthRecord(); if (!record || !record.code) return {valid: false, expired: false, expiry: null, deviceMismatch: false}; var r = verifyAuthCode(record.code); r.deviceMismatch = !isDeviceMatch(record); r.device = record.device; return r; } catch (e) { return {valid: false, expired: false, expiry: null, deviceMismatch: false}; } } // 生成授权码(expiryDays=0 表示永久;否则为有效天数) function generateAuthCode(expiry) { var chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; var seed = ''; for (var i = 0; i < 6; i++) seed += chars[Math.floor(Math.random() * chars.length)]; if (expiry && expiry instanceof Date) { var tc = encodeDateTime(expiry); return 'JL-' + seed + '-' + tc + '-' + authHash(seed + tc + AUTH_SALT); } if (expiry && typeof expiry === 'number' && expiry > 0) { var expDate = new Date(); expDate.setMinutes(expDate.getMinutes() + expiry); var tc2 = encodeDateTime(expDate); return 'JL-' + seed + '-' + tc2 + '-' + authHash(seed + tc2 + AUTH_SALT); } // 永久有效(旧格式) return 'JL-' + seed + '-' + authHash(seed + AUTH_SALT); } try { window.__ckGenCode = generateAuthCode; } catch (e) {} // 授权码输入弹窗 function showAuthDialog(onSuccess) { if (document.getElementById('ck-auth-mask')) return; var mask = document.createElement('div'); mask.id = 'ck-auth-mask'; mask.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.5);z-index:9999999;display:flex;align-items:center;justify-content:center;'; var box = document.createElement('div'); box.style.cssText = 'background:#fff;border-radius:12px;padding:28px 32px;width:340px;box-shadow:0 8px 32px rgba(0,0,0,.2);'; box.innerHTML = '
会员授权
' + '
导入报考信息表为会员功能,请输入授权码
' + '' + '' + '
' + '' + '' + '
'; mask.appendChild(box); document.body.appendChild(mask); var input = box.querySelector('#ck-auth-input'); input.focus(); input.select(); function close() { if (mask.parentNode) mask.parentNode.removeChild(mask); } box.querySelector('#ck-auth-cancel').onclick = close; mask.onclick = function (e) { if (e.target === mask) close(); }; function submit() { var code = input.value; var result = verifyAuthCode(code); if (result.revoked) { errEl.textContent = '该授权码已被停用,无法使用'; errEl.style.display = 'block'; return; } // 检查该授权码是否已在其他设备激活 var existingRecord = getAuthRecord(); if (existingRecord && existingRecord.code === code.trim().toUpperCase() && existingRecord.device && existingRecord.device !== getDeviceFingerprint()) { errEl.textContent = '该授权码已在其他设备激活,一个授权码只能在一台设备使用'; errEl.style.display = 'block'; return; } if (result.valid && !result.expired) { saveAuthRecord(code); close(); // 更新所有工具栏的授权标签(显示到期时间) var tags = document.querySelectorAll('#ck-toolbar > div'); var label = '已授权'; if (result.expiry) { label = '授权至' + formatDateTime(result.expiry); } for (var ti = 0; ti < tags.length; ti++) { if (tags[ti].textContent.indexOf('授权') !== -1) { tags[ti].textContent = label; tags[ti].style.color = '#07c160'; } } if (onSuccess) onSuccess(); } else if (result.valid && result.expired) { box.querySelector('#ck-auth-err').textContent = '授权码已过期,请联系卖家获取新授权码'; box.querySelector('#ck-auth-err').style.display = 'block'; input.select(); } else { box.querySelector('#ck-auth-err').style.display = 'block'; input.select(); } } box.querySelector('#ck-auth-ok').onclick = submit; input.onkeydown = function (e) { if (e.key === 'Enter') submit(); }; } var DATA = null; // Excel 解析出的数据对象 var $ = null, frameDoc = null; var filled = {}; // 已填充完成的页面 var lastPage = ''; var qrdTriggered = null; // 报考报名点:已触发加载的考区 var autoFillStopped = false; // 最后一页填充完成后停止自动填充 var qrdRetryCount = 0; // 报考报名点填充重试次数 var QRD_MAX_RETRY = 15; // 报考报名点最大重试次数(约9秒) // 全自动模式相关 var autoMode = (function(){ try { return localStorage.getItem('jl_ck_auto_mode') || 'semi'; } catch(e){ return 'semi'; } })(); // 'semi'半自动 | 'auto'全自动,自动记住上次选择 var autoNextRunning = false; // 防止重复点击下一步 function pad(n) { return n < 10 ? '0' + n : '' + n; } function log(m) { try { console.log('[吉林成考自动填写] ' + m); } catch (e) {} } /* ---------- 工具函数 ---------- */ // 19850110 / "1985-1-10" / "01/15/2020"(月日年) / Excel序列号 / Date -> "1985-01-10" function toDateStr(v) { if (v == null || v === '') return ''; // Date 对象 if (v instanceof Date && !isNaN(v)) return v.getFullYear() + '-' + pad(v.getMonth() + 1) + '-' + pad(v.getDate()); // 数字:Excel 日期序列号(1900系统,20000~80000 对应1954~2119年) if (typeof v === 'number' && isFinite(v) && v > 20000 && v < 80000) { var ed = new Date(Math.round((v - 25569) * 86400000)); if (!isNaN(ed)) return ed.getFullYear() + '-' + pad(ed.getMonth() + 1) + '-' + pad(ed.getDate()); } var s = String(v).trim(); // 年月日格式:2020-01-15 / 2020/01/15 / 2020年1月15日 var m = s.match(/^(\d{4})[-\/年.](\d{1,2})[-\/月.](\d{1,2})/); if (m) return pad(+m[1]) + '-' + pad(+m[2]) + '-' + pad(+m[3]); // 月日年格式:01/15/2020 / 1-15-2020 / 1.15.2020 var m2 = s.match(/^(\d{1,2})[-\/.](\d{1,2})[-\/.](\d{4})/); if (m2 && +m2[1] >= 1 && +m2[1] <= 12 && +m2[2] >= 1 && +m2[2] <= 31) { return m2[3] + '-' + pad(+m2[1]) + '-' + pad(+m2[2]); } // 纯数字8位:先判断 YYYYMMDD,再判断 MMDDYYYY var d = s.replace(/\D/g, ''); if (d.length >= 8) { var y = d.slice(0, 4); if (+y >= 1900 && +y <= 2099) return y + '-' + d.slice(4, 6) + '-' + d.slice(6, 8); if (d.length === 8) { var mm = d.slice(0, 2), dd = d.slice(2, 4), yy = d.slice(4, 8); if (+mm >= 1 && +mm <= 12 && +dd >= 1 && +dd <= 31 && +yy >= 1900 && +yy <= 2099) { return yy + '-' + mm + '-' + dd; } } } return ''; } function parseDate(s) { var ds = toDateStr(s); if (!ds) return null; var m = ds.match(/^(\d{4})-(\d{2})-(\d{2})/); var d = new Date(+m[1], +m[2] - 1, +m[3]); return isNaN(d.getTime()) ? null : d; } function fmtDate(d) { return d.getFullYear() + '年' + pad(d.getMonth() + 1) + '月' + pad(d.getDate()) + '日'; } function getFrame() { var f = document.getElementById('mainIframe'); if (!f || !f.contentDocument) return null; frameDoc = f.contentDocument; $ = (frameDoc.defaultView && frameDoc.defaultView.jQuery) || window.jQuery; return frameDoc; } function pageName() { var f = document.getElementById('mainIframe'); if (!f) return ''; var m = String(f.src).match(/[?&]page=([^&]+)/); return m ? m[1] : ''; } // 日期字段:直接写入中文格式 YYYY年MM月DD日,确保显示正确 function setDatePicker(id, val) { var d = parseDate(val); if (!d) return false; var $el = $('#' + id); if (!$el.length) return false; var formatted = fmtDate(d); $el.val(formatted); $el.trigger('input'); $el.trigger('change'); try { var dp = $el.data('datepicker') || $el.data('fdatepicker'); if (dp && dp.date) dp.date = d; if (dp && dp.viewDate) dp.viewDate = new Date(d.getTime()); } catch (e) {} setTimeout(function () { if ($el.val() !== formatted) { $el.val(formatted); $el.trigger('change'); } }, 100); return true; } // 下拉框按选项文字选值(先精确,后模糊),并触发 change function pick(sel, text) { var $s = $(sel); if (!$s.length || text === '' || text == null) return false; var hit = null; $s.find('option').each(function () { if (this.text.trim() === String(text).trim()) { hit = this.value; return false; } }); if (hit == null) $s.find('option').each(function () { if (this.text.indexOf(String(text)) !== -1) { hit = this.value; return false; } }); if (hit != null) { $s.val(hit); $s.trigger('change'); return true; } return false; } // 用页面自带的 ChineseDistricts 把 "吉林省延边朝鲜族自治州延吉市" 拆成 省/市/区 function splitAddr(addr) { if (!addr) return null; var s = String(addr).trim(); if (!frameDoc || !frameDoc.defaultView.ChineseDistricts) return null; var CD = frameDoc.defaultView.ChineseDistricts; if (s.indexOf('/') !== -1) { var p = s.split('/').filter(function (x) { return x; }); if (p.length >= 2) return p; } var province = '', pcode = '', city = '', ccode = '', district = ''; var groups = CD[86] || {}; outer: for (var g in groups) { var arr = groups[g] || []; for (var i = 0; i < arr.length; i++) { if (arr[i].address && s.indexOf(arr[i].address) === 0) { province = arr[i].address; pcode = arr[i].code; break outer; } } } if (!pcode) return null; var rest = s.slice(province.length); var cities = CD[pcode] || {}; for (var c in cities) { var cn = cities[c]; if (typeof cn === 'string' && cn && rest.indexOf(cn) === 0) { city = cn; ccode = c; break; } } var parts = [province]; if (ccode) { parts.push(city); var rest2 = rest.slice(city.length); var ds = CD[ccode] || {}; for (var dc in ds) { if (rest2.indexOf(ds[dc]) === 0) { district = ds[dc]; break; } } if (district) parts.push(district); } return parts; } // 录取通知书邮寄地址(city-picker):写值 + refresh 触发插件自身逻辑 function setCityPicker(id, addr) { var parts = splitAddr(addr); if (!parts) return false; $('#' + id).val(parts.join('/')); var cp = $('#' + id).data('citypicker'); if (cp) { cp.refresh(true); return true; } return false; } /* ============ 五个页面填充函数 ============ */ // 1. 基本信息 function fillInfo() { if (!DATA) return false; if (!$('#xm').length) return false; if (DATA.xm) $('#xm').val(DATA.xm); if (DATA.xbdm) { var v = String(DATA.xbdm).trim() === '女' ? '2' : '1'; $('input[name="xbdm"][value="' + v + '"]').prop('checked', true); $('label.checked').removeClass('checked'); $('input[name="xbdm"]:checked').parent().addClass('checked'); } if (DATA.csrq) setDatePicker('csrq', DATA.csrq); pick('#mzdm', DATA.mzdm); pick('#zzmmdm', DATA.zzmmdm); pick('#zylbdm', DATA.zylbdm); if (DATA.dw) $('#dw').val(DATA.dw); if (EXTRA.gzrq) setDatePicker('gzrq', EXTRA.gzrq); if (DATA.kjdm) { var kv = String(DATA.kjdm).trim() === '外省' ? '2' : '1'; $('#kjdm').val(kv); if (typeof frameDoc.defaultView.kjChange === 'function') frameDoc.defaultView.kjChange(); $('#kjdm').trigger('change'); } if (DATA.fzjg) $('#fzjg').val(DATA.fzjg); if (DATA.sbh || DATA.jzzh) $('#sbh').val(DATA.sbh || DATA.jzzh); if (DATA.hkdz) $('#hkdz').val(DATA.hkdz); if (DATA.sjhm) $('#sjhm').val(DATA.sjhm); return true; } // 2. 联系方式 function fillLxfs() { if (!DATA) return false; if (!$('#jtdz').length) return false; if (DATA.sjhm) $('#sjhm').val(DATA.sjhm); if (DATA.lxdh) $('#lxdh').val(DATA.lxdh); // 其他联系方式:有值用值,无值默认填"无"避免表单验证问题 var qt = EXTRA.qtlxfs || '无'; $('#qtlxfs').val(qt); if (DATA.yjdz) setCityPicker('yjdz', DATA.yjdz); if (DATA.jtdz) $('#jtdz').val(DATA.jtdz); if (EXTRA.yzbm) $('#yzbm').val(EXTRA.yzbm); return true; } // 3. 报考方向 function fillBkfx() { if (!DATA) return false; if (!$('#ccdm').length) return false; pick('#whcd', DATA.whcd); pick('#bklx', DATA.bklx); if (DATA.ccdm) pick('#ccdm', DATA.ccdm); // onchange -> ccdmChange -> 加载答卷文字/语种选项 + 显示毕业信息行 pick('#djwz', DATA.djwz); pick('#wyyzdm', DATA.wyyzdm); if (DATA.byxx) $('#byxx').val(DATA.byxx); if (DATA.byzy) $('#byzy').val(DATA.byzy); if (DATA.byrq) setDatePicker('byrq', DATA.byrq); if (DATA.byzh) { $('#byzh').val(DATA.byzh); if (typeof DATA.byzh === 'number' && /0+$/.test(String(DATA.byzh))) { log('警告:原毕业证书号来自数字单元格,可能存在精度丢失,请核对原件!当前读值:' + String(DATA.byzh)); } } return true; } // 4. 报考志愿 function fillBkzy() { if (!DATA) return false; if (!$('#yxdm1').length) return false; var w = frameDoc.defaultView; if (!w.MAP_YXXX || !w.MAP_ZYXX4) return false; // 等待映射表加载完成 // 层次名称显示栏(专升本/高起本/高起专) if (DATA.ccdm) { var ccn = (String(DATA.ccdm).indexOf('专') !== -1 && String(DATA.ccdm).indexOf('本') !== -1) ? '专升本' : (String(DATA.ccdm).indexOf('高起本') !== -1 ? '高起本' : '高起专'); $('#ccmc1').val(ccn); $('#ccmc2').val(ccn); $('#ccmc3').val(ccn); $('#ccmc4').val(ccn); } for (var n = 1; n <= 4; n++) { var yxdm = DATA['yxdm' + n], zydm = DATA['zydm' + n]; $('#yxdm' + n).val(yxdm || ''); $('#yxmc' + n).val((yxdm && w.MAP_YXXX[yxdm]) ? w.MAP_YXXX[yxdm] : ''); $('#zydm' + n).val(zydm || ''); $('#zymc' + n).val((yxdm && zydm && w.MAP_ZYXX4[yxdm + '_' + zydm]) ? w.MAP_ZYXX4[yxdm + '_' + zydm] : ''); } return true; } // 5. 报考报名点(考区 → 报名点联动) function guessXzqh(bmdName) { if (!bmdName) return null; var name = String(bmdName).trim(); // 地址格式先去掉"XX省"前缀,避免"吉林省"误匹配考区"吉林" var work = name; var pm = name.match(/^.+?省/); if (pm) work = name.slice(pm[0].length); var best = null, bestLen = 0; // 第1级:前缀匹配(如 "延边州招生考试服务中心" -> "延边","白城市镇赉县" -> "白城") $('#xzqhdm option').each(function () { if (this.value && work.indexOf(this.text) === 0 && this.text.length > bestLen) { best = this.value; bestLen = this.text.length; } }); if (best) return best; // 第2级:提取 "***市/州/地区/盟/县" 前缀再匹配 var m = work.match(/^(.+?)(市|州|地区|盟|县)/); if (m) { var city = m[1]; $('#xzqhdm option').each(function () { if (this.value && (this.text === city || city.indexOf(this.text) === 0 || this.text.indexOf(city) === 0)) { best = this.value; return false; } }); } if (best) return best; // 第3级:包含匹配 $('#xzqhdm option').each(function () { if (this.value && work.indexOf(this.text) !== -1 && this.text.length > bestLen) { best = this.value; bestLen = this.text.length; } }); return best; } // 灵活匹配报名点名称:完整名 -> 去市/州前缀 -> 包含匹配 function matchBmd(bmdName) { var name = String(bmdName).trim(); if (!name) return false; // 1. 完整名称匹配 if (pick('#bmddm', name)) return true; // 2. 去掉已选考区名前缀(如 "长春市朝阳区..." -> "朝阳区...") var xzqText = $('#xzqhdm option:selected').text(); if (xzqText) { var re = new RegExp('^' + xzqText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[市州地区盟]?'); var rest = name.replace(re, ''); if (rest && rest !== name && pick('#bmddm', rest)) return true; } // 3. 通用:去掉开头任意 "***市/州/地区/盟" 后匹配 var m = name.match(/^.+?[市州地区盟]/); if (m) { var rest2 = name.slice(m[0].length); if (rest2 && pick('#bmddm', rest2)) return true; } // 4. 包含匹配:选项文字是否出现在确认点名称中 var hit = null; $('#bmddm option').each(function () { if (this.value && this.text && name.indexOf(this.text) !== -1) { hit = this.value; return false; } }); if (hit) { $('#bmddm').val(hit); $('#bmddm').trigger('change'); return true; } return false; } function fillQrd() { if (!DATA) return false; if (!$('#bmddm').length) return false; var bmdName = DATA.bmddm; if (!bmdName) { autoFillStopped = true; return true; } // 报名点选项已加载 -> 灵活匹配名称 if (matchBmd(bmdName)) { autoFillStopped = true; // 最后一页填充成功,停止自动填充 log('报考报名点填充完成,已停止自动填充'); return true; } // 重试上限保护:避免反复触发加载导致页面卡死 qrdRetryCount++; if (qrdRetryCount > QRD_MAX_RETRY) { autoFillStopped = true; log('报考报名点重试超过' + QRD_MAX_RETRY + '次,已停止自动填充(可手动点一键填充重试)'); return false; } // 未加载:确保考区已选并触发 getBmdList var xzq = $('#xzqhdm').val(); if (!xzq) { var guess = EXTRA.xzqhdm || guessXzqh(bmdName); if (!guess) { guess = guessXzqh(DATA.yjdz) || guessXzqh(DATA.hkdz); } if (guess) { qrdTriggered = guess; if (!pick('#xzqhdm', guess)) { $('#xzqhdm').val(guess).trigger('change'); } } else { return false; } } else if (qrdTriggered !== xzq) { qrdTriggered = xzq; if (typeof frameDoc.defaultView.getBmdList === 'function') frameDoc.defaultView.getBmdList(); } return false; } var FILLERS = { stu_info: fillInfo, stu_lxfs: fillLxfs, stu_bkfx: fillBkfx, stu_bkzy: fillBkzy, stu_qrd: fillQrd }; function fillCurrent(force) { if (!force && autoFillStopped) return; // 已停止自动填充 if (!getFrame()) return; var name = pageName(); var fn = FILLERS[name]; if (!fn) return; if (!force && filled[name] && name !== 'stu_qrd') return; try { if (fn()) { filled[name] = true; log('已自动填充:' + name); autoNextIfNeeded(name); // 全自动模式自动下一步 } } catch (e) { log('填充异常:' + name + ' -> ' + e.message); } } // 查找并点击"下一步"按钮 function clickNextStep() { if (!getFrame()) return false; var btns = frameDoc.querySelectorAll('button, input[type="button"], input[type="submit"], a, .btn, [class*=next]'); for (var i = 0; i < btns.length; i++) { var txt = (btns[i].textContent || btns[i].value || '').trim(); if (txt === '下一步' || txt === '提交' || txt === '保存并下一步' || (txt.indexOf('下一步') !== -1 && txt.length < 10)) { btns[i].click(); log('已自动点击:' + txt); return true; } } log('未找到下一步按钮'); return false; } // 全自动模式:填充完成后自动下一步 var _autoNextStartTime = 0; function autoNextIfNeeded(pageName) { if (autoMode !== 'auto') return; // 超时保护:如果标志超过3秒未重置,强制重置(防止页面切换导致的死锁) if (autoNextRunning && Date.now() - _autoNextStartTime > 3000) { autoNextRunning = false; log('自动下一步状态超时,已重置'); } if (autoNextRunning) return; if (!DATA) return; autoNextRunning = true; _autoNextStartTime = Date.now(); log('全自动:页面[' + pageName + ']填充完成,准备自动下一步'); var delay = (pageName === 'stu_qrd') ? 1500 : 600; setTimeout(function () { if (pageName === 'stu_qrd') { autoFillStopped = true; log('最后一页填充完成,自动进入报名信息确认页'); } else { log('自动进入下一页'); } var clicked = clickNextStep(); if (!clicked) log('自动下一步失败:未找到按钮'); autoNextRunning = false; }, delay); } /* ============ Excel 导入与解析 ============ */ // 表头关键词 -> 字段名 var HEADER_MAP = [ ['姓名', 'xm'], ['性别', 'xbdm'], ['出生日期', 'csrq'], ['民族', 'mzdm'], ['政治面貌', 'zzmmdm'], ['职业类别', 'zylbdm'], ['工作单位', 'dw'], ['户籍', 'kjdm'], ['户口本住址', 'hkdz'], ['发证机关', 'fzjg'], ['居住证号', 'jzzh'], ['社保号', 'sbh'], ['电话1', 'sjhm'], ['电话2', 'lxdh'], ['手机号码', 'sjhm'], ['电话', 'sjhm'], ['录取通知书邮寄地址', 'yjdz'], ['详细地址', 'jtdz'], ['文化程度', 'whcd'], ['报考类别', 'bklx'], ['报考层次', 'ccdm'], ['答卷文字', 'djwz'], ['应试语种', 'wyyzdm'], ['原毕业学校', 'byxx'], ['原毕业专业', 'byzy'], ['原毕业日期', 'byrq'], ['原毕业证书号', 'byzh'], ['第一志愿院校代码', 'yxdm1'], ['第一志愿专业代码', 'zydm1'], ['第二志愿院校代码', 'yxdm2'], ['第二志愿专业代码', 'zydm2'], ['确认点', 'bmddm'], ]; function fieldForHeader(h) { h = String(h).replace(/\s/g, ''); for (var i = 0; i < HEADER_MAP.length; i++) { if (h.indexOf(HEADER_MAP[i][0]) !== -1) return HEADER_MAP[i][1]; } return ''; } function importData(headers, values) { var d = {}; for (var i = 0; i < headers.length; i++) { var f = fieldForHeader(headers[i]); if (f && values[i] !== '' && values[i] != null) d[f] = values[i]; } DATA = d; log('Excel 导入成功:' + Object.keys(d).length + ' 个字段'); filled = {}; autoFillStopped = false; qrdRetryCount = 0; autoNextRunning = false; fillCurrent(true); return d; } function importFile(file) { var reader = new FileReader(); reader.onload = function (ev) { try { if (typeof XLSX === 'undefined') { alert('xlsx 解析库未加载,请检查网络后刷新页面重试'); return; } var wb = XLSX.read(new Uint8Array(ev.target.result), { type: 'array' }); var ws = wb.Sheets[wb.SheetNames[0]]; var rows = XLSX.utils.sheet_to_json(ws, { header: 1, defval: '', raw: true }); if (rows.length < 2) { alert('文件中没有数据行'); return; } var headers = rows[0], values = rows[1]; importData(headers, values); alert('导入成功,已自动填充当前页。请逐个页面核对后再提交。'); } catch (e) { alert('解析失败:' + e.message); } }; reader.readAsArrayBuffer(file); } try { window.__ckImport = importFile; } catch (e) {} /* ============ 悬浮工具栏 ============ */ function setupUI() { // 清理旧版脚本的按钮和可能重复的工具栏 var oldBtn = document.getElementById('ck-afill-btn'); if (oldBtn && oldBtn.parentNode) oldBtn.parentNode.removeChild(oldBtn); var oldBars = document.querySelectorAll('#ck-toolbar'); for (var i = 0; i < oldBars.length; i++) { if (oldBars[i].parentNode) oldBars[i].parentNode.removeChild(oldBars[i]); } var bar = document.createElement('div'); bar.id = 'ck-toolbar'; bar.setAttribute('data-ck-main', '1'); bar.style.cssText = 'position:fixed;right:20px;bottom:150px;z-index:999999;display:flex;flex-direction:column;gap:8px;'; var mk = function (txt, color, cb) { var b = document.createElement('button'); b.textContent = txt; b.style.cssText = 'padding:10px 14px;border:none;border-radius:8px;font-size:13px;cursor:pointer;color:#fff;box-shadow:0 2px 8px rgba(0,0,0,.3);background:' + color + ';'; b.onclick = cb; return b; }; bar.appendChild(mk('导入报考信息表', '#1e6fff', function () { if (!isAuthorized()) { showAuthDialog(function () { // 授权成功后自动打开文件选择 if (typeof XLSX === 'undefined') { alert('正在加载 xlsx 解析库,请稍候再点,或刷新页面'); return; } var inp = document.createElement('input'); inp.type = 'file'; inp.accept = '.xlsx,.xls'; inp.style.display = 'none'; inp.onchange = function () { if (inp.files && inp.files[0]) importFile(inp.files[0]); }; document.body.appendChild(inp); inp.click(); }); return; } if (typeof XLSX === 'undefined') { alert('正在加载 xlsx 解析库,请稍候再点,或刷新页面'); return; } var inp = document.createElement('input'); inp.type = 'file'; inp.accept = '.xlsx,.xls'; inp.style.display = 'none'; inp.id = 'ck-file-input'; inp.onchange = function () { if (inp.files && inp.files[0]) importFile(inp.files[0]); }; document.body.appendChild(inp); inp.click(); })); // 授权状态小标签 var authInfo = getAuthInfo(); var authLabel = '未授权'; if (authInfo.valid && !authInfo.expired) { authLabel = authInfo.expiry ? '授权至' + formatDateTime(authInfo.expiry) : '已授权'; } else if (authInfo.valid && authInfo.expired) { authLabel = '授权已过期'; } var authTag = document.createElement('div'); authTag.style.cssText = 'font-size:11px;color:' + (isAuthorized() ? '#07c160' : (authInfo.expired ? '#e74c3c' : '#999')) + ';text-align:center;cursor:pointer;'; authTag.textContent = authLabel; authTag.title = '点击更换授权码'; authTag.onclick = function () { showAuthDialog(function () { authTag.textContent = '已授权'; authTag.style.color = '#07c160'; }); }; bar.appendChild(authTag); bar.appendChild(mk('一键填充当前页', '#07c160', function () { filled = {}; autoFillStopped = false; qrdRetryCount = 0; fillCurrent(true); })); // 模式选择区域 var modeBox = document.createElement('div'); modeBox.style.cssText = 'background:#fff;border-radius:8px;padding:10px 12px;box-shadow:0 2px 8px rgba(0,0,0,.2);font-size:12px;'; modeBox.innerHTML = '
运行模式
' + '' + ''; var radios = modeBox.querySelectorAll('input[name="ck-auto-mode"]'); for (var ri = 0; ri < radios.length; ri++) { radios[ri].onchange = function () { autoMode = this.value; try { localStorage.setItem('jl_ck_auto_mode', autoMode); } catch(e) {} log('运行模式切换为:' + (autoMode === 'auto' ? '全自动' : '半自动') + '(已记住)'); }; } bar.appendChild(modeBox); document.body.appendChild(bar); } /* ============ 主流程 ============ */ // 确保只有一组按钮:只保留带 data-ck-main 标记的工具栏,干掉其他脚本实例创建的重复按钮 function ensureUI() { var myBar = document.querySelector('#ck-toolbar[data-ck-main="1"]'); if (!myBar) { setupUI(); return; } // 我的工具栏被干掉了,重建 var bars = document.querySelectorAll('#ck-toolbar'); for (var i = 0; i < bars.length; i++) { if (bars[i] !== myBar && bars[i].parentNode) bars[i].parentNode.removeChild(bars[i]); } var oldBtn = document.getElementById('ck-afill-btn'); if (oldBtn && oldBtn.parentNode) oldBtn.parentNode.removeChild(oldBtn); } // 监听 iframe 加载完成,页面切换后自动填充 var _iframeWatched = false; function watchIframe() { if (_iframeWatched) return; var f = document.getElementById('mainIframe'); if (!f) return; _iframeWatched = true; f.addEventListener('load', function () { // 等新页面 DOM 和 JS 初始化完成 setTimeout(function () { if (DATA && !autoFillStopped) { filled = {}; qrdTriggered = null; qrdRetryCount = 0; autoNextRunning = false; lastPage = pageName(); fillCurrent(true); log('iframe加载完成,已自动填充:' + lastPage); } }, 1000); }); } function tick() { ensureUI(); watchIframe(); var f = document.getElementById('mainIframe'); if (!f || !f.contentDocument) return; var name = pageName(); if (name !== lastPage) { filled = {}; qrdTriggered = null; qrdRetryCount = 0; autoNextRunning = false; lastPage = name; } if (DATA) fillCurrent(false); } setupUI(); watchIframe(); setInterval(tick, 600); })();