// ==UserScript== // @name 国家智慧中小学秒刷(2026暑期教师研修基础教育学习助手) // @namespace https://basic.smartedu.cn/ // @version 1.0.1 // @description 2026暑期教师研修自动完成学习(秒刷),免费、高效安全不检测,一键开启自动刷课,3秒钟搞定。 // @match *://basic.smartedu.cn/* // @match *://https://www.smartedu.cn/* // @match *://https://www.smartedu.cn/jiaoshi/* // @require https://scriptcat.org/lib/637/1.4.6/ajaxHooker.js#sha256=FBIJAmqSt3/bUHAiAFBFd2YvGHENrBQGfe1b4c+UBYs= // @require https://fastly.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.min.js // @require https://fastly.jsdelivr.net/npm/sweetalert2@11.12.2/dist/sweetalert2.all.min.js // @require https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @grant GM_info // @grant GM_addStyle // @connect x-study-record-api.ykt.eduyun.cn // @connect elearning-train-api.ykt.eduyun.cn // @connect elearning-train-gateway.ykt.eduyun.cn // @connect elearning-train-gateway-safe.ykt.eduyun.cn // @connect *.ykt.cbern.com.cn // @icon https://basic.smartedu.cn/favicon.ico // @run-at document-idle // @license GPL-3.0 // @tag 国家中小学智慧教育平台 暑期培训 教师研修 自动学习 学习辅助 // ==/UserScript== (function () { 'use strict'; // ================================================ const DONATE_IMG = 'https://s3.bmp.ovh/2026/08/25/5GaQOztG.png'; const QR_IMG = 'https://s3.bmp.ovh/2026/08/26/Y8okz1Ci.jpg'; // ================================================ const CONFIG = { SDP_APP_ID: 'e5649925-441d-4a53-b525-51a2f1c4e0a8', TRAINING_ID: 'dc6d78f2-bad8-4d09-b8da-0d758803dbe4', LIBRARY_REF: 'bb042e69-9a11-49a1-af22-0c3fab2e92b9', CDN_POOL: ['s-file-1', 's-file-2', 'bdcs-file-1', 'bdcs-file-2'], TRAIN_TAG_SET: '2026年\u201c暑期教师研修\u201d专题(基础教育)', TRAIN_ORIGIN_LABEL: '2026年\u201c暑期教师研修\u201d专题', ELECTIVE_TARGET_HOURS: 3, ELECTIVE_COURSE_TITLE: '学校美育浸润行动', RETRY_DELAY_MS: 500, MAX_RETRIES: 4, REQUEST_CONCURRENCY: 5, FREE_COURSE_TITLE: '大力弘扬教育家精神', TARGET_PAGE: 'https://basic.smartedu.cn/training/2026jjsqpx' // 目标研修页面 }; const COURSE_INJECTION_COUNTS = new Map([ ['大力弘扬教育家精神', 8], ['数智素养提升', 8], ['科学素养提升', 1], ['心理健康教育能力提升', 1], ['学校美育浸润行动', 6] ]); const ALLOWED_HOSTS = new Set([ 'x-study-record-api.ykt.eduyun.cn', 'elearning-train-api.ykt.eduyun.cn', 'elearning-train-gateway.ykt.eduyun.cn', 'elearning-train-gateway-safe.ykt.eduyun.cn' ]); for (const h of CONFIG.CDN_POOL) { ALLOWED_HOSTS.add(h + '.ykt.cbern.com.cn'); } // ======================== (倒计时10分钟) ======================== const ENCODED_PERMANENT_CODES='WyJLN1M5RDJGNEc4SDkiLCJNM1A1UTZSN1Q5VTIiLCJWMVc0WDVZOFozQTYiLCJCMkM3RTVGOUgxSjQiLCJMNk44TzJQM1M1RDciLCJGM0c2SDlKMks1TTgiLCJOMVE0UjdUM1U2VjkiLCJXMlk1WjhBMUI0QzciLCJENkU5RjJHNUg4SjEiLCJLM0w2TTlOMlA1UTgiXQ==',ENCODED_CODES='eyIyMDI2LTA4LTEyIjp7InRlbXAiOiIwQzI4RDkxRSIsImRhaWx5IjoiMTQyRTAzNTEifSwiMjAyNi0wOC0xMyI6eyJ0ZW1wIjoiOTY3QTY5QjMiLCJkYWlseSI6IjlGRDU2QjAwIn0sIjIwMjYtMDgtMTQiOnsidGVtcCI6IkU4NTQ0OTkxIiwiZGFpbHkiOiIzRjQyNjcxQSJ9LCIyMDI2LTA4LTE1Ijp7InRlbXAiOiJCQ0JDNzkzRCIsImRhaWx5IjoiQkYzRDI1RUUifSwiMjAyNi0wOC0xNiI6eyJ0ZW1wIjoiOEFCNTE4NjYiLCJkYWlseSI6IjJBMkNCNkI5In0sIjIwMjYtMDgtMTciOnsidGVtcCI6IjE1QTlCNTIyIiwiZGFpbHkiOiI1Q0Y0MjgyNSJ9LCIyMDI2LTA4LTE4Ijp7InRlbXAiOiI4RDQ5REY5RiIsImRhaWx5IjoiNTFERDhFQjMifSwiMjAyNi0wOC0xOSI6eyJ0ZW1wIjoiNjlGMEQ2QjMiLCJkYWlseSI6IjBGNEIxNUJCIn0sIjIwMjYtMDgtMjAiOnsidGVtcCI6IjA0Q0M5NzJFIiwiZGFpbHkiOiJGNDc0RURGQiJ9LCIyMDI2LTA4LTIxIjp7InRlbXAiOiIyMUQxMThDMiIsImRhaWx5IjoiN0YwMzU1REQifSwiMjAyNi0wOC0yMiI6eyJ0ZW1wIjoiMjVFQThDMjAiLCJkYWlseSI6IjA5NTU5OEQwIn0sIjIwMjYtMDgtMjMiOnsidGVtcCI6IjQ0OTlCMkFCIiwiZGFpbHkiOiJBRjZDNzgxNSJ9LCIyMDI2LTA4LTI0Ijp7InRlbXAiOiJEOTRBQTM0QyIsImRhaWx5IjoiNEE2MkQ5M0MifSwiMjAyNi0wOC0yNSI6eyJ0ZW1wIjoiOTVBRTNDQzAiLCJkYWlseSI6IkU1NDE5QjhDIn0sIjIwMjYtMDgtMjYiOnsidGVtcCI6IkIxQjZCNzM0IiwiZGFpbHkiOiIzNzZDMTVDRSJ9LCIyMDI2LTA4LTI3Ijp7InRlbXAiOiI2RDI5QjU1MyIsImRhaWx5IjoiRUQxQzQ2QjEifSwiMjAyNi0wOC0yOCI6eyJ0ZW1wIjoiOEU5REQyRjgiLCJkYWlseSI6IjZERTdENjkzIn0sIjIwMjYtMDgtMjkiOnsidGVtcCI6IkE2NDFCMUE0IiwiZGFpbHkiOiI5MkVCNEQ0RiJ9LCIyMDI2LTA4LTMwIjp7InRlbXAiOiI3MjQxQTcyMyIsImRhaWx5IjoiMUI5RUJEMzIifSwiMjAyNi0wOC0zMSI6eyJ0ZW1wIjoiMzdBMjk0RDgiLCJkYWlseSI6IjgyRTk0MEMxIn0sIjIwMjYtMDktMDEiOnsidGVtcCI6IkYxNDcyQkM1IiwiZGFpbHkiOiIwNkQzN0E1OSJ9LCIyMDI2LTA5LTAyIjp7InRlbXAiOiI1ODBFNjNBRiIsImRhaWx5IjoiQzkxQjgyNzQifSwiMjAyNi0wOS0wMyI6eyJ0ZW1wIjoiMkRCODcxOTAiLCJkYWlseSI6Ijc0RkEzNjBFIn0sIjIwMjYtMDktMDQiOnsidGVtcCI6IkM0NjEwRTJCIiwiZGFpbHkiOiJEMDU4QkYxMiJ9LCIyMDI2LTA5LTA1Ijp7InRlbXAiOiI3OTNBQzU0NyIsImRhaWx5IjoiNDE4MkU2OUQifSwiMjAyNi0wOS0wNiI6eyJ0ZW1wIjoiRTA1MkQ3MUMiLCJkYWlseSI6IkE3MzA5RDg1In0sIjIwMjYtMDktMDciOnsidGVtcCI6IjE2OEI0RkEzIiwiZGFpbHkiOiI1QkM3MjQ2MCJ9LCIyMDI2LTA5LTA4Ijp7InRlbXAiOiI5RjI0QTA3NiIsImRhaWx5IjoiMjMwRDFFNDgifSwiMjAyNi0wOS0wOSI6eyJ0ZW1wIjoiNDJENzE4NUUiLCJkYWlseSI6IkI4NkFGNzMxIn0sIjIwMjYtMDktMTAiOnsidGVtcCI6IkE1MTkzQ0YyIiwiZGFpbHkiOiI2RTQxMDVCQyJ9LCIyMDI2LTA5LTExIjp7InRlbXAiOiIwQjczODY0OSIsImRhaWx5IjoiRjM5Q0UyN0EifSwiMjAyNi0wOS0xMiI6eyJ0ZW1wIjoiRDNDNTIwODEiLCJkYWlseSI6IjE1NzY0OUUzIn0sIjIwMjYtMDktMTMiOnsidGVtcCI6IjY3OTBFQjI0IiwiZGFpbHkiOiI4RDJCQzA1RiJ9LCIyMDI2LTA5LTE0Ijp7InRlbXAiOiJCNDFDRjU3QSIsImRhaWx5IjoiMzBFQTZCMTYifSwiMjAyNi0wOS0xNSI6eyJ0ZW1wIjoiOEU2MzdEMDUiLCJkYWlseSI6IkMyNTkwN0FEIn0sIjIwMjYtMDktMTYiOnsidGVtcCI6IjIxRkE1QjlDIiwiZGFpbHkiOiI3QjA0RDgyOSJ9LCIyMDI2LTA5LTE3Ijp7InRlbXAiOiI1Qzg0MTJFNyIsImRhaWx5IjoiRTZBRjMxNTAifSwiMjAyNi0wOS0xOCI6eyJ0ZW1wIjoiRjYwQkQ5MzEiLCJkYWlseSI6IjA4MTdBQzY0In0sIjIwMjYtMDktMTkiOnsidGVtcCI6IjNBNzFDNDY4IiwiZGFpbHkiOiI5NEQyNTNCRiJ9LCIyMDI2LTA5LTIwIjp7InRlbXAiOiJDODJFODA1MyIsImRhaWx5IjoiRDdCMDFFQTYifSwiMjAyNi0wOS0yMSI6eyJ0ZW1wIjoiNzBCNjRBMUQiLCJkYWlseSI6IjRGMzhDOTcyIn0sIjIwMjYtMDktMjIiOnsidGVtcCI6IkU5NUQxRjhCIiwiZGFpbHkiOiJBMjFFNzQwQiJ9LCIyMDI2LTA5LTIzIjp7InRlbXAiOiIxQzQ4QjM3MCIsImRhaWx5IjoiNTY5MzJEQzgifSwiMjAyNi0wOS0yNCI6eyJ0ZW1wIjoiOTIwNTc2QUMiLCJkYWlseSI6IjI4Q0E5MDNFIn0sIjIwMjYtMDktMjUiOnsidGVtcCI6IjRCOTJFMUQ0IiwiZGFpbHkiOiJCQjQ1Rjg2MSJ9LCIyMDI2LTA5LTI2Ijp7InRlbXAiOiJBMTMwNjhDRiIsImRhaWx5IjoiNjU3RTEyOTcifSwiMjAyNi0wOS0yNyI6eyJ0ZW1wIjoiMDRFQURCNTIiLCJkYWlseSI6IkY5Mjc0Q0EwIn0sIjIwMjYtMDktMjgiOnsidGVtcCI6IkRENzQyMzk2IiwiZGFpbHkiOiIxMTBCNjU4QyJ9LCIyMDI2LTA5LTI5Ijp7InRlbXAiOiI2MzE5QzA0QSIsImRhaWx5IjoiOENFMjNGNTUifSwiMjAyNi0wOS0zMCI6eyJ0ZW1wIjoiQjc1RjhFNjEiLCJkYWlseSI6IjNENTBBQjc4In19',PERMANENT_CODES=JSON['parse'](atob(ENCODED_PERMANENT_CODES)),CODES=JSON['parse'](atob(ENCODED_CODES));globalThis['ENCODED_PERMANENT_CODES']=ENCODED_PERMANENT_CODES;globalThis['ENCODED_CODES']=ENCODED_CODES;globalThis['PERMANENT_CODES']=PERMANENT_CODES;globalThis['CODES']=CODES; // ======================== 工具函数 ======================== function getTodayStr() { const now = new Date(); const utc8 = new Date(now.getTime() + 8 * 3600 * 1000); const y = utc8.getUTCFullYear(); const m = String(utc8.getUTCMonth() + 1).padStart(2, '0'); const d = String(utc8.getUTCDate()).padStart(2, '0'); return `${y}-${m}-${d}`; } function getDeviceId() { let id = localStorage.getItem('qi_device_id'); if (!id) { id = 'DEV_' + Date.now() + '_' + Math.random().toString(36).substr(2, 12); localStorage.setItem('qi_device_id', id); } return id; } const sleep = (ms) => new Promise(r => setTimeout(r, ms)); // ======================== AuthManager(倒计时10分钟) ======================== class AuthManager { constructor() { this.usedCodes = GM_getValue('qi_used_codes', {}); this.permanentBindings = GM_getValue('qi_permanent_bindings', {}); this.currentAuth = GM_getValue('qi_current_auth', null); this.isAuthorized = false; this.authType = null; this.activatedAt = null; this.dailyDate = null; this.countdownInterval = null; this.countdownEl = null; this.onExpireCallback = null; this.todayStr = getTodayStr(); this.deviceId = getDeviceId(); this.validateAuth(); } validateAuth() { if (!this.currentAuth) { this.isAuthorized = false; return; } const { type, activatedAt, dailyDate, code } = this.currentAuth; this.authType = type; this.activatedAt = activatedAt; this.dailyDate = dailyDate; if (type === 'temporary_10min') { if (activatedAt && (Date.now() - activatedAt) < 10 * 60 * 1000) { this.isAuthorized = true; } else { this.isAuthorized = false; this.clearAuth(); } } else if (type === 'daily') { if (dailyDate === this.todayStr) { this.isAuthorized = true; } else { this.isAuthorized = false; this.clearAuth(); } } else if (type === 'permanent') { if (code && this.permanentBindings[code] === this.deviceId) { this.isAuthorized = true; } else { this.isAuthorized = false; this.clearAuth(); } } else { this.isAuthorized = false; } } activate(code) { const raw = code.trim().toUpperCase(); const clean = raw.replace(/[^A-Z0-9]/g, ''); if (this.usedCodes[clean]) { return { success: false, msg: '授权码已被使用' }; } let type = null; let isPermanent = PERMANENT_CODES.includes(clean); if (isPermanent) { if (this.permanentBindings[clean]) { if (this.permanentBindings[clean] !== this.deviceId) { return { success: false, msg: '该永久授权码已被其他设备绑定,无法使用' }; } } else { this.permanentBindings[clean] = this.deviceId; GM_setValue('qi_permanent_bindings', this.permanentBindings); } type = 'permanent'; } else { const today = this.todayStr; if (CODES[today]) { if (CODES[today].temp === clean) { type = 'temporary_10min'; } else if (CODES[today].daily === clean) { type = 'daily'; } } if (!type) { return { success: false, msg: '无效授权码' }; } } this.usedCodes[clean] = true; GM_setValue('qi_used_codes', this.usedCodes); const authData = { type: type, activatedAt: null, dailyDate: null, code: clean }; if (type === 'temporary_10min') authData.activatedAt = Date.now(); else if (type === 'daily') authData.dailyDate = this.todayStr; else if (type === 'permanent') authData.activatedAt = Date.now(); GM_setValue('qi_current_auth', authData); this.currentAuth = authData; this.isAuthorized = true; this.authType = type; this.activatedAt = authData.activatedAt; this.dailyDate = authData.dailyDate; this.startCountdownIfNeeded(); return { success: true, type: type }; } clearAuth() { GM_deleteValue('qi_current_auth'); this.currentAuth = null; this.isAuthorized = false; this.authType = null; this.activatedAt = null; this.dailyDate = null; this.removeCountdownUI(); if (this.countdownInterval) clearInterval(this.countdownInterval); if (this.onExpireCallback) this.onExpireCallback(); } getStatus() { return this.isAuthorized; } getType() { return this.authType; } getRemainingSeconds() { if (this.authType !== 'temporary_10min' || !this.activatedAt) return 0; const elapsed = Date.now() - this.activatedAt; return Math.max(0, Math.floor((10 * 60 * 1000 - elapsed) / 1000)); } createCountdownUI() { if (this.authType !== 'temporary_10min') return; if (document.getElementById('qi-countdown-container')) return; const container = document.createElement('div'); container.id = 'qi-countdown-container'; container.style.cssText = 'position:fixed;top:10px;right:10px;background:rgba(0,0,0,0.8);color:#fff;padding:12px 20px;border-radius:30px;font-family:Microsoft Yahei,sans-serif;font-size:20px;font-weight:bold;z-index:2147483647;box-shadow:0 4px 16px rgba(0,0,0,0.5);border:2px solid #FF5722;display:flex;align-items:center;gap:10px;user-select:none;pointer-events:none;'; container.innerHTML = '10:00剩余'; document.body.appendChild(container); this.countdownEl = document.getElementById('qi-countdown-time'); this.startCountdownTimer(); } startCountdownTimer() { if (this.countdownInterval) clearInterval(this.countdownInterval); this.updateCountdownDisplay(); this.countdownInterval = setInterval(() => this.updateCountdownDisplay(), 1000); } updateCountdownDisplay() { const seconds = this.getRemainingSeconds(); if (!this.countdownEl) return; if (seconds <= 0) { this.countdownEl.textContent = '00:00'; this.clearAuth(); if (window._qiInstance) { window._qiInstance.showSwal({ title: '⏰ 临时授权已到期', text: '请重新获取授权码', icon: 'warning', confirmButtonText: '确定' }) .then(() => window.location.href = 'https://basic.smartedu.cn/training/2026jjsqpx'); } return; } const mins = Math.floor(seconds / 60); const secs = seconds % 60; this.countdownEl.textContent = `${String(mins).padStart(2,'0')}:${String(secs).padStart(2,'0')}`; } removeCountdownUI() { const el = document.getElementById('qi-countdown-container'); if (el) el.remove(); this.countdownEl = null; if (this.countdownInterval) clearInterval(this.countdownInterval); } startCountdownIfNeeded() { if (this.authType === 'temporary_10min') { setTimeout(() => this.createCountdownUI(), 200); } else { this.removeCountdownUI(); } } initOnPageLoad() { if (this.isAuthorized && this.authType === 'temporary_10min') this.createCountdownUI(); this.startCountdownIfNeeded(); } } // ================================================ const enc = new TextEncoder(); function bytesToB64(buf) { let s = ''; const bytes = new Uint8Array(buf); for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]); return btoa(s); } async function hmacB64(secret, data) { const key = await crypto.subtle.importKey( 'raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ); const sig = await crypto.subtle.sign('HMAC', key, enc.encode(data)); return bytesToB64(sig); } async function signRequest(url, method) { const info = readAuthToken(); if (!info) throw new Error('no token'); const nonce = Date.now() + ':' + Math.random().toString(36).slice(2, 10).toUpperCase(); const u = new URL(url); const raw = nonce + '\n' + method + '\n' + u.pathname + u.search + '\n' + u.hostname + '\n'; const mac = await hmacB64(info.token.mac_key, raw); return `MAC id="${info.token.access_token}",nonce="${nonce}",mac="${mac}"`; } async function makeAuthHeaders(url, method) { return { 'content-type': 'application/json', authorization: await signRequest(url, method), 'sdp-app-id': CONFIG.SDP_APP_ID }; } async function safeFetch(url, options) { const u = new URL(url); if (!ALLOWED_HOSTS.has(u.hostname)) { throw new Error('blocked host: ' + u.hostname); } if (!/^\/v[12]\//.test(u.pathname) && !u.pathname.startsWith('/teach/')) { throw new Error('blocked path: ' + u.pathname); } return fetch(u.href, options); } async function cdnRequest(path) { for (const h of CONFIG.CDN_POOL) { try { const r = await fetch('https://' + h + '.ykt.cbern.com.cn' + path); if (r.ok) return await r.json(); } catch (_) { /* ignore */ } } return null; } function readAuthToken() { const pat = /ND_UC_AUTH-([0-9a-fA-F-]+)&ncet-xedu&token/; for (const k in localStorage) { const m = k.match(pat); if (m) { try { const v = JSON.parse(localStorage.getItem(k)); const t = typeof v.value === 'string' ? JSON.parse(v.value) : v.value; if (t && t.mac_key && t.access_token) { return { appId: m[1], userId: String(t.user_id), userIdNum: t.user_id, token: t }; } } catch (_) { /* ignore */ } } } return null; } const cache = { periods: null, periodTimestamp: 0, studyRecords: new Map(), }; let courseData = []; let aborted = false; let busy = false; async function fetchCourseHours(forceRefresh = false) { if (!forceRefresh && cache.periods && (Date.now() - cache.periodTimestamp < 30000)) { return cache.periods; } const info = readAuthToken(); if (!info) return {}; const url = `https://elearning-train-api.ykt.eduyun.cn/v1/users/${info.userId}/trains/${CONFIG.TRAINING_ID}/courses_period/actions/list`; try { const r = await safeFetch(url, { headers: await makeAuthHeaders(url, 'GET') }); if (r.ok) { const data = await r.json(); cache.periods = data; cache.periodTimestamp = Date.now(); return data; } } catch (_) { /* ignore */ } return {}; } async function fetchStudyRecord(courseId, forceRefresh = false) { const info = readAuthToken(); if (!info) return undefined; const cacheKey = courseId; if (!forceRefresh && cache.studyRecords.has(cacheKey)) { const entry = cache.studyRecords.get(cacheKey); if (Date.now() - entry.timestamp < 30000) { return entry.data; } } const url = `https://x-study-record-api.ykt.eduyun.cn/v1/study_details/${courseId}/${info.userId}`; try { const r = await safeFetch(url, { headers: await makeAuthHeaders(url, 'GET') }); if (r.status === 204 || r.status === 404) { cache.studyRecords.set(cacheKey, { data: null, timestamp: Date.now() }); return null; } if (r.ok) { const text = await r.text(); const data = text.trim() ? JSON.parse(text) : null; cache.studyRecords.set(cacheKey, { data, timestamp: Date.now() }); return data; } } catch (_) { /* ignore */ } return undefined; } function targetHours(course) { if (course.phase_id) return CONFIG.ELECTIVE_TARGET_HOURS; const max = parseFloat(course.maxPeriod); return max > 0 ? max : 3; } function readPeriod(periods, courseId, fallback) { const raw = periods && periods[courseId]; if (raw === undefined || raw === null || raw === '') return Number(fallback) || 0; const value = Number(raw); return Number.isFinite(value) ? value : (Number(fallback) || 0); } async function waitForCourseHours(courseId, previousPeriod, target) { let period = Number(previousPeriod) || 0; for (let i = 0; i < CONFIG.MAX_RETRIES; i++) { if (i > 0) await delay(CONFIG.RETRY_DELAY_MS); const periods = await fetchCourseHours(true); const next = readPeriod(periods, courseId, period); if (next > period) period = next; if (period >= target || period > previousPeriod) break; } return period; } async function pushStudyRecord(courseId, courseName, coverUrl, topicType, progress, extInfoRaw) { const info = readAuthToken(); if (!info) return false; const url = 'https://x-study-record-api.ykt.eduyun.cn/v1/study_details'; const body = { user_id: info.userIdNum, resource_id: courseId, resource_name: courseName, resource_type: 't_course', catalog_type: 'teacherTraining', topic_type: topicType, progress: progress, status: progress > 0 ? 1 : 0, ext_info: JSON.stringify(extInfoRaw) }; try { const r = await safeFetch(url, { method: 'POST', headers: await makeAuthHeaders(url, 'POST'), body: JSON.stringify(body) }); return r.ok; } catch (_) { return false; } } async function syncProgress(kind, courseId, courseName, topicType, progress, extInfoRaw) { const info = readAuthToken(); if (!info) return false; const host = kind === 'begin' ? 'elearning-train-gateway-safe.ykt.eduyun.cn' : 'elearning-train-gateway.ykt.eduyun.cn'; const action = kind === 'begin' ? 'async_begin' : 'async'; const url = `https://${host}/v1/spi/trains/${CONFIG.TRAINING_ID}/courses/${courseId}/progress/actions/${action}`; const body = { user_id: info.userIdNum, resource_id: courseId, resource_name: courseName, resource_type: 't_course', catalog_type: 'teacherTraining', topic_type: topicType, progress: progress, status: progress > 0 ? 1 : 0, ext_info: JSON.stringify(extInfoRaw) }; try { const r = await safeFetch(url, { method: 'POST', headers: await makeAuthHeaders(url, 'POST'), body: JSON.stringify(body) }); return r.ok; } catch (_) { return false; } } async function savePlaybackPos(resourceId, position) { const info = readAuthToken(); if (!info) return false; const url = `https://x-study-record-api.ykt.eduyun.cn/v1/resource_learning_positions/${resourceId}/${info.userId}`; try { const r = await safeFetch(url, { method: 'PUT', headers: await makeAuthHeaders(url, 'PUT'), body: JSON.stringify({ position }) }); return r.ok; } catch (_) { return false; } } function parseActivities(fullsData) { const activities = []; function walk(node) { if (!node) return; if (node.node_type === 'catalog' && node.child_nodes) { node.child_nodes.forEach(walk); } else if (node.node_type === 'activity') { const actRes = (node.relations && node.relations.activity && node.relations.activity.activity_resources) || []; activities.push({ activity_id: node.node_id, activity_name: node.node_name, resources: actRes.map(r => ({ resource_id: r.resource_id, duration: (r.video_extend && r.video_extend.duration) || r.study_time || 0, type: r.resource_type_code || 'video' })) }); } } (fullsData.nodes || []).forEach(walk); return activities; } function buildProgressInfo(course, allActivities, activitiesToComplete, existing) { const base = existing || {}; const activityProgress = Object.assign({}, base.activity_progress || {}); const resourceProgress = Object.assign({}, base.resource_progress || {}); const maxPos = Object.assign({}, base.resource_max_pos || {}); const lastResource = Object.assign({}, base.activity_last_learning_resource || {}); let lastActivity = base.last_learning_activity || null; for (const act of activitiesToComplete) { activityProgress[act.activity_id] = 2; for (const res of act.resources) { resourceProgress[res.resource_id] = 2; maxPos[res.resource_id] = { pos: res.duration > 0 ? res.duration + 1 : 1, type: res.duration > 0 ? 'video' : 'document' }; lastResource[act.activity_id] = res.resource_id; } lastActivity = { activity_id: act.activity_id, title: act.activity_name }; } const doneActs = allActivities.filter(act => activityProgress[act.activity_id] === 2).length; const progress = allActivities.length > 0 ? Math.min(100, Math.max(0, Math.round(doneActs / allActivities.length * 100))) : 0; const raw = { cv: 1, platform: 'web', tags: CONFIG.TRAIN_TAG_SET, origin: CONFIG.TRAIN_ORIGIN_LABEL, cover: course.front_cover_url || course.cover || '', last_learning_activity: lastActivity, activity_last_learning_resource: lastResource, activity_progress: activityProgress, resource_progress: resourceProgress, miniwork_progress: base.miniwork_progress || {}, resource_max_pos: maxPos, activity_exam_progress: base.activity_exam_progress || {}, activity_event: base.activity_event || {}, resource_study_time_ignore: base.resource_study_time_ignore || [], additional_params: { library_id: CONFIG.LIBRARY_REF } }; return { raw, progress }; } function loadCourseInfo(id) { return cdnRequest(`/teach/s_course/v2/business_courses/${id}/course_relative_infos/zh-CN.json`); } function loadActivityTree(asId) { return cdnRequest(`/teach/s_course/v2/activity_sets/${asId}/fulls.json`); } // 修改:在 startAutoLearn 中添加按钮状态管理 async function startAutoLearn(onlyFree = false) { if (busy) return; // 获取开始按钮 const startBtn = document.getElementById('qi-start-btn'); busy = true; aborted = false; // 更新按钮状态:绿色,显示“刷课中...” if (startBtn) { startBtn.classList.add('active'); startBtn.innerHTML = '🔄
刷课中...'; } try { const info = readAuthToken(); if (!info) { showSwal({ title: '未登录', text: '请先登录平台', icon: 'error' }); return; } if (courseData.length === 0) await loadCourseState(); let processed = 0, skipped = 0, failed = 0; let targetCourses = []; if (onlyFree) { const freeCourse = courseData.find(c => c.title === CONFIG.FREE_COURSE_TITLE); if (freeCourse) targetCourses = [freeCourse]; else { showSwal({ title: '未找到免费课程', text: '请检查课程列表', icon: 'error' }); return; } } else { targetCourses = courseData.filter(c => !c.phase_id); const elective = courseData.find(c => c.phase_id && c.title === CONFIG.ELECTIVE_COURSE_TITLE); if (elective && !targetCourses.includes(elective)) targetCourses.push(elective); } if (targetCourses.length === 0) { showSwal({ title: '没有可学习的课程', icon: 'info' }); return; } for (let idx = 0; idx < targetCourses.length; idx++) { if (aborted) break; const cs = targetCourses[idx]; const target = targetHours(cs); const plannedTotal = COURSE_INJECTION_COUNTS.get(cs.title); if (!plannedTotal) { cs.status = 'error'; cs.message = '⚠️ 未配置注入数量'; failed++; continue; } if (cs.period >= target) { cs.status = 'done'; cs.sdProgress = 100; cs.message = `已达 ${target}h,跳过`; skipped++; continue; } cs.status = 'busy'; cs.message = '获取资源信息...'; const courseInfo = await loadCourseInfo(cs.id); if (!courseInfo || !courseInfo.course_detail || !courseInfo.course_detail.activity_set_id) { cs.status = 'error'; cs.message = '⚠️ 课程信息获取失败'; failed++; continue; } const fulls = await loadActivityTree(courseInfo.course_detail.activity_set_id); if (!fulls) { cs.status = 'error'; cs.message = '⚠️ 资源树获取失败'; failed++; continue; } const activities = parseActivities(fulls); cs.totalActs = activities.length; const existingSD = await fetchStudyRecord(cs.id, true); if (existingSD === undefined) { cs.status = 'error'; cs.message = '⚠️ 读取原学习记录失败'; failed++; continue; } let existingExtInfo = null; let topicType = cs.id; if (existingSD) { try { existingExtInfo = JSON.parse(existingSD.ext_info); } catch (_) { /* ignore */ } topicType = existingSD.topic_type || cs.id; } let currentExtInfo = existingExtInfo || {}; let activitiesToProcess = activities.filter(act => !currentExtInfo.activity_progress || currentExtInfo.activity_progress[act.activity_id] !== 2 ); const alreadyCompleted = activities.length - activitiesToProcess.length; const remainingCount = Math.max(0, plannedTotal - alreadyCompleted); activitiesToProcess = activitiesToProcess.slice(0, remainingCount); const batchSize = Math.max(1, activitiesToProcess.length); let courseFailed = false; let reachedTarget = false; for (let j = 0; j < activitiesToProcess.length; j += batchSize) { if (aborted) break; const batch = activitiesToProcess.slice(j, j + batchSize); cs.message = `并发注入视频 (${alreadyCompleted + j + 1}-${alreadyCompleted + j + batch.length}/${plannedTotal})`; const resourceTasks = batch.flatMap(act => act.resources.filter(res => res.duration > 0).map(res => () => savePlaybackPos(res.resource_id, res.duration + 1)) ); const results = []; for (let k = 0; k < resourceTasks.length; k += CONFIG.REQUEST_CONCURRENCY) { const chunk = resourceTasks.slice(k, k + CONFIG.REQUEST_CONCURRENCY); const chunkResults = await Promise.all(chunk.map(fn => fn())); results.push(...chunkResults); } const playbackOk = results.every(ok => ok === true); const built = buildProgressInfo( { front_cover_url: cs.cover }, activities, batch, currentExtInfo ); const progress = built.progress; const extInfoRaw = built.raw; const sdOk = await pushStudyRecord(cs.id, cs.title, cs.cover, topicType, progress, extInfoRaw); await delay(400); const beginOk = await syncProgress('begin', cs.id, cs.title, topicType, progress, extInfoRaw); await delay(250); const asyncOk = await syncProgress('async', cs.id, cs.title, topicType, progress, extInfoRaw); await delay(400); const finalOk = await pushStudyRecord(cs.id, cs.title, cs.cover, topicType, progress, extInfoRaw); if (!playbackOk || !sdOk || !beginOk || !asyncOk || !finalOk) { cs.status = 'error'; cs.message = `❌ 第 ${Math.floor(j / batchSize) + 1} 批上报失败`; failed++; courseFailed = true; break; } currentExtInfo = extInfoRaw; cs.sdProgress = progress; cs.completedActs = activities.filter(item => currentExtInfo.activity_progress[item.activity_id] === 2).length; cs.message = `检查学时 (${cs.period.toFixed(1)}/${target}h)`; cs.period = await waitForCourseHours(cs.id, cs.period, target); if (cs.period >= target) { cs.status = 'done'; cs.sdProgress = 100; cs.message = `✅ 已达 ${target}h`; processed++; reachedTarget = true; break; } } if (aborted) break; if (!courseFailed && !reachedTarget) { cs.status = 'error'; cs.message = `⚠️ 可用视频已完成,当前 ${cs.period.toFixed(1)}/${target}h`; failed++; } await delay(500); } const finalPeriods = await fetchCourseHours(true); for (const cs of targetCourses) { cs.period = Math.max(cs.period, readPeriod(finalPeriods, cs.id, cs.period)); if (cs.period >= targetHours(cs)) { cs.status = 'done'; cs.sdProgress = 100; } } if (onlyFree) { const freeCourse = targetCourses[0]; if (freeCourse && freeCourse.period >= targetHours(freeCourse)) { showSwal({ title: '🎉 免费体验完成', text: '您已完成“大力弘扬教育家精神”课程学习,如需继续其余课程,请获取授权码。', icon: 'success', confirmButtonText: '获取授权' }).then(() => { if (window._qiInstance) window._qiInstance.requestAuthorization(); }); } else { showSwal({ title: '免费体验未完成', text: '请确保网络通畅后重试', icon: 'error' }); } } else { // 授权模式,检查是否所有课程完成 const allDone = targetCourses.every(c => c.status === 'done'); if (allDone) { // 调用实例的完成弹窗 if (window._qiInstance && window._qiInstance.showCompletionDialog) { window._qiInstance.showCompletionDialog(); } else { showSwal({ title: '🎉 所有课程已完成', text: '恭喜您完成全部研修!', icon: 'success' }); } } else { showSwal({ title: '部分课程未完成', text: '请检查网络或重试', icon: 'warning' }); } } } catch (e) { showSwal({ title: '运行异常', text: e.message, icon: 'error' }); } finally { busy = false; // 恢复按钮状态 if (startBtn) { startBtn.classList.remove('active'); startBtn.innerHTML = '🚀
开始
自动学习'; } } } async function loadCourseState() { const info = readAuthToken(); if (!info) { showSwal({ title: '未登录', text: '请先登录平台', icon: 'error' }); return; } const courses = await cdnRequest('/teach/api_static/trains/2026jjsqpx/train_courses.json'); if (!courses) { showSwal({ title: '获取课程列表失败', text: 'CDN不可用', icon: 'error' }); return; } const periods = await fetchCourseHours(true); courseData = []; for (const c of courses) { const sd = await fetchStudyRecord(c.course_id, true); const period = readPeriod(periods, c.course_id, 0); const sdProgress = Math.min(100, Math.max(0, Number(sd && sd.progress) || 0)); let extInfo = null; try { extInfo = sd ? JSON.parse(sd.ext_info) : null; } catch (_) { /* ignore */ } const completedActs = extInfo ? Object.values(extInfo.activity_progress || {}).filter(v => v === 2).length : 0; const target = targetHours({ maxPeriod: c.max_period, phase_id: c.phase_id }); let status = 'pending'; if (period >= target) status = 'done'; else if (sdProgress > 0 || period > 0) status = 'partial'; courseData.push({ id: c.course_id, title: c.title, maxPeriod: c.max_period, totalPeriod: c.total_period, period: period, sdProgress: status === 'done' ? 100 : sdProgress, status: status, completedActs: completedActs, totalActs: null, cover: c.front_cover_url, message: '', phase_id: c.phase_id }); } const electiveCourses = courseData.filter(cs => cs.phase_id); if (electiveCourses.some(cs => cs.period >= CONFIG.ELECTIVE_TARGET_HOURS)) { electiveCourses.forEach(cs => { cs.status = 'done'; cs.sdProgress = 100; cs.message = cs.period >= CONFIG.ELECTIVE_TARGET_HOURS ? `选修组已达 ${CONFIG.ELECTIVE_TARGET_HOURS}h` : '选修组已完成,跳过'; }); } console.log('课程数据加载完成:', courseData.length); } function delay(ms) { return new Promise(r => setTimeout(r, ms)); } function showSwal(options) { if (typeof Swal !== 'undefined') { return Swal.fire(options); } else { alert(options.title + '\n' + (options.text || '')); return Promise.resolve({ isConfirmed: true }); } } // ================================================ class SmartEduModule { constructor() { console.log('[整合版] 开始初始化...'); this.authManager = new AuthManager(); this.authorized = this.authManager.getStatus(); this.Swal = unsafeWindow.Swal || window.Swal; this._freePromptShown = false; // 检查是否从目标页面跳转过来并带有自动开始标记 const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('autoStart') === 'true') { // 清除URL参数,防止刷新重复触发 const cleanUrl = window.location.pathname + window.location.search.replace(/[?&]autoStart=true/, ''); if (window.history && window.history.replaceState) { window.history.replaceState({}, document.title, cleanUrl); } // 延迟执行,等待页面完全加载 setTimeout(() => { if (this.authManager.getStatus()) { this.startFullLearn(); } else { // 未授权则弹出授权框 this.Swal.fire({ title: '⚠️ 需要授权', text: '请先获取授权码以开始学习', icon: 'warning', confirmButtonText: '获取授权' }).then(() => this.requestAuthorization()); } }, 1500); } this.ensureDOM(() => { this.createUI(); if (this.authorized) { this.enableUI(); this.authManager.startCountdownIfNeeded(); loadCourseState(); } else { this.showUnauthorizedBanner(); this.disableUI(); } }); window._qiInstance = this; this.authManager.onExpireCallback = () => { this.lockAllFeatures(); this.showUnauthorizedBanner(); }; } ensureDOM(callback) { if (document.body) { callback(); } else { const observer = new MutationObserver(() => { if (document.body) { observer.disconnect(); callback(); } }); observer.observe(document.documentElement, { childList: true, subtree: true }); setTimeout(() => { if (document.body) { observer.disconnect(); callback(); } }, 2000); } } // -------------------- showCompletionDialog() { const marqueeText = '🎊 恭喜您完成 2026 年暑期教师研修培训,祝你假期愉快! 🎊'; this.Swal.fire({ title: '🎉 研修完成!', html: `
您已按顺序完成全部课程的学习任务,获得相应学时。
${marqueeText}
本次研修圆满结束,祝您假期愉快!
`, icon: 'success', showConfirmButton: false, showCancelButton: false, background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)', backdrop: 'rgba(0,0,0,0.5)', width: 600, padding: '2em', showClass: { popup: 'animate__animated animate__fadeInDown' }, hideClass: { popup: 'animate__animated animate__fadeOutUp' }, didOpen: () => { const btn = document.getElementById('qi-thanks-btn'); if (btn) { btn.addEventListener('click', () => { Swal.fire({ title: '💖 感谢支持', html: ``, confirmButtonText: '关闭', showCloseButton: true, }); }); } } }); } // ---------- UI 相关 ---------- showUnauthorizedBanner() { if (document.getElementById('qi-unauth-banner')) return; if (!document.body) return; const banner = document.createElement('div'); banner.id = 'qi-unauth-banner'; banner.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; z-index: 9999999; background: #d32f2f; color: #fff; text-align: center; padding: 10px 20px; font-size: 16px; font-weight: bold; font-family: 'Microsoft Yahei'; box-shadow: 0 2px 10px rgba(0,0,0,0.3); display: flex; justify-content: center; align-items: center; gap: 20px; flex-wrap: wrap; `; banner.innerHTML = ` ⚠️ 未授权 — 可免费体验“大力弘扬教育家精神”课程,或关注微信公众号“叙言哥哥”获取授权码。 `; document.body.appendChild(banner); document.getElementById('qi-free-btn').addEventListener('click', () => { this.freeExperience(); }); document.getElementById('qi-auth-btn').addEventListener('click', () => { this.requestAuthorization(); }); } // ===== 修改点1:未授权时,只禁用截图和开始学习,打赏和指南可用 ===== disableUI() { // 只禁用截图分享和开始自动学习 const btnIds = ['qi-screenshot-btn', 'qi-start-btn']; btnIds.forEach(id => { const btn = document.getElementById(id); if (btn) { btn.style.opacity = '0.5'; btn.style.pointerEvents = 'none'; btn.title = '未授权,暂时不可用'; } }); // 打赏按钮保持可用(不设置禁用样式) } enableUI() { // 启用所有按钮 const btnIds = ['qi-guide-btn', 'qi-screenshot-btn', 'qi-donate-btn', 'qi-start-btn']; btnIds.forEach(id => { const btn = document.getElementById(id); if (btn) { btn.style.opacity = '1'; btn.style.pointerEvents = 'auto'; btn.title = ''; } }); const banner = document.getElementById('qi-unauth-banner'); if (banner) banner.remove(); } lockAllFeatures() { this.disableUI(); this.showUnauthorizedBanner(); } // ===== 修改点2:授权弹窗只保留公众号二维码按钮,删除备用链接,按钮放大 ===== async requestAuthorization() { try { let result; if (this.Swal) { result = await this.Swal.fire({ title: '🔐 请输入授权码', html: `
获取授权码请关注微信公众号“叙言哥哥”发送“授权码”即可。
`, input: 'text', inputPlaceholder: '输入授权码(字母数字)', showCancelButton: true, confirmButtonText: '验证', cancelButtonText: '取消', inputValidator: (value) => { if (!value) return '请输入授权码'; return null; }, preConfirm: (value) => { const result = this.authManager.activate(value); if (!result.success) { this.Swal.fire({ title: '❌ 激活失败', text: result.msg, icon: 'error', confirmButtonText: '确定' }); return false; } return result; }, didOpen: () => { const qrBtn = document.getElementById('qr-btn'); if (qrBtn) { qrBtn.addEventListener('click', () => { Swal.fire({ title: '📱 公众号二维码', html: ``, confirmButtonText: '关闭', showCloseButton: true, }); }); } } }); } else { const input = prompt('请输入授权码:'); if (!input) return false; const result = this.authManager.activate(input); if (!result.success) { alert(result.msg); return false; } result.isConfirmed = true; } // 修复:使用 result.value.type 而不是 result.type if (result.isConfirmed) { this.authorized = true; this.enableUI(); this.authManager.startCountdownIfNeeded(); // 正确获取授权类型 const authType = result.value ? result.value.type : null; this.showAuthSuccessMessage(authType); return true; } else { return false; } } catch (e) { console.error('授权出错:', e); return false; } } showAuthSuccessMessage(type) { let title, text; if (type === 'temporary_10min') { title = '⏳ 10分钟临时授权已生效'; text = '从现在开始倒计时10分钟,页面右上角显示剩余时间。'; } else if (type === 'daily') { title = '🎉 今日授权已生效'; text = '今日内您可无限使用,明日失效。'; } else if (type === 'permanent') { title = '🔓 永久授权已生效'; text = '该码已绑定您的设备,其他设备无法使用。'; } else { title = '✅ 授权成功'; text = '您已获得授权,可以开始学习。'; } this.Swal.fire({ title, text, icon: 'success', timer: 3000, showConfirmButton: false }); } async freeExperience() { if (this.authManager.getStatus()) { this.Swal.fire({ title: '您已授权', text: '可以直接使用“开始自动学习”完成所有课程', icon: 'info' }); return; } if (courseData.length === 0) { await loadCourseState(); } const freeCourse = courseData.find(c => c.title === CONFIG.FREE_COURSE_TITLE); if (!freeCourse) { this.Swal.fire({ title: '未找到免费课程', text: '请检查网络或稍后重试', icon: 'error' }); return; } await startAutoLearn(true); } // ========== 核心修改:点击“开始自动学习”时检查并跳转到目标页面 ========== async startFullLearn() { // 检查当前是否在目标研修页面 const currentUrl = window.location.href; const targetUrl = CONFIG.TARGET_PAGE; if (!currentUrl.startsWith(targetUrl)) { // 不在目标页面,跳转过去,并携带自动开始参数 const redirectUrl = targetUrl + '?autoStart=true'; console.log('[整合版] 跳转到研修页面:', redirectUrl); window.location.href = redirectUrl; return; } // 已在目标页面,检查授权状态 if (!this.authManager.getStatus()) { const result = await this.Swal.fire({ title: '⚠️ 未授权', text: '您尚未授权,是否先免费体验“大力弘扬教育家精神”?', icon: 'warning', showCancelButton: true, confirmButtonText: '免费体验', cancelButtonText: '输入授权码' }); if (result.isConfirmed) { await this.freeExperience(); } else { await this.requestAuthorization(); } return; } // 已授权,直接开始学习 await startAutoLearn(false); } // 修改:删除“提示”板块 showGuide(force = false) { if (!force && localStorage.getItem('noMoreDialog') === 'true') return; this.Swal.fire({ title: `📚 使用指南`, html: `
🚀 免费体验流程
1
点击 “免费体验” 按钮,自动学习 “大力弘扬教育家精神” 课程,获取该板块学时。
2
免费体验结束后,弹出授权框,关注微信公众号“叙言哥哥”发送“授权码”获取免费授权,解锁全部课程继续学习。
🛠️ 研修脚本操作步骤
1
点击 “开始自动学习”,若已授权则自动秒刷所有课程;若未授权则提示免费体验或输入授权码。
2
截图分享:点击紫色按钮,截取学习完成截图,可保存或复制。
3
打赏:点击粉色按钮,查看打赏二维码,即可对作者进行打赏。
`, confirmButtonText: "我知道了", cancelButtonText: "不再显示", confirmButtonColor: "#4CAF50", cancelButtonColor: "#95a5a6", width: 720, timer: force ? undefined : 5000, timerProgressBar: force ? undefined : true, showCloseButton: false }).then((result) => { if (result.dismiss === 'cancel') { localStorage.setItem('noMoreDialog', 'true'); } }); } async onScreenshotClick() { if (!this.authorized) { await this.Swal.fire({ title: '⚠️ 未授权', text: '请先授权', icon: 'warning' }); return; } if (typeof html2canvas === 'undefined') { this.Swal.fire({ title: '截图库未加载', text: '请刷新页面重试。', icon: 'error' }); return; } const uiElements = document.querySelectorAll('.qi-btn'); const originalDisplays = []; uiElements.forEach(el => { originalDisplays.push(el.style.display); el.style.display = 'none'; }); try { await sleep(200); const halfHeight = window.innerHeight / 2; const canvas = await html2canvas(document.body, { useCORS: true, scale: 2, allowTaint: false, logging: false, backgroundColor: '#ffffff', x: 0, y: 0, width: window.innerWidth, height: halfHeight }); uiElements.forEach((el, idx) => { el.style.display = originalDisplays[idx] || ''; }); const dataUrl = canvas.toDataURL('image/png'); const result = await this.Swal.fire({ title: '📸 截图预览(上半部分)', html: `
截图范围:视口上半部分(高度 ${Math.round(halfHeight)}px)
`, showCancelButton: true, confirmButtonText: '💾 保存图片', cancelButtonText: '📋 复制图片', confirmButtonColor: '#4CAF50', cancelButtonColor: '#2196F3', reverseButtons: true, focusConfirm: false, width: 700, showCloseButton: true, }); if (result.isConfirmed) { const link = document.createElement('a'); link.download = `截图分享_${new Date().toISOString().slice(0,19).replace(/[:-]/g, '')}.png`; link.href = dataUrl; link.click(); this.Swal.fire({ title: '✅ 图片已保存', icon: 'success', timer: 1500, showConfirmButton: false }); } else if (result.dismiss === 'cancel') { try { const blob = await fetch(dataUrl).then(res => res.blob()); await navigator.clipboard.write([ new ClipboardItem({ [blob.type]: blob }) ]); this.Swal.fire({ title: '✅ 图片已复制', text: '已复制到剪贴板', icon: 'success', timer: 2000, showConfirmButton: false }); } catch (err) { this.Swal.fire({ title: '复制失败', text: '请使用保存功能。', icon: 'error' }); } } } catch (err) { console.error('截图失败:', err); uiElements.forEach((el, idx) => { el.style.display = originalDisplays[idx] || ''; }); this.Swal.fire({ title: '截图失败', text: err.message || '未知错误', icon: 'error' }); } } createUI() { if (!document.body) { console.warn('[整合版] document.body 尚不存在,稍后重试'); setTimeout(() => this.createUI(), 200); return; } console.log('[整合版] 开始创建UI...'); const style = document.createElement('style'); style.textContent = ` .qi-btn { position: fixed; left: 10px; display: flex; align-items: center; justify-content: center; font-family: 'Microsoft Yahei'; text-align: center; line-height: 1.2; padding: 0; z-index: 2147483647; border: none; cursor: pointer; transition: transform 0.2s, box-shadow 0.2s, background 0.3s; user-select: none; box-shadow: 0 2px 12px rgba(0,0,0,0.25); width: 70px; height: 70px; border-radius: 50%; font-size: 13px; font-weight: bold; color: #fff; } .qi-btn:hover { transform: scale(1.06); } .qi-btn:active { transform: scale(0.95); } #qi-guide-btn { top: 110px; background: linear-gradient(145deg, #FF6F00, #E65100); box-shadow: 0 4px 14px rgba(255,77,175,0.3); } #qi-screenshot-btn { top: 195px; background: linear-gradient(145deg, #9C27B0, #6A1B9A); box-shadow: 0 4px 14px rgba(156,39,176,0.3); } #qi-donate-btn { top: 280px; background: linear-gradient(145deg, #FF4081, #C2185B); box-shadow: 0 4px 14px rgba(255,64,129,0.3); } #qi-start-btn { top: 370px; width: 72px; height: 72px; border-radius: 50%; background: linear-gradient(145deg, #FF9800, #E65100); color: #fff; font-size: 12px; font-weight: bold; box-shadow: 0 4px 14px rgba(255,152,0,0.4); line-height: 1.3; border: 2px solid #fff; transition: background 0.3s, border-color 0.3s; display: flex; } #qi-start-btn.active { background: linear-gradient(145deg, #4CAF50, #2E7D32) !important; border-color: #4CAF50 !important; box-shadow: 0 0 20px rgba(76,175,80,0.6) !important; } `; document.head.appendChild(style); const btnData = [ { id: 'qi-guide-btn', text: '使用
指南' }, { id: 'qi-screenshot-btn', text: '截图
分享' }, { id: 'qi-donate-btn', text: '💖
打赏' }, { id: 'qi-start-btn', text: '🚀
开始
自动学习' } ]; btnData.forEach(({id, text}) => { const btn = document.createElement('div'); btn.id = id; btn.className = 'qi-btn'; btn.innerHTML = text; document.body.appendChild(btn); }); document.getElementById('qi-guide-btn').addEventListener('click', () => this.showGuide(true)); document.getElementById('qi-screenshot-btn').addEventListener('click', () => this.onScreenshotClick()); // ===== 打赏按钮:弹窗显示打赏码 ===== document.getElementById('qi-donate-btn').addEventListener('click', () => { Swal.fire({ title: '💖 感谢打赏', html: ``, confirmButtonText: '关闭', showCloseButton: true, }); }); document.getElementById('qi-start-btn').addEventListener('click', () => this.startFullLearn()); if (!this.authorized) { this.disableUI(); this.showUnauthorizedBanner(); } else { this.enableUI(); } console.log('[整合版] UI创建完成'); } } // ======================== 初始化 ======================== (function init() { if (document.readyState === 'complete' || document.readyState === 'interactive') { new SmartEduModule(); } else { window.addEventListener('DOMContentLoaded', () => { new SmartEduModule(); }); } })(); })();