// ==UserScript== // @name 青书学堂API刷课助手 By:王杰 // @namespace https://github.com/barryallennnnn // @version 1.0.3 // @description 青书学堂API刷课脚本,自动检测学校API地址 // @author lidppp // @match *://*.qingshuxuetang.com/* // @match *://*.qingshuxuetang.com.cn/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @connect * // @run-at document-idle // ==/UserScript== (function() { 'use strict'; const MAX_RETRY = 3; const MAX_POSITION = 1000; const REPORT_INTERVAL_MIN = 60000; // 最小60秒 const REPORT_INTERVAL_MAX = 90000; // 最大90秒 GM_addStyle(` #qs-panel{position:fixed;top:20px;right:20px;width:380px;background:linear-gradient(135deg,#667eea,#764ba2);border-radius:12px;box-shadow:0 10px 40px rgba(0,0,0,.3);z-index:99999;font-family:system-ui,sans-serif;color:#fff;overflow:hidden} .qs-hd{background:rgba(0,0,0,.2);padding:10px 14px;display:flex;justify-content:space-between;align-items:center;cursor:move} .qs-hd h3{margin:0;font-size:14px} .qs-hd button{background:none;border:none;color:#fff;font-size:16px;cursor:pointer} .qs-bd{padding:14px;max-height:500px;overflow-y:auto} .qs-tip{background:rgba(255,193,7,.2);border-left:3px solid #ffc107;padding:8px 10px;border-radius:0 6px 6px 0;margin-bottom:12px;font-size:11px;line-height:1.5} .qs-gp{margin-bottom:10px} .qs-gp label{display:block;margin-bottom:4px;font-size:12px;opacity:.9} .qs-gp input,.qs-gp select{width:100%;padding:8px 10px;border:none;border-radius:6px;background:rgba(255,255,255,.9);color:#000;font-size:13px;box-sizing:border-box} .qs-gp input:focus,.qs-gp select:focus{outline:none;background:rgba(255,255,255,.25)} .qs-gp select option{background:#333;color:#fff} .qs-row{display:flex;gap:8px} .qs-btn{flex:1;padding:8px;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer} .qs-btn-ok{background:#4caf50;color:#fff} .qs-btn-no{background:#f44336;color:#fff} .qs-btn-2{background:rgba(255,255,255,.2);color:#fff} .qs-btn:disabled{opacity:.5;cursor:not-allowed} .qs-stat{background:rgba(0,0,0,.15);border-radius:6px;padding:10px;margin:10px 0;font-size:12px} .qs-task{background:rgba(0,0,0,.15);border-radius:6px;padding:8px 10px;margin:6px 0;font-size:11px} .qs-task-ok{border-left:3px solid #4caf50} .qs-task-err{border-left:3px solid #f44336} .qs-task-run{border-left:3px solid #2196f3} .qs-log{background:rgba(0,0,0,.2);border-radius:6px;padding:8px;margin-top:10px;max-height:120px;overflow-y:auto;font-family:monospace;font-size:10px;line-height:1.5} .qs-log div{padding:1px 0} .qs-t{opacity:.5;margin-right:6px} .qs-i{color:#81d4fa} .qs-ok{color:#81c784} .qs-err{color:#e57373} .qs-w{color:#ffd54f} `); function log(msg, type) { type = type || 'i'; var el = document.getElementById('qs-log'); if (el) { var t = new Date().toLocaleTimeString(); el.innerHTML += '
[' + t + ']' + msg + '
'; el.scrollTop = el.scrollHeight; } console.log('[青书刷课] ' + msg); } function getCookie(name) { var m = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); return m ? m[2] : ''; } function httpGet(url, cb) { GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Accept': 'application/json' }, timeout: 15000, onload: function(r) { try { cb(null, JSON.parse(r.responseText)); } catch(e) { cb(null, null); } }, onerror: function(e) { cb(e || new Error('error')); }, ontimeout: function() { cb(new Error('timeout')); } }); } function getToken() { // 优先从输入框获取 var tokenEl = document.getElementById('qs-token'); if (tokenEl && tokenEl.value.trim()) { return tokenEl.value.trim(); } // 从cookie获取 var token = getCookie('AccessToken'); if (token) return token; // 从localStorage获取 try { token = localStorage.getItem('AccessToken') || localStorage.getItem('token') || ''; if (token) return token; } catch(e) {} // 尝试从页面脚本获取 try { var scripts = document.querySelectorAll('script'); for (var i = 0; i < scripts.length; i++) { var text = scripts[i].textContent || ''; var m = text.match(/token['":\s]*['"]([^'"]+)['"]/); if (m && m[1]) return m[1]; } } catch(e) {} return ''; } function getAllCookies() { return document.cookie || ''; } // 检查token是否过期 function isTokenExpired(token) { if (!token || !token.includes('.')) return false; try { var parts = token.split('.'); if (parts.length === 3) { var payload = JSON.parse(atob(parts[1])); if (payload.exp) { var expDate = new Date(payload.exp * 1000); var now = new Date(); return now > expDate; } } } catch(e) {} return false; } function httpPost(url, data, cb) { var token = getToken(); var allCookies = getAllCookies(); var headers = { 'Accept': 'application/json', 'Referer': window.location.href, 'User-Agent': navigator.userAgent }; // 使用完整cookie if (allCookies) { headers['Cookie'] = allCookies; } // 添加AccessToken if (token) { headers['Cookie'] = (headers['Cookie'] || '') + '; AccessToken=' + token; headers['Authorization'] = 'Bearer ' + token; headers['X-Access-Token'] = token; headers['token'] = token; } var opts = { method: 'POST', url: url, headers: headers, cookie: allCookies, timeout: 30000, onload: function(r) { if (r.status === 200) { try { var json = JSON.parse(r.responseText); cb(null, json); } catch(e) { log('JSON解析失败', 'err'); cb(null, { hr: -1, message: '解析失败' }); } } else if (r.status === 401) { log('认证失败(401),请刷新页面重新登录', 'err'); cb(null, { hr: -1, message: 'HTTP 401 - 认证失败' }); } else { log('HTTP错误: ' + r.status, 'err'); cb(null, { hr: -1, message: 'HTTP ' + r.status }); } }, onerror: function(e) { log('网络错误', 'err'); cb(e || new Error('error')); }, ontimeout: function() { log('请求超时', 'err'); cb(new Error('timeout')); } }; if (data instanceof FormData) { opts.data = data; } else { opts.headers['Content-Type'] = 'application/json'; opts.data = JSON.stringify(data); } GM_xmlhttpRequest(opts); } // 自动检测学校API - 从当前页面URL推断 function detectSchoolApi(cb) { var path = window.location.pathname; log('当前路径: ' + path, 'i'); // 从路径中提取学校代码,例如 /tykj/Student/Course/CourseStudy -> /tykj/Student var match = path.match(/^(\/[^\/]+\/[^\/]+)\//); if (match && match[1]) { var apiPath = match[1]; log('API路径: ' + apiPath, 'ok'); // 构造API地址 var apiBase = location.origin + apiPath; log('API地址: ' + apiBase, 'ok'); document.getElementById('qs-api').value = apiBase; cb(apiBase); } else { log('无法从路径推断API地址', 'w'); log('使用当前域名: ' + location.origin, 'i'); document.getElementById('qs-api').value = location.origin; cb(location.origin); } } // 测试API路径 function testApiPath(base, path, cb) { var url = base + path; log('测试: ' + url, 'i'); // 使用实际参数测试 var testParams = { contentId: 'test', contentType: 11, courseId: '959', teachPlanId: '558', periodId: '25', classId: '558' }; httpPost(url, testParams, function(err, data) { if (err) { log('失败: ' + (err.message || '网络错误'), 'err'); cb(false); } else { log('响应: ' + JSON.stringify(data), 'i'); cb(true, data); } }); } // 测试多个API路径 function findWorkingApiPath(base, cb) { var paths = [ '/Course/UploadStudyRecordBegin', '/Student/Course/UploadStudyRecordBegin', '/Course/CourseData', '/Student/Course/CourseData', '/Course/UploadStudyRecord', '/Student/Course/UploadStudyRecord', '/Course/StudyRecord', '/Student/Course/StudyRecord', '/Course/SaveStudyRecord', '/Student/Course/SaveStudyRecord', '/Course/UpdateStudyRecord', '/Student/Course/UpdateStudyRecord', '/UploadStudyRecordBegin', '/StudyRecordBegin', '/SaveStudyRecordBegin' ]; var index = 0; function testNext() { if (index >= paths.length) { log('所有路径都失败', 'err'); cb(null); return; } var path = paths[index]; index++; testApiPath(base, path, function(success, data) { if (success && data && data.hr === 0) { log('找到可用API: ' + base + path, 'ok'); cb(base + path); } else { testNext(); } }); } testNext(); } // 检测课程 function detectCourse() { if (!window.location.pathname.includes('CourseStudy')) return null; var u = new URL(window.location.href); var params = { courseId: u.searchParams.get('courseId') || '', teachPlanId: u.searchParams.get('teachPlanId') || '', periodId: u.searchParams.get('periodId') || '', classId: u.searchParams.get('classId') || u.searchParams.get('teachPlanId') || '' }; if (!params.courseId) return null; var nodes = []; // 扩展选择器,支持更多页面结构 var selectors = [ '.left_part .level-1 a[id]', '.course-tree a[href*="javascript"]', '.chapter-list a[href*="javascript"]', '.course-nav a[href*="javascript"]', '.catalog-list a', '.course-catalog a', 'a[href*="setContentId"]', 'a[onclick*="setContentId"]' ]; var links = []; for (var s = 0; s < selectors.length; s++) { var found = document.querySelectorAll(selectors[s]); if (found.length > 0) { links = found; log('使用选择器: ' + selectors[s] + ' (找到' + found.length + '个)', 'i'); break; } } for (var i = 0; i < links.length; i++) { var href = links[i].getAttribute('href') || links[i].getAttribute('onclick') || ''; var m = href.match(/.*\('([^']+)'.*/); if (!m || !m[1]) { // 尝试其他匹配模式 m = href.match(/contentId[=:]\s*['"]?([^'"&]+)/i); } if (m && m[1]) { // 多维度判断是否已学习 var isLearned = false; var parent = links[i].closest('li') || links[i].parentElement; // 检查各种可能的学习完成标记 if (links[i].querySelector('span.mark, span.learned, span.done, span.complete, i.icon-finish, .icon-complete')) { isLearned = true; } if (parent && (parent.classList.contains('learned') || parent.classList.contains('completed') || parent.classList.contains('done'))) { isLearned = true; } if (parent && parent.querySelector('span.mark, .learned-icon, .complete-icon, .finish-icon')) { isLearned = true; } // 检查文本标记 var text = links[i].textContent || ''; if (text.includes('已学') || text.includes('已完成') || text.includes('✓') || text.includes('√')) { isLearned = true; } nodes.push({ contentId: m[1], contentType: 11, courseId: params.courseId, teachPlanId: params.teachPlanId, periodId: params.periodId, classId: params.classId, learned: isLearned }); } } // 去重 var uniqueNodes = []; var seen = {}; for (var j = 0; j < nodes.length; j++) { if (!seen[nodes[j].contentId]) { seen[nodes[j].contentId] = true; uniqueNodes.push(nodes[j]); } } // 统计未学习的课件 var unlearned = uniqueNodes.filter(function(n) { return !n.learned; }); log('总课件: ' + uniqueNodes.length + ', 未学习: ' + unlearned.length, 'i'); return { params: params, nodes: unlearned, title: document.title }; } // 任务类 function Task(base, params, onUpdate) { this.base = base; this.params = params; this.onUpdate = onUpdate; this.recordId = -1; this.pos = 0; this.retry = 0; this.timer = null; this.done = false; } Task.prototype.begin = function() { if (this.done) return; this.retry++; if (this.retry > MAX_RETRY) { this.onUpdate('fail', '重试次数用尽'); return; } var self = this; var url = this.base + '/Course/UploadStudyRecordBegin'; log('POST ' + url, 'i'); log('参数: ' + JSON.stringify(this.params), 'i'); this.onUpdate('begin', '第' + this.retry + '次获取...'); httpPost(url, this.params, function(err, data) { if (self.done) return; if (err) { log('请求失败: ' + (err.message || ''), 'err'); setTimeout(function() { self.begin(); }, 5000); return; } log('响应: ' + JSON.stringify(data), 'i'); if (data.hr === 0) { self.recordId = data.data; log('recordId=' + self.recordId, 'ok'); self.retry = 0; // 重置重试计数 self.onUpdate('ready'); // 随机延迟20-40秒后开始上报(缩短等待时间) var delay = Math.floor(Math.random() * 20000) + 20000; log('等待 ' + Math.round(delay/1000) + ' 秒后开始上报', 'i'); setTimeout(function() { self.run(); }, delay); } else if (data.message && data.message.includes('401')) { log('认证失败,请检查Token', 'err'); self.onUpdate('fail', '认证失败'); } else { log('失败: hr=' + data.hr + ' msg=' + (data.message || ''), 'err'); // 如果是其他错误,等待更长时间后重试 var retryDelay = self.retry * 3000; setTimeout(function() { self.begin(); }, retryDelay); } }); }; Task.prototype.run = function() { if (this.done) return; this.onUpdate('run'); this.report(); }; Task.prototype.report = function() { if (this.done) return; var self = this; // 每次增加80-150,加快完成速度 this.pos += Math.floor(Math.random() * 71) + 80; if (this.pos >= MAX_POSITION) { this.pos = MAX_POSITION; // 确保精确到1000 log('进度完成', 'ok'); this.end(); return; } var url = this.base + '/Course/UploadStudyRecordContinue?_t=' + Date.now(); log('上报: recordId=' + this.recordId + ' pos=' + this.pos, 'i'); // 使用FormData格式,与原始代码一致 var fd = new FormData(); fd.append('recordId', this.recordId); fd.append('end', 'false'); fd.append('position', this.pos); fd.append('timeOutConfirm', 'false'); httpPost(url, fd, function(err, resp) { if (self.done) return; if (err) { log('上报错误: ' + (err.message || ''), 'err'); } else { log('上报响应: ' + JSON.stringify(resp), 'i'); if (resp.hr === 0) { log('进度 ' + self.pos + '/' + MAX_POSITION, 'ok'); } else { log('上报失败: ' + (resp.message || 'hr=' + resp.hr), 'err'); // 如果失败,减少进度避免过快增长 self.pos = Math.max(0, self.pos - 30); // 如果是频繁请求,等待更长时间 if (resp.message && resp.message.includes('频繁')) { log('检测到频繁请求,等待2分钟后重试', 'w'); self.onUpdate('run', self.pos); self.timer = setTimeout(function() { self.report(); }, 120000); return; } } } self.onUpdate('run', self.pos); if (!self.done) { // 随机等待60-90秒后再次上报 var interval = Math.floor(Math.random() * (REPORT_INTERVAL_MAX - REPORT_INTERVAL_MIN)) + REPORT_INTERVAL_MIN; log('等待 ' + Math.round(interval/1000) + ' 秒后再次上报', 'i'); self.timer = setTimeout(function() { self.report(); }, interval); } }); }; Task.prototype.end = function() { var self = this; var url = this.base + '/Course/UploadStudyRecordContinue?_t=' + Date.now(); // 使用FormData格式 var fd = new FormData(); fd.append('recordId', this.recordId); fd.append('end', 'true'); fd.append('position', this.pos); fd.append('timeOutConfirm', 'false'); httpPost(url, fd, function(err, resp) { self.done = true; if (err) { log('结束请求失败', 'err'); } else { log('结束响应: ' + JSON.stringify(resp), 'i'); } self.onUpdate('done'); log('任务完成!', 'ok'); }); }; Task.prototype.stop = function() { this.done = true; clearTimeout(this.timer); }; // 主应用 function App() { this.course = null; this.tasks = []; this.running = false; this.current = 0; this.apiBase = ''; this.init(); } App.prototype.init = function() { this.createUI(); this.autoDetect(); // 延迟检测课程,等待页面DOM加载完成 var self = this; setTimeout(function() { self.detect(); }, 1000); // 再次延迟检测,确保动态内容加载完成 setTimeout(function() { self.detect(); }, 3000); }; App.prototype.createUI = function() { var div = document.createElement('div'); div.id = 'qs-panel'; div.innerHTML = '
' + '

青书学堂刷课助手 By:王杰

' + '' + '
' + '
' + '
自动检测学校API,进入课程页面后点击"开始刷课"。
' + '
' + '' + '' + '
' + '
' + '' + '
' + '
' + '
' + '
未检测到课程
' + '
' + '' + '' + '
' + '
' + '' + '' + '' + '
' + '
' + '
' + '
'; document.body.appendChild(div); // 拖拽 var hd = div.querySelector('.qs-hd'); var drag = false, sx, sy, sl, st; hd.onmousedown = function(e) { drag = true; sx = e.clientX; sy = e.clientY; var r = div.getBoundingClientRect(); sl = r.left; st = r.top; div.style.position = 'fixed'; div.style.left = sl + 'px'; div.style.top = st + 'px'; }; document.onmousemove = function(e) { if (drag) { div.style.left = (sl + e.clientX - sx) + 'px'; div.style.top = (st + e.clientY - sy) + 'px'; } }; document.onmouseup = function() { drag = false; }; var self = this; document.getElementById('qs-min').onclick = function() { div.style.display = 'none'; }; document.getElementById('qs-start').onclick = function() { self.start(); }; document.getElementById('qs-stop').onclick = function() { self.stop(); }; document.getElementById('qs-refresh').onclick = function() { self.detect(); self.autoDetect(); }; document.getElementById('qs-gettk').onclick = function() { self.getToken(); }; document.getElementById('qs-save').onclick = function() { self.save(); }; document.getElementById('qs-test').onclick = function() { self.testApi(); }; document.getElementById('qs-debug').onclick = function() { self.debugAuth(); }; // 加载配置 var savedApi = GM_getValue('qs-api', ''); if (savedApi && savedApi.startsWith('http')) { document.getElementById('qs-api').value = savedApi; } document.getElementById('qs-token').value = GM_getValue('qs-token', ''); log('脚本已加载', 'ok'); }; App.prototype.autoDetect = function() { var self = this; detectSchoolApi(function(url) { self.apiBase = url; document.getElementById('qs-api').value = url; GM_setValue('qs-api', url); }); }; App.prototype.detect = function() { this.course = detectCourse(); var stat = document.getElementById('qs-stat'); var btn = document.getElementById('qs-start'); if (this.course) { var total = this.course.nodes.length; stat.innerHTML = '✓ ' + this.course.title + '
待学习课件: ' + total + ' 个'; btn.disabled = total === 0; if (total > 0) { log('检测到 ' + total + ' 个待学习课件', 'ok'); } else { log('所有课件已学习完成!', 'ok'); } } else { stat.innerHTML = '✗ 未检测到课程页面'; btn.disabled = true; } }; App.prototype.getToken = function() { var token = ''; // 尝试多种方式获取token // 1. 从cookie获取 token = getCookie('AccessToken'); if (token) { document.getElementById('qs-token').value = token; log('从Cookie获取Token成功', 'ok'); return; } // 2. 从localStorage获取 try { token = localStorage.getItem('AccessToken') || localStorage.getItem('token') || ''; if (token) { document.getElementById('qs-token').value = token; log('从localStorage获取Token成功', 'ok'); return; } } catch(e) {} // 3. 尝试从页面元素获取 try { var tokenInput = document.querySelector('input[name="token"], input[name="access_token"], #access_token, #token'); if (tokenInput && tokenInput.value) { document.getElementById('qs-token').value = tokenInput.value; log('从页面输入框获取Token成功', 'ok'); return; } } catch(e) {} // 4. 尝试从页面脚本中提取 try { var scripts = document.querySelectorAll('script'); for (var i = 0; i < scripts.length; i++) { var text = scripts[i].textContent || ''; var patterns = [ /['"]?AccessToken['"]?\s*[:=]\s*['"]([^'"]+)['"]/i, /['"]?token['"]?\s*[:=]\s*['"]([^'"]+)['"]/i, /['"]?access_token['"]?\s*[:=]\s*['"]([^'"]+)['"]/i ]; for (var p = 0; p < patterns.length; p++) { var m = text.match(patterns[p]); if (m && m[1] && m[1].length > 10) { document.getElementById('qs-token').value = m[1]; log('从页面脚本获取Token成功', 'ok'); return; } } } } catch(e) {} log('无法自动获取Token,请手动输入', 'w'); log('提示: F12打开控制台,输入 document.cookie 查看', 'i'); }; App.prototype.save = function() { var api = document.getElementById('qs-api').value.trim().replace(/\/+$/, ''); document.getElementById('qs-api').value = api; GM_setValue('qs-api', api); GM_setValue('qs-token', document.getElementById('qs-token').value.trim()); log('配置已保存', 'ok'); }; App.prototype.start = function() { if (!this.course || !this.course.nodes || !this.course.nodes.length) return; var base = document.getElementById('qs-api').value.trim().replace(/\/+$/, ''); if (!base) { log('请填写API地址', 'err'); return; } // 检查token状态 var token = getToken(); if (!token) { log('未找到Token,请先获取', 'err'); return; } // 检查token是否过期 if (token.includes('.')) { try { var parts = token.split('.'); if (parts.length === 3) { var payload = JSON.parse(atob(parts[1])); if (payload.exp) { var expDate = new Date(payload.exp * 1000); var now = new Date(); if (now > expDate) { log('Token已过期,请刷新页面重新登录!', 'err'); log('过期时间: ' + expDate.toLocaleString(), 'err'); return; } } } } catch(e) {} } this.save(); this.running = true; this.current = 0; this.tasks = []; document.getElementById('qs-start').disabled = true; document.getElementById('qs-stop').disabled = false; document.getElementById('qs-tasks').innerHTML = ''; log('开始刷课,共 ' + this.course.nodes.length + ' 个任务', 'ok'); this.processNext(base); }; App.prototype.processNext = function(base) { if (!this.running || this.current >= this.course.nodes.length) { if (this.running) { log('所有任务完成!', 'ok'); this.running = false; document.getElementById('qs-start').disabled = false; document.getElementById('qs-stop').disabled = true; // 所有任务完成后刷新页面以更新状态 log('3秒后刷新页面更新状态...', 'i'); setTimeout(function() { location.reload(); }, 3000); } return; } var node = this.course.nodes[this.current]; var idx = this.current; var self = this; var div = document.createElement('div'); div.className = 'qs-task qs-task-run'; div.id = 'qs-t-' + idx; div.textContent = '任务' + (idx + 1) + ': ' + node.contentId; document.getElementById('qs-tasks').appendChild(div); var task = new Task(base, node, function(status, pos) { var el = document.getElementById('qs-t-' + idx); if (!el) return; if (status === 'done' || status === 'fail') { el.className = 'qs-task ' + (status === 'done' ? 'qs-task-ok' : 'qs-task-err'); el.textContent = '任务' + (idx + 1) + ': ' + node.contentId + ' [' + (status === 'done' ? '完成' : '失败') + ']'; self.current++; // 直接处理下一个任务,不刷新页面 setTimeout(function() { self.processNext(base); }, 2000); } else { el.textContent = '任务' + (idx + 1) + ': ' + node.contentId + ' [' + status + (pos !== undefined ? ' ' + pos + '/1000' : '') + ']'; } }); this.tasks.push(task); task.begin(); }; App.prototype.stop = function() { this.running = false; this.tasks.forEach(function(t) { t.stop(); }); document.getElementById('qs-start').disabled = false; document.getElementById('qs-stop').disabled = true; log('已停止', 'w'); }; App.prototype.testApi = function() { var base = document.getElementById('qs-api').value.trim().replace(/\/+$/, ''); if (!base) { log('请填写API地址', 'err'); return; } log('开始测试API路径...', 'i'); findWorkingApiPath(base, function(url) { if (url) { log('找到可用API: ' + url, 'ok'); } else { log('未找到可用API', 'err'); } }); }; App.prototype.debugAuth = function() { log('=== 认证调试 ===', 'w'); // 1. 检查cookie var allCookies = document.cookie; log('Cookie内容: ' + (allCookies || '空'), 'i'); // 2. 检查AccessToken var accessToken = getCookie('AccessToken'); log('AccessToken: ' + (accessToken || '无'), accessToken ? 'ok' : 'err'); // 3. 检查token过期 if (accessToken && accessToken.includes('.')) { try { var parts = accessToken.split('.'); if (parts.length === 3) { var payload = JSON.parse(atob(parts[1])); log('JWT解析成功', 'ok'); if (payload.exp) { var expDate = new Date(payload.exp * 1000); var now = new Date(); var isExpired = now > expDate; log('过期时间: ' + expDate.toLocaleString(), isExpired ? 'err' : 'ok'); log('当前时间: ' + now.toLocaleString(), 'i'); if (isExpired) { log('!!! Token已过期,请刷新页面重新登录 !!!', 'err'); } else { var remain = Math.round((expDate - now) / 60000); log('剩余时间: ' + remain + ' 分钟', 'ok'); } } } } catch(e) { log('JWT解析失败: ' + e.message, 'w'); } } // 4. 检查其他可能的token var otherTokens = ['token', 'jwt', 'Authorization', 'X-Token']; for (var i = 0; i < otherTokens.length; i++) { var val = getCookie(otherTokens[i]); if (val) { log(otherTokens[i] + ': ' + val.substring(0, 20) + '...', 'i'); } } // 5. localStorage检查 try { var lsToken = localStorage.getItem('AccessToken') || localStorage.getItem('token'); if (lsToken) { log('localStorage token: ' + lsToken.substring(0, 20) + '...', 'i'); } } catch(e) {} log('================', 'w'); log('如Token过期,请刷新页面后重新登录', 'w'); }; // 启动 if (document.readyState === 'complete') { new App(); } else { window.addEventListener('load', function() { new App(); }); } })();