// ==UserScript== // @name 安徽专业技术继续教育公需一学习助手 // @version 1.2.0 // @description 支持安徽专业技术继续教育公需一自动学习,免费1次体验,激活后可帮同事刷3次(1次=1个账号全部课程+考试),6.8元/3次超值刷课,全程后台静默运行,支持效率模拟人工倍速/1:1慢刷/狂暴极速3种模式,考试时长可调 // @author berain // @match https://www.zjzx.ah.cn/* // @icon https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png // @grant GM_openInTab // @grant GM_setClipboard // @antifeature payment // @antifeature membership // @tag 安徽专业技术继续教育 // @tag 安徽继续教育公需一 // @tag 自动学习 // @tag 课程辅助 // @tag 考试辅助 // @tag 继续教育 // @run-at document-end // @license LGPL-3.0 // ==/UserScript== (function() { 'use strict'; const API_BASE = 'https://1300815573-7g7cz1ts5w.ap-shanghai.tencentscf.com'; const QQ_GROUP_NUMBER = '1041375393'; const AUTHOR_QQ = '3352639207'; const QQ_GROUP_LINK = 'https://qm.qq.com/q/EBLDisQRBm'; const AUTHOR_ADD_LINK = 'https://qm.qq.com/q/C9M2GTbN5K'; const STORAGE_KEY_PANEL_POS = 'zjzx_panel_pos_v1'; const FREE_TRIAL_KEY = 'zjzx_free_trial_used'; let state = { taskId: null, running: false, courses: [], selected: [], logLines: [], cloudAuthorized: false, cloudRemaining: 0, cloudChecked: false, isLoggedIn: false, mode: 'efficiency', multiplier: 3, examDuration: 30, pollTimer: null, activeTab: 'course', currentCsId: null, videoCache: new Map(), refreshCounter: 0, lastCourseUpdate: 0, modalVisible: false, }; let displayedSet = new Set(); function getCookie(name) { const m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)')); return m ? decodeURIComponent(m[1]) : ''; } function getUserData() { try { const raw = getCookie('userData_lenye') || localStorage.getItem('userData_lenye'); if (raw) return JSON.parse(raw); } catch (_) {} return null; } function getCustId() { try { const raw = getCookie('userData_lenye') || localStorage.getItem('userData_lenye'); if (raw) { const data = JSON.parse(raw); return data.custId || ''; } } catch (_) {} return ''; } function getSessionToken() { const user = getUserData(); return getCookie('SESSION_TOKEN') || user?.SESSION_TOKEN || ''; } function getUUID() { const user = getUserData(); let uuid = getCookie('UUID') || user?.UUID || ''; if (!uuid && crypto.randomUUID) uuid = crypto.randomUUID(); return uuid; } function checkIsLoggedIn() { return !!(getCookie('SESSION_TOKEN') || getUserData()?.SESSION_TOKEN); } function log(msg) { const ts = new Date().toLocaleTimeString('zh-CN', { hour12: false }); const line = `${ts} ${msg}`; displayedSet.add(msg); state.logLines.push(line); if (state.logLines.length > 500) { state.logLines = state.logLines.slice(-500); } console.log(line); updateLogUI(); updateUI(); } function getModeLabel(mode) { const map = { 'realtime': '🐢 1:1慢刷', 'efficiency': '⚡ 效率', 'berserk': '💥 狂暴' }; return map[mode] || mode; } function isUnfinished(c) { const p = parseFloat(c.lcsProcess || 0); const hasExam = c.courseTestList && c.courseTestList.length > 0; const examDone = c.lcsExameFinished === '1'; return p < 100 || (hasExam && !examDone); } function getCourseStatus(c) { const p = parseFloat(c.lcsProcess || 0); const hasExam = c.courseTestList && c.courseTestList.length > 0; const examDone = c.lcsExameFinished === '1'; if (p < 100) return '⏳ 未学完'; if (hasExam && !examDone) return '📝 待考试'; return '✅ 已完成'; } // ================================================================ // 免费体验 - 调用后端接口 // ================================================================ async function grantFreeTrial(custId) { try { const res = await fetch(`${API_BASE}/free`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ custId }) }); const data = await res.json(); if (data.success) { state.cloudAuthorized = true; state.cloudRemaining = data.remaining || 1; state.cloudChecked = true; localStorage.setItem(FREE_TRIAL_KEY, '1'); log('🎁 首次免费体验已激活,剩余 1 次'); updateUI(); return true; } else { log(`⚠️ 免费体验发放失败: ${data.message || '未知错误'}`); return false; } } catch (e) { log(`⚠️ 免费体验请求失败: ${e.message}`); return false; } } async function callAPI(endpoint, payload = {}) { const custId = getCustId(); const sessionToken = getSessionToken(); const uuid = getUUID(); if (!custId) throw new Error('未登录,请刷新页面重试'); const body = JSON.stringify({ custId, sessionToken, uuid, ...payload }); const res = await fetch(`${API_BASE}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return await res.json(); } async function fetchVideos(csId) { if (state.videoCache.has(csId)) { return state.videoCache.get(csId); } try { const res = await callAPI('/videos', { csId }); const videos = res.data || []; state.videoCache.set(csId, videos); return videos; } catch (e) { console.error('获取视频列表失败:', e); return null; } } // ================================================================ // 授权检查(含免费体验发放) // ================================================================ async function checkAuth() { const custId = getCustId(); state.isLoggedIn = checkIsLoggedIn(); if (!state.isLoggedIn || !custId) { state.cloudChecked = true; state.cloudAuthorized = false; state.cloudRemaining = 0; state.isLoggedIn = false; updateUI(); return false; } try { const res = await fetch(`${API_BASE}/check?cust_id=${encodeURIComponent(custId)}`); const data = await res.json(); state.cloudAuthorized = data.authorized || false; state.cloudRemaining = data.remaining || 0; state.cloudChecked = true; const freeUsed = localStorage.getItem(FREE_TRIAL_KEY) === '1'; if (!state.cloudAuthorized && state.cloudRemaining === 0 && !freeUsed) { const granted = await grantFreeTrial(custId); if (granted) { const res2 = await fetch(`${API_BASE}/check?cust_id=${encodeURIComponent(custId)}`); const data2 = await res2.json(); state.cloudAuthorized = data2.authorized || false; state.cloudRemaining = data2.remaining || 0; state.cloudChecked = true; } } const freeUsedNow = localStorage.getItem(FREE_TRIAL_KEY) === '1'; if (state.cloudRemaining === 0 && !state.cloudAuthorized && freeUsedNow && !state.modalVisible) { setTimeout(() => { if (state.cloudRemaining === 0 && !state.cloudAuthorized && !state.modalVisible) { showPurchaseModal(); } }, 2000); } if (state.cloudRemaining > 0 && state.modalVisible) { closePurchaseModal(); } state.lastRemaining = state.cloudRemaining; return state.cloudAuthorized || state.cloudRemaining > 0; } catch (e) { state.cloudChecked = true; state.cloudAuthorized = false; state.cloudRemaining = 0; return false; } } async function activateKey(key) { const custId = getCustId(); if (!custId) return { success: false, message: '请先登录' }; const res = await fetch(`${API_BASE}/activate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ custId, key }) }); const result = await res.json(); if (result.success) { await checkAuth(); if (state.cloudRemaining > 0) { closePurchaseModal(); } } return result; } async function fetchCourses() { const res = await callAPI('/courses', {}); return res.data || []; } async function startTask(courseIds) { const res = await callAPI('/start', { courseIds, mode: state.mode, multiplier: state.multiplier, examDuration: state.examDuration }); if (!res.success) throw new Error(res.message); state.taskId = res.taskId; return res.taskId; } async function getTaskStatus() { if (!state.taskId) return null; const res = await callAPI('/status', { taskId: state.taskId }); return res; } async function stopTask() { if (!state.taskId) return; await callAPI('/stop', { taskId: state.taskId }); } // ================================================================ // 弹窗函数 // ================================================================ function showPurchaseModal() { if (document.getElementById('zjzx-purchase-modal-overlay')) { state.modalVisible = true; return; } state.modalVisible = true; const overlay = document.createElement('div'); overlay.id = 'zjzx-purchase-modal-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 99999999; background: rgba(0,0,0,0.7); display: flex; justify-content: center; align-items: center; backdrop-filter: blur(8px); animation: zjzxModalFadeIn 0.4s ease; `; const modal = document.createElement('div'); modal.style.cssText = ` background: linear-gradient(145deg, #ffffff, #fef3c7); border-radius: 32px; padding: 40px 48px 32px; max-width: 480px; width: 92%; box-shadow: 0 30px 80px rgba(180,83,9,0.5); text-align: center; border: 4px solid #f59e0b; position: relative; animation: zjzxModalBounce 0.5s ease; `; modal.innerHTML = `
🔒

