// ==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);
})();