次数已用完!

请购买密钥后继续使用

💬 加入QQ群获取帮助

📢 ${QQ_GROUP_NUMBER}

👤 直接添加作者QQ好友

QQ: ${AUTHOR_QQ}

关闭后自动跳转到「激活授权」面板

`; overlay.appendChild(modal); document.body.appendChild(overlay); document.getElementById('zjzx-modal-join-group').addEventListener('click', function(e) { e.stopPropagation(); try { if (typeof GM_openInTab === 'function') { GM_openInTab(QQ_GROUP_LINK, { active: true, insert: true, setParent: true }); } else { window.open(QQ_GROUP_LINK, '_blank'); } } catch (_) { window.open(QQ_GROUP_LINK, '_blank'); } }); document.getElementById('zjzx-modal-add-author').addEventListener('click', function(e) { e.stopPropagation(); try { if (typeof GM_openInTab === 'function') { GM_openInTab(AUTHOR_ADD_LINK, { active: true, insert: true, setParent: true }); } else { window.open(AUTHOR_ADD_LINK, '_blank'); } } catch (_) { window.open(AUTHOR_ADD_LINK, '_blank'); } }); document.getElementById('zjzx-modal-close').addEventListener('click', function(e) { e.stopPropagation(); closePurchaseModal(); switchTab('auth'); setTimeout(() => { const input = document.getElementById('zjzx-license-key'); if (input) { input.focus(); input.select(); input.style.borderColor = '#f59e0b'; input.style.boxShadow = '0 0 0 3px rgba(245,158,11,0.3)'; } }, 300); }); overlay.addEventListener('click', function(e) { if (e.target === overlay) { closePurchaseModal(); } }); if (!document.getElementById('zjzx-modal-styles')) { const style = document.createElement('style'); style.id = 'zjzx-modal-styles'; style.textContent = ` @keyframes zjzxModalFadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes zjzxModalBounce { 0% { transform: scale(0.7) translateY(30px); opacity: 0; } 60% { transform: scale(1.03) translateY(-5px); opacity: 1; } 100% { transform: scale(1) translateY(0); } } #zjzx-modal-join-group:hover { transform: scale(1.03); box-shadow: 0 6px 24px rgba(59,130,246,0.5); } #zjzx-modal-add-author:hover { transform: scale(1.03); box-shadow: 0 6px 24px rgba(245,158,11,0.5); } #zjzx-modal-close:hover { background: #fef3c7; border-color: #f59e0b; transform: scale(1.02); } `; document.head.appendChild(style); } state.modalVisible = true; } function closePurchaseModal() { const overlay = document.getElementById('zjzx-purchase-modal-overlay'); if (overlay) { overlay.remove(); } state.modalVisible = false; } // ================================================================ // UI 函数 // ================================================================ function updateUI() { const startBtn = document.getElementById('zjzx-start-btn'); const stopBtn = document.getElementById('zjzx-stop-btn'); const dot = document.getElementById('zjzx-status-dot'); const text = document.getElementById('zjzx-status-text'); const isLoggedIn = state.isLoggedIn; const canStart = isLoggedIn && state.cloudAuthorized && state.cloudRemaining > 0 && !state.running; if (startBtn) { startBtn.disabled = !canStart; startBtn.textContent = state.running ? '🏃 运行中' : '▶ 开始'; startBtn.style.opacity = canStart ? '1' : '0.6'; startBtn.title = !isLoggedIn ? '请先登录平台' : (!state.cloudAuthorized || state.cloudRemaining <= 0 ? '次数已用完,请购买密钥' : ''); } if (stopBtn) { stopBtn.disabled = !state.running; stopBtn.textContent = '⏹ 停止'; } if (dot) dot.className = state.running ? 'zjzx-dot-green' : 'zjzx-dot-gray'; if (text) { if (!isLoggedIn) { text.textContent = '🔓 免费体验1次(登录后使用)'; text.style.color = '#f59e0b'; } else if (!state.cloudChecked) { text.textContent = '⏳ 授权检查中...'; text.style.color = '#f59e0b'; } else if (!state.cloudAuthorized || state.cloudRemaining <= 0) { text.textContent = '🔒 次数已用完 (联系作者购买)'; text.style.color = '#dc2626'; } else { text.textContent = state.running ? '🏃 运行中...' : '⏸ 已停止'; text.style.color = '#1e293b'; } } if (state.activeTab === 'course') renderCourseList(); updateQueueSummary(); updateCurrentInfo(); updateAuthUI(); updateMiniAuth(); } function updateLogUI() { const box = document.getElementById('zjzx-run-log'); if (box) { const lines = state.logLines.slice(-100); box.innerHTML = lines.length ? lines.map(line => `
${line}
`).join('') : '
⏳ 等待日志输出...
'; box.scrollTop = box.scrollHeight; } const realtimeBox = document.getElementById('zjzx-realtime-log'); if (realtimeBox) { const lines = state.logLines.slice(-8); realtimeBox.innerHTML = lines.length ? lines.map(line => `
${line}
`).join('') : '
⏳ 等待日志...
'; realtimeBox.scrollTop = realtimeBox.scrollHeight; } updateAuthUI(); updateMiniAuth(); } function updateAuthUI() { const authStatus = document.getElementById('zjzx-auth-status'); const authRemaining = document.getElementById('zjzx-auth-remaining'); const authTip = document.getElementById('zjzx-auth-tip'); if (authStatus) { let statusText = ''; let color = ''; if (!state.isLoggedIn) { statusText = '✅ 可用 (1次免费)'; color = '#f59e0b'; } else if (!state.cloudChecked) { statusText = '⏳ 检查中...'; color = '#f59e0b'; } else if (state.cloudAuthorized && state.cloudRemaining > 0) { statusText = `✅ 可用 (${state.cloudRemaining}次)`; color = '#065f46'; } else { statusText = '🔒 次数已用完 (联系作者购买)'; color = '#dc2626'; } authStatus.textContent = statusText; authStatus.style.color = color; } if (authRemaining) { if (!state.isLoggedIn) { authRemaining.textContent = '1(登录后可用)'; authRemaining.style.color = '#f59e0b'; } else { authRemaining.textContent = String(state.cloudRemaining); authRemaining.style.color = state.cloudRemaining > 0 ? '#065f46' : '#dc2626'; } } if (authTip) { if (!state.cloudChecked) { authTip.textContent = ''; } else { authTip.textContent = '如有问题加入群聊或加 QQ:' + AUTHOR_QQ; authTip.style.textAlign = 'center'; authTip.style.fontSize = '14px'; authTip.style.fontWeight = 'bold'; authTip.style.color = '#92400e'; } } } // ================================================================ // ★★★ 修改:联系信息加上括号 ★★★ // ================================================================ function updateMiniAuth() { const mini = document.getElementById('zjzx-mini-auth'); if (!mini) return; let mainText = ''; let color = '#92400e'; // 联系信息用括号包裹 const contactText = '(如有问题加入群聊或加 QQ:' + AUTHOR_QQ + ')'; if (!state.isLoggedIn) { mainText = '🔑 授权状态:✅ 可用 (1次免费) ' + contactText; color = '#f59e0b'; } else if (!state.cloudChecked) { mainText = '⏳ 授权检查中...'; color = '#f59e0b'; } else if (state.cloudAuthorized && state.cloudRemaining > 0) { mainText = '🔑 授权状态:✅ 可用 (' + state.cloudRemaining + '次) ' + contactText; color = '#065f46'; } else { mainText = '🔑 授权状态:🔒 次数已用完 ' + contactText; color = '#dc2626'; } mini.textContent = mainText; mini.style.color = color; } function updateQueueSummary() { const doneEl = document.getElementById('zjzx-queue-done'); const totalEl = document.getElementById('zjzx-queue-total'); const pctEl = document.getElementById('zjzx-queue-percent'); const barEl = document.getElementById('zjzx-queue-progress'); const examEl = document.getElementById('zjzx-queue-exam'); const total = state.courses.length; const done = state.courses.filter(c => parseFloat(c.lcsProcess || 0) >= 100).length; const pct = total > 0 ? Math.round((done / total) * 100) : 0; if (doneEl) doneEl.textContent = String(done); if (totalEl) totalEl.textContent = String(total); if (pctEl) pctEl.textContent = `${pct}%`; if (barEl) barEl.style.width = `${pct}%`; if (examEl) { const examTotal = state.courses.filter(c => c.courseTestList && c.courseTestList.length > 0).length; const examDone = state.courses.filter(c => c.lcsExameFinished === '1').length; examEl.textContent = examTotal > 0 ? `${examDone} / ${examTotal}` : '—'; } } function updateCurrentInfo() { const courseEl = document.getElementById('zjzx-current-course'); const chapterEl = document.getElementById('zjzx-current-chapter'); const taskEl = document.getElementById('zjzx-current-task'); if (!courseEl) return; const currentCourse = state.courses.find(c => c.csId === state.currentCsId); courseEl.textContent = currentCourse?.csName || '无'; chapterEl.textContent = getModeLabel(state.mode); if (state.running) taskEl.textContent = '🏃 运行中...'; else { const selectedCount = state.selected.length; taskEl.textContent = selectedCount > 0 ? `📚 已选 ${selectedCount} 门课程` : '👉 点开始后自动提交'; } } function renderCourseList() { const list = document.getElementById('zjzx-course-list'); if (!list) return; const sortedCourses = [...state.courses].sort((a, b) => { const aUnfinished = isUnfinished(a) ? 0 : 1; const bUnfinished = isUnfinished(b) ? 0 : 1; return aUnfinished - bUnfinished; }); if (!sortedCourses.length) { list.innerHTML = `
📭 当前年度计划暂无课程
可切换上方培训计划查看其他年度
`; return; } list.innerHTML = sortedCourses.map(c => { const pct = parseFloat(c.lcsProcess || 0).toFixed(1); const checked = state.selected.includes(c.csId) ? 'checked' : ''; const statusText = getCourseStatus(c); let statusClass = ''; let statusIcon = ''; if (statusText.includes('待考试')) { statusClass = 'exam'; statusIcon = '📝'; } else if (statusText.includes('未学完')) { statusClass = 'learning'; statusIcon = '⏳'; } else { statusClass = 'done'; statusIcon = '✅'; } return ``; }).join(''); list.querySelectorAll('input[type=checkbox]').forEach(el => { el.addEventListener('change', function() { const id = this.dataset.csid; if (this.checked) { if (!state.selected.includes(id)) state.selected.push(id); } else { state.selected = state.selected.filter(s => s !== id); } updateUI(); }); }); } async function renderChapterPreview() { const box = document.getElementById('zjzx-chapter-preview'); if (!box) return; let targetCourses = state.courses.filter(c => state.selected.includes(c.csId)); if (!targetCourses.length) { targetCourses = state.courses.filter(c => isUnfinished(c)); } if (!targetCourses.length) { box.innerHTML = '
📖 请先选择课程,或当前无未完成课程
'; return; } let html = ''; for (const course of targetCourses) { try { state.videoCache.delete(course.csId); const videos = await fetchVideos(course.csId); const statusText = getCourseStatus(course); const pct = parseFloat(course.lcsProcess || 0).toFixed(1); if (videos === null) { html += `
📺 ${course.csName} 获取失败
⚠️ 获取视频列表失败,请重试
`; continue; } if (videos.length === 0) { const hasExam = course.courseTestList && course.courseTestList.length > 0; const examDone = course.lcsExameFinished === '1'; let extraInfo = '📭 无视频'; if (hasExam && !examDone) extraInfo += '(仅考试,待完成)'; else if (hasExam && examDone) extraInfo += '(仅考试,已通过 ✅)'; else extraInfo += '(仅考试)'; html += `
📺 ${course.csName} ${extraInfo} ${statusText} ${pct}%
${hasExam ? (examDone ? '✅ 考试已通过' : '📝 待考试') : '无考试'}
`; } else { const items = videos.map(v => { const done = v.studyFinished === '1' || v.process >= 100; const mark = done ? '✅' : '⏳'; const pct2 = Math.round(v.process || 0); const name = v.resName || v.resId || '未命名视频'; const displayName = name.length > 25 ? name.slice(0, 22) + '...' : name; return `
${mark} ${displayName} ${done ? '✅' : pct2 + '%'}
`; }).join(''); const hasExam = course.courseTestList && course.courseTestList.length > 0; const examDone = course.lcsExameFinished === '1'; let examInfo = ''; if (hasExam) { examInfo = examDone ? '📝 考试已通过 ✅' : '📝 待考试'; } html += `
📺 ${course.csName} ${statusText} ${pct}% ${examInfo ? '| ' + examInfo : ''}
${items}
`; } } catch (e) { html += `
❌ ${course.csName}
加载出错: ${e.message}
`; } } box.innerHTML = html || '
📭 暂无课程数据
'; } function switchTab(tab) { state.activeTab = tab; document.querySelectorAll('.zjzx-tab-btn').forEach(el => { el.classList.toggle('active', el.dataset.tab === tab); }); document.querySelectorAll('.zjzx-pane').forEach(el => { el.classList.toggle('active', el.dataset.pane === tab); }); const realtimeArea = document.getElementById('zjzx-realtime-area'); if (realtimeArea) realtimeArea.style.display = (tab === 'course') ? '' : 'none'; if (tab === 'log') updateLogUI(); if (tab === 'chapter') renderChapterPreview(); } let refreshingCourses = false; async function refreshCourseListData(force = false) { if (refreshingCourses) return; const now = Date.now(); if (!force && (now - state.lastCourseUpdate) < 3000) return; refreshingCourses = true; try { const courses = await fetchCourses(); const oldSelected = state.selected; state.courses = courses; const currentIds = new Set(courses.map(c => c.csId)); state.selected = oldSelected.filter(id => currentIds.has(id)); state.lastCourseUpdate = now; state.videoCache.clear(); renderCourseList(); updateUI(); updateQueueSummary(); } catch (e) { console.warn('刷新课程列表失败:', e.message); } finally { refreshingCourses = false; } } async function autoSelectUnfinished() { await refreshCourseListData(true); const unfinishedIds = state.courses.filter(c => isUnfinished(c)).map(c => c.csId); state.selected = unfinishedIds; renderCourseList(); updateUI(); updateQueueSummary(); if (unfinishedIds.length > 0) { log(`🎯 已自动勾选 ${unfinishedIds.length} 门未完成课程`); } else { log('🎯 所有课程已完成,无需勾选'); } } // ================================================================ // 启动 / 停止 // ================================================================ async function startStudy() { if (state.running) return; if (!state.cloudAuthorized || state.cloudRemaining <= 0) { log('🔒 授权次数已用完,请激活或联系作者购买'); setTimeout(() => showPurchaseModal(), 500); updateUI(); return; } if (!state.selected.length) { log('⚠️ 请勾选课程'); return; } try { await refreshCourseListData(true); const taskId = await startTask(state.selected); state.running = true; state.taskId = taskId; displayedSet = new Set(); state.refreshCounter = 0; updateUI(); log(`🚀 任务已启动,ID: ${taskId}`); if (state.pollTimer) clearInterval(state.pollTimer); state.pollTimer = setInterval(async () => { try { const status = await getTaskStatus(); if (!status) return; if (status.logs && status.logs.length) { status.logs.forEach(msg => { if (!displayedSet.has(msg)) { log(msg); } }); } state.refreshCounter++; if (state.refreshCounter % 2 === 0) { await refreshCourseListData(false); } if (status.status === 'completed') { state.running = false; clearInterval(state.pollTimer); state.pollTimer = null; log('🎉 所有课程学习完成!'); await refreshCourseListData(true); updateUI(); await callAPI('/use', {}); await checkAuth(); updateUI(); } else if (status.status === 'stopped') { state.running = false; clearInterval(state.pollTimer); state.pollTimer = null; log('⏹ 已停止'); await refreshCourseListData(true); updateUI(); } else if (status.status === 'error') { state.running = false; clearInterval(state.pollTimer); state.pollTimer = null; log('❌ 任务出错,请查看日志'); await refreshCourseListData(true); updateUI(); } updateUI(); } catch (e) { console.warn('轮询状态失败:', e.message); } }, 3000); } catch (e) { log('❌ 启动失败:' + e.message); } } async function stopStudy() { if (!state.running) return; try { await stopTask(); state.running = false; if (state.pollTimer) { clearInterval(state.pollTimer); state.pollTimer = null; } log('⏹ 已发送停止指令'); await refreshCourseListData(true); updateUI(); } catch (e) { log('❌ 停止失败:' + e.message); } } // ================================================================ // 创建 UI 面板 // ================================================================ function createUI() { if (document.getElementById('free-zjzx-panel')) return; const style = document.createElement('style'); style.textContent = ` #free-zjzx-panel { position: fixed; right: 20px; top: 80px; z-index: 999999; width: 420px; max-height: calc(100vh - 120px); background: #fffbf5; border-radius: 16px; box-shadow: 0 8px 32px rgba(180,83,9,0.18); font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif; font-size: 13px; color: #1e293b; display: flex; flex-direction: column; border: 2px solid #fcd34d; overflow: hidden; } .zjzx-panel-header { background: linear-gradient(135deg, #f59e0b, #f97316); color: #fff; padding: 10px 16px; cursor: move; user-select: none; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } .zjzx-panel-header .zjzx-title { font-size: 15px; font-weight: 800; } .zjzx-panel-header .zjzx-close-btn { background: rgba(255,255,255,0.2); border: none; color: #fff; font-size: 18px; cursor: pointer; padding: 0 8px; border-radius: 6px; } .zjzx-status-card { background: #fffbeb; margin: 6px 10px 4px; padding: 6px 10px; border-radius: 10px; border: 2px solid #fde68a; flex-shrink: 0; } .zjzx-status-row { display: flex; justify-content: space-between; } .zjzx-status-label { font-size: 11px; color: #92400e; font-weight: 700; } .zjzx-status-badge { font-size: 11px; font-weight: 700; color: #78350f; background: #fff; padding: 1px 10px; border-radius: 20px; border: 1px solid #fcd34d; } .zjzx-status-main { display: flex; justify-content: space-between; align-items: center; margin-top: 2px; } .zjzx-status-metrics { display: flex; align-items: center; gap: 4px; font-size: 12px; color: #78350f; } .zjzx-status-metrics em { font-style: normal; font-weight: 800; color: #92400e; } .zjzx-progress-pct { font-size: 16px; font-weight: 800; color: #f97316; } .zjzx-progress-bar { height: 5px; background: #fef3c7; border-radius: 4px; margin-top: 3px; overflow: hidden; border: 1px solid #fde68a; } .zjzx-progress-bar span { display: block; height: 100%; background: linear-gradient(90deg, #f59e0b, #f97316); border-radius: 4px; transition: width 0.4s ease; width: 0%; } .zjzx-mini-auth { font-size: 10px; color: #92400e; text-align: center; margin-top: 3px; font-weight: 600; } .zjzx-controls { display: flex; gap: 6px; margin: 3px 10px 5px; padding: 6px 10px; flex-wrap: wrap; align-items: center; background: #fffbeb; border-radius: 10px; border: 2px solid #fde68a; flex-shrink: 0; } .zjzx-controls select { padding: 5px 8px; border-radius: 8px; border: 2px solid #fde68a; background: #fff; font-size: 12px; font-weight: 700; color: #92400e; flex: 1.2; min-width: 80px; height: 32px; } .zjzx-controls input[type="number"] { width: 46px; padding: 4px 4px; border-radius: 8px; border: 2px solid #fde68a; background: #fff; font-size: 13px; text-align: center; color: #92400e; font-weight: 700; height: 32px; } .zjzx-controls .zjzx-control-label { font-size: 12px; color: #92400e; font-weight: 700; } .zjzx-btn { padding: 5px 12px; border-radius: 10px; border: none; font-size: 13px; font-weight: 800; cursor: pointer; transition: all 0.25s; flex: 1; min-width: 50px; height: 32px; display: flex; align-items: center; justify-content: center; } .zjzx-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; } .zjzx-btn-start { background: linear-gradient(135deg, #f59e0b, #f97316); color: #fff; box-shadow: 0 2px 8px rgba(245,158,11,0.3); } .zjzx-btn-start:hover:not(:disabled) { transform: translateY(-2px) scale(1.02); box-shadow: 0 4px 16px rgba(245,158,11,0.4); } .zjzx-btn-stop { background: #ef4444; color: #fff; box-shadow: 0 2px 8px rgba(239,68,68,0.25); } .zjzx-btn-stop:hover:not(:disabled) { transform: translateY(-2px) scale(1.02); box-shadow: 0 4px 16px rgba(239,68,68,0.35); } .zjzx-settings-row { display: flex; align-items: center; gap: 12px; padding: 3px 10px 5px; background: #fffbeb; border-radius: 8px; border: 1px solid #fde68a; margin: 2px 10px 4px; flex-wrap: nowrap; } .zjzx-settings-row .exam-group { display: flex; align-items: center; gap: 8px; flex: 0 1 auto; } .zjzx-settings-row .exam-group label { font-size: 12px; font-weight: 700; color: #92400e; display: flex; align-items: center; gap: 4px; } .zjzx-settings-row .exam-group input[type="range"] { flex: 0 0 80px; min-width: 60px; accent-color: #f59e0b; } .zjzx-settings-row .exam-group .exam-value { font-size: 13px; font-weight: 800; color: #f97316; min-width: 30px; text-align: center; } .zjzx-settings-row .sync-group { display: flex; align-items: center; gap: 12px; margin-left: auto; flex: 0 0 auto; } .zjzx-settings-row .sync-group label { font-size: 12px; font-weight: 700; color: #92400e; display: flex; align-items: center; gap: 4px; cursor: pointer; } .zjzx-settings-row .sync-group .sync-label { font-size: 12px; font-weight: 700; color: #92400e; cursor: pointer; } .zjzx-settings-row .sync-group .manual-sync { font-size: 12px; font-weight: 700; color: #92400e; cursor: pointer; padding: 2px 4px; user-select: none; background: transparent; border: none; outline: none; } .zjzx-settings-row .sync-group .manual-sync:hover { color: #b45309; text-decoration: underline; } .zjzx-tabbar { display: flex; gap: 2px; padding: 0 10px; margin-top: 2px; flex-shrink: 0; border-bottom: 2px solid #fde68a; } .zjzx-tab-btn { flex: 1; border: none; background: transparent; padding: 5px 4px; font-size: 12px; font-weight: 700; color: #b45309; cursor: pointer; border-bottom: 3px solid transparent; transition: all 0.2s; border-radius: 0; } .zjzx-tab-btn:hover { color: #78350f; background: #fffbeb; } .zjzx-tab-btn.active { color: #92400e; border-bottom-color: #f59e0b; background: #fffbeb; } .zjzx-panel-body { flex: 1; overflow-y: auto; padding: 4px 10px 2px; min-height: 0; } .zjzx-pane { display: none; } .zjzx-pane.active { display: block; } .zjzx-course-toolbar { display: flex; gap: 4px; margin-bottom: 4px; flex-wrap: wrap; align-items: center; } .zjzx-course-toolbar button { padding: 2px 10px; border-radius: 20px; border: 2px solid #fde68a; background: #fffbeb; font-size: 11px; cursor: pointer; font-weight: 700; color: #92400e; transition: all 0.2s; } .zjzx-course-toolbar button:hover { background: #fef3c7; border-color: #f59e0b; transform: scale(1.02); } .zjzx-course-toolbar button.zjzx-btn-unfinished { background: #fef3c7; border-color: #f59e0b; color: #b45309; } .zjzx-course-toolbar .zjzx-toolbar-hint { font-size: 10px; color: #b45309; margin-left: auto; font-weight: 600; } .zjzx-course-list { max-height: 120px; overflow-y: auto; border: 2px solid #fde68a; border-radius: 10px; padding: 3px 6px; background: #fffbeb; margin-bottom: 4px; } .zjzx-course-item { display: flex; align-items: center; gap: 6px; padding: 3px 4px; border-bottom: 1px solid #fef3c7; font-size: 12px; cursor: pointer; border-radius: 6px; transition: background 0.15s; } .zjzx-course-item:last-child { border-bottom: none; } .zjzx-course-item:hover { background: #fef3c7; } .zjzx-course-item input[type="checkbox"] { flex-shrink: 0; cursor: pointer; accent-color: #f59e0b; width: 15px; height: 15px; } .zjzx-course-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; color: #1e293b; font-size: 12px; } .zjzx-course-progress { font-size: 11px; color: #92400e; flex-shrink: 0; margin-left: 4px; font-weight: 700; } .zjzx-course-status { font-size: 10px; font-weight: 700; padding: 1px 8px; border-radius: 20px; flex-shrink: 0; border: 1px solid #fde68a; } .zjzx-course-status.learning { background: #fef3c7; color: #92400e; border-color: #fcd34d; } .zjzx-course-status.exam { background: #dbeafe; color: #1d4ed8; border-color: #93c5fd; } .zjzx-course-status.done { background: #d1fae5; color: #065f46; border-color: #6ee7b7; } .zjzx-refresh-btn { font-size: 11px; padding: 2px 10px; border-radius: 20px; border: 2px solid #fde68a; background: #fffbeb; cursor: pointer; font-weight: 700; color: #92400e; transition: all 0.2s; } .zjzx-refresh-btn:hover { background: #fef3c7; border-color: #f59e0b; } .zjzx-empty-state { padding: 10px 8px; text-align: center; color: #b45309; font-size: 13px; line-height: 1.8; } .zjzx-chapter-course { margin-bottom: 6px; border: 2px solid #fde68a; border-radius: 10px; overflow: hidden; } .zjzx-chapter-title { padding: 5px 10px; background: #fffbeb; font-weight: 700; font-size: 12px; color: #92400e; border-bottom: 1px solid #fde68a; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; } .zjzx-chapter-item { display: flex; justify-content: space-between; padding: 3px 10px; font-size: 12px; color: #78350f; border-bottom: 1px solid #fef3c7; } .zjzx-chapter-item:last-child { border-bottom: none; } .zjzx-log-box { background: #fffbeb; color: #78350f; padding: 6px 8px; border-radius: 10px; font-size: 11px; max-height: 200px; overflow-y: auto; font-family: monospace; margin-top: 4px; white-space: pre-wrap; word-break: break-all; border: 2px solid #fde68a; } .zjzx-log-row { padding: 2px 0; border-bottom: 1px solid #fef3c7; font-size: 11px; color: #78350f; } .zjzx-log-actions { display: flex; gap: 6px; margin-bottom: 4px; } .zjzx-log-actions button { font-size: 11px; padding: 2px 10px; border-radius: 12px; border: 1px solid #fde68a; background: #fffbeb; cursor: pointer; color: #92400e; font-weight: 700; transition: all 0.2s; } .zjzx-log-actions button:hover { background: #fef3c7; } .zjzx-realtime-area { border-top: 2px solid #fde68a; padding-top: 3px; margin-top: 3px; flex-shrink: 0; background: #fffbeb; padding-left: 10px; padding-right: 10px; padding-bottom: 4px; } .zjzx-realtime-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2px; } .zjzx-realtime-header span { font-size: 11px; font-weight: 700; color: #92400e; } .zjzx-realtime-header button { font-size: 10px; padding: 1px 8px; border-radius: 12px; border: 1px solid #fde68a; background: #fff; cursor: pointer; color: #92400e; font-weight: 600; } .zjzx-realtime-header button:hover { background: #fef3c7; } .zjzx-realtime-log-box { background: #fffbeb; color: #78350f; padding: 4px 8px; border-radius: 8px; font-size: 10px; max-height: 60px; overflow-y: auto; font-family: monospace; white-space: pre-wrap; word-break: break-all; border: 1px solid #fde68a; } .zjzx-realtime-log-row { padding: 1px 0; border-bottom: 1px solid #fef3c7; font-size: 10px; color: #78350f; } .zjzx-realtime-log-row:last-child { border-bottom: none; } .zjzx-current-card { background: #fffbeb; border: 2px solid #fde68a; border-radius: 10px; padding: 4px 10px; margin: 3px 0 3px; } .zjzx-current-row { display: flex; justify-content: space-between; font-size: 12px; padding: 1px 0; } .zjzx-current-row .zjzx-label { color: #92400e; font-weight: 600; } .zjzx-current-row .zjzx-value { font-weight: 700; color: #78350f; text-align: right; max-width: 60%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .zjzx-auth-pane { padding: 6px 4px; } .zjzx-auth-item { display: flex; justify-content: space-between; align-items: center; padding: 6px 8px; border-bottom: 1px solid #fde68a; font-size: 13px; } .zjzx-auth-item .label { color: #92400e; font-weight: 600; } .zjzx-auth-item .value { font-weight: 700; color: #78350f; } .zjzx-auth-tip { text-align: center; font-size: 14px; font-weight: bold; color: #92400e; padding: 4px 0; } .zjzx-auth-activate { display: flex; gap: 4px; margin-top: 6px; flex-wrap: wrap; } .zjzx-auth-activate input { flex: 1; padding: 4px 6px; border-radius: 6px; border: 1px solid #fde68a; font-size: 12px; min-width: 80px; } .zjzx-auth-activate button { padding: 4px 12px; border-radius: 20px; border: 2px solid #f59e0b; background: #fef3c7; font-size: 12px; font-weight: 700; color: #92400e; cursor: pointer; } .zjzx-auth-activate button:hover { background: #fde68a; } .zjzx-footer-buy { display: flex; align-items: center; justify-content: space-between; padding: 6px 10px; background: #fffbeb; border-top: 2px solid #fde68a; flex-shrink: 0; gap: 6px; flex-wrap: wrap; } .zjzx-footer-buy .buy-label { font-size: 12px; font-weight: 700; color: #92400e; } .zjzx-footer-buy .buy-buttons { display: flex; gap: 6px; } .zjzx-footer-buy .buy-btn { padding: 2px 10px; border-radius: 16px; border: 1px solid #f59e0b; background: #fef3c7; color: #92400e; font-weight: 700; font-size: 12px; cursor: pointer; transition: background 0.2s; } .zjzx-footer-buy .buy-btn:hover { background: #fde68a; } .zjzx-dot-green { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: #22c55e; margin-right: 4px; box-shadow: 0 0 8px rgba(34,197,94,0.4); } .zjzx-dot-gray { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: #94a3b8; margin-right: 4px; } #free-zjzx-panel ::-webkit-scrollbar { width: 4px; } #free-zjzx-panel ::-webkit-scrollbar-track { background: transparent; } #free-zjzx-panel ::-webkit-scrollbar-thumb { background: #fcd34d; border-radius: 4px; } #free-zjzx-panel ::-webkit-scrollbar-thumb:hover { background: #f59e0b; } `; document.head.appendChild(style); const panel = document.createElement('div'); panel.id = 'free-zjzx-panel'; panel.innerHTML = `
📘 安徽专业技术继续教育公需一学习助手 v1.2 berain
🎯 运行状态 ⏸ 已停止
0/0 视频已学习 · 📝 考试
0%
⏳ 授权检查中...
30s
⟳ 手动同步
⏳未学完 📝待考试 ✅已完成
⏳ 加载中...
📚 当前课程
⚙️ 当前模式 ⚡ 效率模式
📌 当前任务 👉 点开始后自动提交
📖 请先选择课程
⏳ 等待日志输出...
🔑 授权状态 ⏳ 检查中...
📊 剩余次数 -
💡 激活密钥
📡 实时日志
⏳ 等待日志...
`; document.body.appendChild(panel); // ---- 拖动逻辑 ---- const header = document.getElementById('zjzx-panel-header'); let isDragging = false; let startX, startY, startLeft, startTop; try { const pos = JSON.parse(localStorage.getItem(STORAGE_KEY_PANEL_POS)); if (pos && pos.left !== undefined && pos.top !== undefined) { panel.style.left = pos.left + 'px'; panel.style.top = pos.top + 'px'; panel.style.right = 'auto'; } } catch (_) {} header.addEventListener('mousedown', (e) => { if (e.target.closest('button')) return; isDragging = true; const rect = panel.getBoundingClientRect(); startX = e.clientX; startY = e.clientY; startLeft = rect.left; startTop = rect.top; panel.style.transition = 'none'; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); e.preventDefault(); }); function onMouseMove(e) { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; let newLeft = startLeft + dx; let newTop = startTop + dy; const maxX = window.innerWidth - panel.offsetWidth; const maxY = window.innerHeight - panel.offsetHeight; newLeft = Math.max(0, Math.min(newLeft, maxX)); newTop = Math.max(0, Math.min(newTop, maxY)); panel.style.left = newLeft + 'px'; panel.style.top = newTop + 'px'; panel.style.right = 'auto'; } function onMouseUp() { if (isDragging) { isDragging = false; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); const rect = panel.getBoundingClientRect(); localStorage.setItem(STORAGE_KEY_PANEL_POS, JSON.stringify({ left: rect.left, top: rect.top })); panel.style.transition = ''; } } // ---- 事件绑定 ---- document.querySelectorAll('.zjzx-tab-btn').forEach(el => { el.addEventListener('click', function() { switchTab(this.dataset.tab); }); }); document.getElementById('zjzx-select-all').addEventListener('click', function() { const allIds = state.courses.map(c => c.csId); state.selected = allIds.slice(); renderCourseList(); updateUI(); log(`✅ 已全选 ${allIds.length} 门课程`); }); document.getElementById('zjzx-deselect-all').addEventListener('click', function() { state.selected = []; renderCourseList(); updateUI(); log('❌ 已取消全选'); }); document.getElementById('zjzx-select-unfinished').addEventListener('click', function() { const unfinishedIds = state.courses.filter(c => isUnfinished(c)).map(c => c.csId); if (!unfinishedIds.length) { log('🎯 所有课程已完成,无需勾选'); return; } state.selected = unfinishedIds.slice(); renderCourseList(); updateUI(); log(`🎯 已勾选 ${unfinishedIds.length} 门未完成课程(未学完 + 待考试)`); }); document.getElementById('zjzx-refresh-courses').addEventListener('click', function() { autoSelectUnfinished(); }); document.getElementById('zjzx-refresh-courses-extra').addEventListener('click', function() { autoSelectUnfinished(); }); document.getElementById('zjzx-mode-select').addEventListener('change', function() { state.mode = this.value; document.getElementById('zjzx-current-chapter').textContent = getModeLabel(state.mode); log(`切换为 ${getModeLabel(state.mode)}`); }); document.getElementById('zjzx-multiplier-input').addEventListener('change', function() { let val = parseFloat(this.value); if (isNaN(val) || val < 1) val = 1; if (val > 10) val = 10; state.multiplier = val; this.value = val; log(`✖ 倍率设置为 ${val}`); }); const examSlider = document.getElementById('zjzx-exam-slider'); const examDisplay = document.getElementById('zjzx-exam-display'); examSlider.addEventListener('input', function() { const val = parseInt(this.value, 10); examDisplay.textContent = val + 's'; state.examDuration = val; localStorage.setItem('zjzx_exam_duration', String(val)); log(`📝 考试时长设置为 ${val} 秒`); }); document.getElementById('zjzx-auto-sync').addEventListener('change', function() { localStorage.setItem('zjzx_auto_sync', this.checked ? '1' : '0'); log(`🔄 自动同步 ${this.checked ? '已开启' : '已关闭'}`); }); // ---- 激活按钮 ---- document.getElementById('zjzx-activate-btn').addEventListener('click', async function() { const keyInput = document.getElementById('zjzx-license-key'); const key = keyInput.value.trim(); if (!key) { alert('请输入密钥'); return; } const btn = this; btn.textContent = '⏳ 验证中...'; btn.disabled = true; try { const result = await activateKey(key); if (result.success) { alert('✅ ' + result.message); keyInput.value = ''; await checkAuth(); updateUI(); log('🔑 授权已更新:剩余 ' + state.cloudRemaining + ' 次'); if (state.modalVisible) { closePurchaseModal(); } } else { alert('❌ ' + result.message); } } catch (e) { alert('❌ 激活失败:' + e.message); } finally { btn.textContent = '激活'; btn.disabled = false; } }); document.getElementById('zjzx-buy-key-btn').addEventListener('click', function() { const text = AUTHOR_QQ; if (typeof GM_setClipboard === 'function') { GM_setClipboard(text, 'text'); } else if (navigator.clipboard) { navigator.clipboard.writeText(text); } else { const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); } alert('已复制作者QQ号:' + AUTHOR_QQ); window.open(QQ_GROUP_LINK, '_blank'); }); document.getElementById('zjzx-footer-qq-link').addEventListener('click', function() { try { if (typeof GM_openInTab === 'function') { GM_openInTab(QQ_GROUP_LINK, { active: true, insert: true, setParent: true }); } else { window.open(QQ_GROUP_LINK, '_blank'); } } catch (_) { window.open(QQ_GROUP_LINK, '_blank'); } }); document.getElementById('zjzx-footer-copy-qq').addEventListener('click', function() { const text = AUTHOR_QQ; if (typeof GM_setClipboard === 'function') { GM_setClipboard(text, 'text'); } else if (navigator.clipboard) { navigator.clipboard.writeText(text); } else { const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); } alert('已复制作者QQ号:' + AUTHOR_QQ); }); document.getElementById('zjzx-start-btn').addEventListener('click', startStudy); document.getElementById('zjzx-stop-btn').addEventListener('click', stopStudy); document.getElementById('zjzx-panel-toggle').addEventListener('click', function() { const body = document.querySelector('#free-zjzx-panel .zjzx-panel-body'); const statusCard = document.querySelector('#free-zjzx-panel .zjzx-status-card'); const controls = document.querySelector('#free-zjzx-panel .zjzx-controls'); const settings = document.querySelector('#free-zjzx-panel .zjzx-settings-row'); const tabbar = document.querySelector('#free-zjzx-panel .zjzx-tabbar'); const realtime = document.querySelector('#free-zjzx-panel .zjzx-realtime-area'); const footer = document.querySelector('#free-zjzx-panel .zjzx-footer-buy'); if (body.style.display === 'none') { body.style.display = ''; statusCard.style.display = ''; controls.style.display = ''; settings.style.display = ''; tabbar.style.display = ''; realtime.style.display = ''; footer.style.display = ''; this.textContent = '−'; } else { body.style.display = 'none'; statusCard.style.display = 'none'; controls.style.display = 'none'; settings.style.display = 'none'; tabbar.style.display = 'none'; realtime.style.display = 'none'; footer.style.display = 'none'; this.textContent = '+'; } }); document.getElementById('zjzx-copy-log').addEventListener('click', function() { const text = state.logLines.join('\n'); navigator.clipboard.writeText(text).then(() => alert('📋 日志已复制')).catch(() => { const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); alert('📋 日志已复制'); }); }); document.getElementById('zjzx-clear-log').addEventListener('click', function() { state.logLines = []; displayedSet = new Set(); updateLogUI(); log('🗑️ 完整日志已清空'); }); document.getElementById('zjzx-clear-realtime').addEventListener('click', function() { const realtimeBox = document.getElementById('zjzx-realtime-log'); if (realtimeBox) { realtimeBox.innerHTML = '
⏳ 已清空
'; } }); // ---- 初始化 ---- refreshCourseListData(true).then(() => autoSelectUnfinished()); (async function initAuth() { await checkAuth(); updateUI(); if (state.cloudAuthorized && state.cloudRemaining > 0) { log(`📊 剩余免费次数:${state.cloudRemaining}`); } else { const freeUsed = localStorage.getItem(FREE_TRIAL_KEY) === '1'; if (!freeUsed) { log('🔒 免费次数已用完,请购买密钥激活 (作者QQ:' + AUTHOR_QQ + ')'); } else { log('🔒 免费次数已用完,请购买密钥激活 (作者QQ:' + AUTHOR_QQ + ')'); } setTimeout(() => { const freeUsedNow = localStorage.getItem(FREE_TRIAL_KEY) === '1'; if (!state.modalVisible && state.cloudRemaining === 0 && freeUsedNow) { showPurchaseModal(); } }, 3000); } })(); updateUI(); updateLogUI(); log('🌈 安徽专业技术继续教育公需一学习助手 v1.2 已加载'); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createUI); } else { createUI(); } })();