💎 学习权益
查看当前可用次数,输入会员码或积分码继续使用
📖 快速上手
1填档案
点顶部「档案设置」,填上目标分、选科和复习周期,回答会更贴近你的真实情况。
2免费试用 / 兑换
默认可免费试用 5 次;如果觉得合适,再去「💎兑换」输入会员码或积分码继续用。
3开问
可以直接发题目,也可以上传 PDF、Word 或图片,让 AI 帮你讲题、批改、总结。
4快捷
顶部的「讲解 / 出题 / 总结 / 批改 / 生成复习计划」能一键发送,省掉重复输入。
这是一款面向高考复习的 AI 学习工具,适合讲题、出题、批改和整理错题。先免费试用,觉得顺手再兑换继续用。
📅 高考考试时间安排
第一天 · 6月7日- 语文 09:00–11:30
- 数学 15:00–17:00
第二天 · 6月8日- 物理 / 历史 09:00–10:15
- 外语 15:00–17:00
第三天 · 6月9日(选考)- 化学 08:30–10:00
- 思想政治 11:00–12:30
- 地理 15:00–16:30
- 生物学 17:40–19:10
以上为「3+1+2」新高考普遍安排示例,具体科目与时间以你所在省份准考证为准。
💡 备考建议
- 制定每日复习计划:按周划分基础→强化→冲刺,落实到每天每科。
- 限时刷真题:按考试时长掐表做,训练节奏与抗压。
- 整理错题本:不只抄答案,标注错因与正确思路。
- 保证睡眠与运动:脑子比时长重要,别熬垮身体。
- 模拟考场环境:周末按真实时段做套卷,找手感。
- 关注政策与准考证:考点、携带物品提前确认。
高考AI
讲题更快,错题更清楚,复习更有方向
距离高考
--天
--时
--分
--秒
你的学习档案(填好AI才会按你定制)
选考科目(点击选择你的3门)
物理
化学
生物
政治
历史
地理
档案仅保存在你本地浏览器
💪 加油!你一定可以的!
✨ 乾坤未定
你我皆是黑马 ✨
📘
你的高考复习助手
先填档案,再提问;你会得到更贴近自己的讲解、计划和批改
支持讲题、出题、批改、总结和错题整理
支持上传 PDF、Word、TXT 和图片
数学讲题
物理讲解
化学总结
英语记忆
⚠️ 本工具为 AI 辅助学习产品,所有回答由大模型生成,仅供学习参考,请以教材与任课老师讲解为准。本产品非教育考试院或任何官方机构出品,不承诺押题命中率。
📘你的高考复习助手
对话已清空
数学讲题物理讲解化学总结英语记忆
`; }
// ====== 导出(PPT / Word / PDF,仅 VIP 会员可用;只导出 AI 生成的计划/总结)======
function getExportContent(){
const items = (conversation||[]).filter(m => m.role === 'assistant' && m.content && m.content.trim());
return items.length ? items.map(m => m.content) : null;
}
function loadScript(src){ return new Promise((res,rej)=>{ const s=document.createElement('script'); s.src=src; s.onload=res; s.onerror=()=>rej(new Error('组件加载失败')); document.head.appendChild(s); }); }
function downloadBlob(content, filename, mime){
const blob = new Blob([content], {type: mime+';charset=utf-8'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href=url; a.download=filename; document.body.appendChild(a); a.click(); a.remove();
setTimeout(()=>URL.revokeObjectURL(url), 1500);
}
function stripMd(t){ return String(t).replace(/^#+\s*/gm,'').replace(/\*\*/g,'').replace(/`/g,'').replace(/^\s*[-*]\s/gm,'• '); }
function chunkText(text, max){
const paras = String(text).replace(/\r/g,'').split(/\n{1,}/).map(s=>s.trim()).filter(Boolean);
const chunks=[]; let cur='';
for(const p of paras){
if(cur && (cur+'\n'+p).length > max){ chunks.push(cur); cur=p; }
else cur = cur ? cur+'\n'+p : p;
}
if(cur) chunks.push(cur);
return chunks.length ? chunks : [String(text)];
}
function ensureVip(){
if(memberActive) return true;
addMsg('system-msg','📎 导出文件是会员专属功能,请先兑换会员码 👑(点顶部「💎兑换」)');
openBenefit();
return false;
}
async function exportPPT(){
if(!ensureVip()) return;
const contents = getExportContent();
if(!contents){ addMsg('system-msg','暂无可导出的内容:先和 AI 聊聊(生成计划/总结)再导出 PPT。'); return; }
addMsg('system-msg','正在生成 PPT…(首次需联网加载组件)');
try {
if(typeof PptxGenJS === 'undefined'){ await loadScript('./vendor/pptxgen.bundle.js'); }
const pptx = new PptxGenJS();
pptx.layout = 'LAYOUT_WIDE';
let idx = 0;
contents.forEach((text) => {
chunkText(stripMd(text), 320).forEach((chunk) => {
idx++;
const slide = pptx.addSlide();
slide.background = { color:'FFFFFF' };
slide.addText('高考学习助手 · AI 学习建议 ' + idx, { x:0.5, y:0.3, w:12.3, h:0.6, fontSize:18, bold:true, color:'1A1A2E' });
slide.addText(chunk, { x:0.5, y:1.1, w:12.3, h:6.0, fontSize:14, color:'222222', valign:'top', shrinkText:true, lineSpacingMultiple:1.15 });
});
});
await pptx.writeFile({ fileName:'高考学习助手_AI建议.pptx' });
addMsg('system-msg','PPT 已生成并下载 ✅');
} catch(e){ addMsg('system-msg','PPT 生成失败:' + e.message + '(需联网加载 PptxGenJS)'); }
}
function exportWord(){
if(!ensureVip()) return;
const contents = getExportContent();
if(!contents){ addMsg('system-msg','暂无可导出的内容:先和 AI 聊聊再导出 Word。'); return; }
const title = '高考学习助手 · AI 生成的计划与总结';
let html = ''+title+'';
html += ''+title+'
';
contents.forEach((t,i)=>{ html += '建议 '+(i+1)+'
' + formatMarkdown(t); });
html += '';
downloadBlob(html, '高考学习助手_AI计划总结.doc', 'application/msword');
addMsg('system-msg','Word 已生成并下载 ✅(.doc,可用 Word / WPS 打开)');
}
function exportPDF(){
if(!ensureVip()) return;
const contents = getExportContent();
if(!contents){ addMsg('system-msg','暂无可导出的内容:先和 AI 聊聊再导出 PDF。'); return; }
const w = window.open('', '_blank');
if(!w){ addMsg('system-msg','PDF 导出被浏览器拦截,请允许弹出窗口后重试。'); return; }
let body = '高考学习助手 · AI 生成的计划与总结
';
contents.forEach((t,i)=>{ body += '建议 '+(i+1)+'
' + formatMarkdown(t); });
const doc = '高考学习助手_AI计划总结' +
'' +
body + '';
w.document.write(doc); w.document.close();
addMsg('system-msg','PDF 预览已打开,按 Ctrl/Cmd+P 选「另存为 PDF」即可 ✅');
}
// ====== 新对话 ======
async function fetchChatWithFallback(payload, allowFallback){
const fallbackModels = allowFallback ? [payload.model, 'qwen-vl', 'deepseek'].filter((m,i,a)=>m && a.indexOf(m)===i) : [payload.model];
let lastData = null;
let lastRaw = '';
let lastStatus = 0;
for(const model of fallbackModels){
const res = await fetch(BACKEND_URL, {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify(Object.assign({}, payload, { model }))
});
lastStatus = res.status;
lastRaw = await res.text();
let data; try { data = JSON.parse(lastRaw); } catch(e) { throw new Error('后端返回格式错误(HTTP '+res.status+')'); }
lastData = data;
if(data.ok) return data;
const err = makeBackendError(data);
if(!isTransientAiError(err)) return data;
}
return lastData || { ok:false, message:'请求失败(HTTP '+lastStatus+')', raw:lastRaw };
}
function newChat(){
if(conversation.length===0){ addMsg('system-msg','已经是空对话了'); return; }
conversation=[];
uploadedFiles=[]; renderFileChips();
const chatEl=document.getElementById('chat');
if(chatEl){ chatEl.innerHTML=''; chatEl.appendChild(makeEmptyState()); }
addMsg('system-msg','✅ 已开启新对话(档案和激活状态保留)');
}
// ====== 发送 ======
async function send() {
if (!BACKEND_URL) { addMsg('system-msg','后端未配置:卖家未填 BACKEND_URL,请联系卖家'); openBenefit(); return; }
const deviceId = getDeviceId();
// S3 兼容:确保 deviceToken 总是有值(后端靠此做设备绑定)
if (!deviceToken) deviceToken = deviceId;
const code = activeCode || ''; // 会员/积分码;空=免费试用
// 软限制:试用额度用尽、且非会员无积分 → 拦截并提示兑换(清缓存可绕过,低价产品可接受)
if (!memberActive && credits <= 0 && trialUsed >= MAX_FREE_TRIALS) {
addMsg('system-msg','免费试用已用完('+MAX_FREE_TRIALS+' 次)。请点顶部「💎兑换」输入会员码/积分码后继续使用。');
openBenefit();
return;
}
const textarea = document.getElementById('prompt'); const btn = document.getElementById('sendBtn');
const text = textarea.value.trim();
if (uploadedFiles.filter(f=>f.loading).length>0) { addMsg('system-msg','还有文件正在解析中...'); return; }
if (!text && uploadedFiles.length===0) return;
const model = getCurrentModel();
const modelInfo = MODELS.find(m => m.id === model) || MODELS[0];
const images = uploadedFiles.filter(f => f.isImage && f.dataUrl);
const docs = uploadedFiles.filter(f => !(f.isImage && f.dataUrl));
// 文字/文档部分拼接(图片不走文本,单独作为多模态内容)
let fullMsg = text || '';
if (docs.length > 0) {
const fts = docs.map((f,i)=>`【上传文件${i+1}:${f.name}】\n${f.text}\n【文件${i+1}结束】`).join('\n\n');
fullMsg = `【上传文件内容】\n${fts}\n\n【用户问题】\n${text||'请帮我分析以上文件内容'}`;
}
if (currentSubject !== '全部') fullMsg = `【当前学科:${currentSubject}】\n${fullMsg}`;
// 图片必须由视觉模型处理:检测到图片但当前是纯文字模型 → 自动切到默认视觉模型
let useVision = false;
if (images.length > 0) {
if (!modelInfo.vision) {
currentModel = 'qwen-vl';
localStorage.setItem('gaokao_model', currentModel);
updateModelDisplay();
syncModelSelectState();
addMsg('system-msg', '📷 检测到图片,已自动切换到「通义千问 视觉·识图」模型来识别');
modelInfo = MODELS.find(m => m.id === 'qwen-vl');
}
useVision = true;
}
// 构造用户消息内容:视觉模型=多模态数组(文字+图片);否则纯文本
let userContent;
if (useVision) {
const parts = [];
if (fullMsg.trim()) parts.push({ type:'text', text: fullMsg });
images.forEach(img => parts.push({ type:'image_url', image_url:{ url: img.dataUrl } }));
userContent = parts;
} else {
userContent = fullMsg;
}
// 展示用户气泡(含图片缩略图)
let displayHtml = (text || (images.length ? '📷 图片已发送' : '请帮我分析上传的文件')).replace(/\n/g,'
');
if (useVision) images.forEach(img => { displayHtml += `
`; });
addUserMsg(userContent, displayHtml);
textarea.value = ''; textarea.style.height = 'auto'; btn.disabled = true; showTyping();
try {
const messages = [{role:'system',content:buildSystemPrompt()}, ...conversation.slice(-30)];
messages[messages.length-1].content = userContent;
const data = await fetchChatWithFallback({ action:'chat', code, deviceId, deviceToken, model: modelInfo.backend, messages, temperature:0.7, max_tokens:4096 }, !useVision);
if (!data.ok) {
removeTyping();
const reason = data.reason || '';
const entitlementReasons = ['no_access','trial_exhausted','invalid_code','bound_elsewhere','code_used','device_mismatch','credit_empty','member_required','need_redeem'];
if(['invalid_code','bound_elsewhere','code_used','device_mismatch','credit_empty','need_redeem'].includes(reason)){ activeCode=''; deviceToken=''; memberActive=false; persistAccess(); }
if(entitlementReasons.includes(reason)) openBenefit();
addMsg('error',' '+ (data.message || ('['+(reason||'?')+'] 请求失败')));
conversation = conversation.slice(0,-1); btn.disabled = false; return;
}
removeTyping(); addMsg('ai', data.content);
// 依据服务端返回更新本地状态(服务端才是权威)
if (data.plan === 'trial') { trialUsed++; persistAccess(); }
else if (data.plan === 'member') { memberActive = true; persistAccess(); }
else if (data.plan === 'credit') { credits = Math.max(0, credits - 1); if(credits<=0) activeCode=''; persistAccess(); }
if (data.plan) { updateBenefitBadge(); updateBenefitBanner(); updateBenefitStatus(); }
uploadedFiles = []; renderFileChips();
} catch(e) { removeTyping(); addMsg('error',' '+e.message); conversation = conversation.slice(0,-1); }
btn.disabled = false; textarea.focus();
}
document.getElementById('prompt').addEventListener('input',function(){this.style.height='auto';this.style.height=Math.min(this.scrollHeight,120)+'px';});
document.addEventListener('dragover',e=>{e.preventDefault();});
document.addEventListener('drop',e=>{e.preventDefault();if(e.dataTransfer.files.length>0)handleFiles(e.dataTransfer.files);});
// ============================================================
// 错题本模块
// ============================================================
const MK_KEY = 'gaokao_mistakes';
const MK_SUBJECTS = ['语文','数学','英语','物理','化学','生物','政治','历史','地理'];
const MK_ERR_MAP = { concept:'概念不清', careless:'粗心大意', method:'方法欠缺', unknown:'原因不明' };
let mkList = mkLoad();
let mkCurrentFilter = '全部';
let mkDraftKps = []; // 添加表单中的知识点
let mkDraftDiff = 3; // 添加表单中的难度
let mkCurrentView = 'chat';
function mkLoad(){ try { return JSON.parse(localStorage.getItem(MK_KEY)) || []; } catch(e){ return []; } }
function mkPersist(){ localStorage.setItem(MK_KEY, JSON.stringify(mkList)); }
// ---- 通用 AI 调用(复用后端 + 权益扣费)----
async function aiChat(opts){
normalizeAccessState();
if(!BACKEND_URL) throw new Error('后端未配置:卖家未填 BACKEND_URL');
if(!memberActive && credits<=0 && trialUsed>=MAX_FREE_TRIALS){
openBenefit();
throw new Error('免费试用已用完,请兑换会员码/积分码后继续');
}
const res = await fetch(BACKEND_URL, {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ action:'chat', code: activeCode||'', deviceId: getDeviceId(), deviceToken, model: opts.model||'deepseek', messages: opts.messages, temperature: opts.temperature!=null?opts.temperature:0.7, max_tokens: opts.max_tokens||2000 })
});
const raw = await res.text();
let data;
try { data = JSON.parse(raw); }
catch(e) { throw new Error('腾讯云后端异常(HTTP '+res.status+'),没有返回有效 JSON,请检查云函数日志和密钥配置'); }
if(!res.ok && data.ok !== true) throw new Error(data.message || ('腾讯云后端异常(HTTP '+res.status+')'));
if(!data.ok){
if(data.reason==='invalid_code'||data.reason==='bound_elsewhere'||data.reason==='code_used'||data.reason==='device_mismatch'||data.reason==='credit_empty'){ activeCode=''; deviceToken=''; memberActive=false; persistAccess(); }
const reason = data.reason || '';
const entitlementReasons = ['no_access','trial_exhausted','invalid_code','bound_elsewhere','code_used','device_mismatch','credit_empty','member_required'];
if(entitlementReasons.includes(reason)) openBenefit();
throw makeBackendError(data);
}
if(data.plan==='trial'){ trialUsed++; persistAccess(); }
else if(data.plan==='member'){ memberActive=true; persistAccess(); }
else if(data.plan==='credit'){ credits=Math.max(0,credits-1); if(credits<=0) activeCode=''; persistAccess(); }
if(data.plan){ updateBenefitBadge(); updateBenefitBanner(); updateBenefitStatus(); }
return data.content || '';
}
function hasMistakeBookAccess(){
return !!(memberActive && activeCode && deviceToken);
}
function requireMistakeBookAccess(actionText){
normalizeAccessState();
if(hasMistakeBookAccess()) return true;
openBenefit();
const msg = actionText || '错题本功能需要先开通会员后使用';
const hint = document.getElementById('mkAddHint');
if(hint) hint.textContent = msg;
alert(msg);
return false;
}
function isTransientAiError(err){
const msg = (err && err.message ? err.message : String(err || '')).toLowerCase();
const reason = (err && err.reason ? String(err.reason) : '').toLowerCase();
return /model_busy|busy|timeout|timed out|overload|rate|429|502|503|504|bad gateway|网关|超时|temporarily|unavailable/.test(reason) || /繁忙|稍后|切换模型|timeout|timed out|overload|busy|rate|429|502|503|504|bad gateway|网关|超时|temporarily|unavailable/.test(msg);
}
function makeBackendError(data){
const err = new Error(data && data.message ? String(data.message) : 'AI 服务请求失败');
err.reason = data && data.reason ? String(data.reason) : 'backend_error';
return err;
}
async function aiChatWithFallback(opts){
const preferred = opts && opts.model ? opts.model : 'deepseek';
const models = [preferred, 'qwen-vl', 'deepseek'].filter((m, i, arr) => m && arr.indexOf(m) === i);
let lastErr = null;
for(const model of models){
try {
return await aiChat(Object.assign({}, opts, { model }));
} catch(e){
lastErr = e;
if(!isTransientAiError(e)) break;
}
}
throw lastErr || new Error('AI 服务暂时不可用,请稍后重试');
}
// ---- 视图切换 ----
function switchView(v){
if(v==='mistakes' && !requireMistakeBookAccess('错题本是会员功能:开通会员后可以保存错题、生成解析和跟踪掌握情况')) return;
mkCurrentView = v;
const isMk = v==='mistakes';
document.getElementById('vsChat').classList.toggle('active', !isMk);
document.getElementById('vsMistake').classList.toggle('active', isMk);
['chatToolbar','chat','chatInput'].forEach(id=>{ const el=document.getElementById(id); if(el) el.style.display = isMk ? 'none' : ''; });
document.getElementById('mistakesView').classList.toggle('show', isMk);
if(isMk){ document.getElementById('profilePanel').classList.remove('open'); mkRenderAll(); }
}
// ---- 子标签 ----
function mkTab(t){
document.querySelectorAll('.mk-tab').forEach(b=>b.classList.toggle('active', b.dataset.tab===t));
document.getElementById('mkList').style.display = t==='list' ? '' : 'none';
document.getElementById('mkAdd').style.display = t==='add' ? '' : 'none';
document.getElementById('mkStats').style.display = t==='stats' ? '' : 'none';
if(t==='list') mkRenderList();
if(t==='stats') mkRenderStats();
}
// ---- 初始化控件 ----
function mkInitControls(){
// 学科下拉:语数外 + 档案选科 + 常见科目(去重)
const subs = Array.from(new Set([...MK_SUBJECTS, ...((profile&&profile.subjects)||[])]));
document.getElementById('mk_subject').innerHTML = subs.map(s=>``).join('');
// 难度星
renderDiffStars();
}
function renderDiffStars(){
const wrap = document.getElementById('mkDiffStars');
wrap.innerHTML = [1,2,3,4,5].map(n=>`★`).join('');
}
function mkSetDiff(n){ mkDraftDiff = n; renderDiffStars(); }
// ---- 知识点(添加表单)----
function mkAddKp(){
const inp = document.getElementById('mk_kp_input');
const v = (inp.value||'').trim();
if(v && !mkDraftKps.includes(v)) mkDraftKps.push(v);
inp.value=''; renderDraftKps();
}
function mkRemoveKp(i){ mkDraftKps.splice(i,1); renderDraftKps(); }
function renderDraftKps(){
document.getElementById('mkKpChips').innerHTML = mkDraftKps.map((k,i)=>`${escapeHtml(k)}×`).join('');
}
async function mkExtractKp(){
if(!requireMistakeBookAccess('开通会员后可以使用 AI 提取知识点')) return;
const title = (document.getElementById('mk_title').value||'').trim();
const subject = document.getElementById('mk_subject').value;
if(!title){ document.getElementById('mkAddHint').textContent='请先填写题目内容,AI 才能提取知识点'; return; }
const btn = document.getElementById('mkKpAiBtn'); btn.disabled=true; const old=btn.textContent; btn.textContent='提取中…';
try {
const content = await aiChatWithFallback({ model:'deepseek', max_tokens:400, messages:[
{ role:'system', content:'你是学科知识点分析助手,只返回JSON,不要多余文字。' },
{ role:'user', content:`分析这道${subject}题目涉及的核心知识点(3-6个),只返回JSON格式:{"points":["知识点1","知识点2"]}\n\n题目:${title}` }
]});
const m = content.match(/\{[\s\S]*\}/);
const pts = m ? (JSON.parse(m[0]).points||[]) : [];
pts.forEach(p=>{ if(p && !mkDraftKps.includes(p)) mkDraftKps.push(p); });
renderDraftKps();
document.getElementById('mkAddHint').textContent = pts.length ? ('已提取 '+pts.length+' 个知识点 ✓') : '未能提取到知识点,可手动添加';
} catch(e){ document.getElementById('mkAddHint').textContent = '提取失败:'+e.message; }
btn.disabled=false; btn.textContent=old;
}
// ---- OCR 拍照识别 ----
async function mkOcrRecognize(file){
if(!requireMistakeBookAccess('开通会员后可以在错题本里拍照识别题目')) return;
if(!file) return;
const btn = document.getElementById('mkOcrBtn'); btn.disabled=true; const old=btn.textContent; btn.textContent='识别中…';
document.getElementById('mkAddHint').textContent='正在用视觉模型识别题目文字…';
try {
const dataUrl = await compressImage(file);
const content = await aiChat({ model:'qwen-vl', temperature:0.1, max_tokens:1500, messages:[
{ role:'user', content:[
{ type:'text', text:'你是OCR识别助手,请准确识别图片中的题目文字,直接输出题目原文,不要添加任何解释、标题或修饰。' },
{ type:'image_url', image_url:{ url: dataUrl } }
]}
]});
const ta = document.getElementById('mk_title');
ta.value = ta.value ? (ta.value + '\n' + content) : content;
document.getElementById('mkAddHint').textContent='识别完成 ✓,可继续补充答案后保存';
} catch(e){ document.getElementById('mkAddHint').textContent='识别失败:'+e.message; }
btn.disabled=false; btn.textContent=old;
document.getElementById('mkOcrInput').value='';
}
// ---- 保存错题 ----
function mkSave(){
if(!requireMistakeBookAccess('开通会员后可以保存错题到错题本')) return;
const title = (document.getElementById('mk_title').value||'').trim();
if(!title){ document.getElementById('mkAddHint').textContent='题目内容不能为空'; return; }
const q = {
id: 'q_'+Date.now().toString(36)+Math.random().toString(36).slice(2,6),
subject: document.getElementById('mk_subject').value,
title,
correctAnswer: (document.getElementById('mk_correct').value||'').trim(),
wrongAnswer: (document.getElementById('mk_wrong').value||'').trim(),
errorType: document.getElementById('mk_errorType').value,
knowledgePoints: [...mkDraftKps],
difficulty: mkDraftDiff,
analysis: '',
mastered: false,
createdAt: Date.now()
};
mkList.unshift(q); mkPersist();
// 重置表单
document.getElementById('mk_title').value='';
document.getElementById('mk_correct').value='';
document.getElementById('mk_wrong').value='';
document.getElementById('mk_errorType').value='concept';
mkDraftKps=[]; mkDraftDiff=3; renderDraftKps(); renderDiffStars();
document.getElementById('mkAddHint').textContent='已保存 ✓ 已切换到「错题库」';
mkTab('list');
document.querySelectorAll('.mk-tab').forEach(b=>b.classList.toggle('active', b.dataset.tab==='list'));
}
// ---- 错题库列表 ----
function mkRenderFilter(){
const subs = ['全部', ...Array.from(new Set(mkList.map(q=>q.subject)))];
document.getElementById('mkFilter').innerHTML = subs.map(s=>`${s}${s==='全部'?'':' ('+mkList.filter(q=>q.subject===s).length+')'}`).join('');
}
function mkSetFilter(s){ mkCurrentFilter=s; mkRenderList(); }
function mkRenderList(){
mkRenderFilter();
const box = document.getElementById('mkListItems');
const items = mkCurrentFilter==='全部' ? mkList : mkList.filter(q=>q.subject===mkCurrentFilter);
if(items.length===0){
box.innerHTML = `📭${mkList.length===0?'还没有错题,点「➕ 添加错题」开始收集吧':'该学科暂无错题'}
`;
return;
}
box.innerHTML = items.map(q=>{
const stars = '★'.repeat(q.difficulty)+'☆'.repeat(5-q.difficulty);
const kps = (q.knowledgePoints||[]).slice(0,4).map(k=>`${escapeHtml(k)}`).join('');
return `
${q.subject}
${MK_ERR_MAP[q.errorType]||q.errorType}
${q.mastered?'已掌握':''}
${q.analysis?'已解析':''}
${stars}
${escapeHtml(q.title)}
${kps?`
${kps}
`:''}
${new Date(q.createdAt).toLocaleDateString()}
`;
}).join('');
}
// ---- 详情 ----
let mkDetailId = null;
function mkOpenDetail(id){
const q = mkList.find(x=>x.id===id); if(!q) return;
mkDetailId = id;
const stars = '★'.repeat(q.difficulty)+'☆'.repeat(5-q.difficulty);
const kps = (q.knowledgePoints||[]).map(k=>`${escapeHtml(k)}`).join(' ') || '无';
document.getElementById('mkDetailCard').innerHTML = `
${q.subject}
${MK_ERR_MAP[q.errorType]||q.errorType}
${q.mastered?'已掌握':''}
${stars}
${q.correctAnswer?`正确答案
${escapeHtml(q.correctAnswer)}
`:''}
${q.wrongAnswer?`我的错误答案
${escapeHtml(q.wrongAnswer)}
`:''}
AI 解析
${q.analysis? formatMarkdown(q.analysis) : '尚未生成,点下方「✨ 生成 AI 解析」'}
`;
document.getElementById('mkDetailOverlay').classList.add('show');
}
function mkCloseDetail(){ document.getElementById('mkDetailOverlay').classList.remove('show'); mkDetailId=null; }
async function mkGenAnalysis(){
if(!requireMistakeBookAccess('开通会员后可以生成错题 AI 解析')) return;
const q = mkList.find(x=>x.id===mkDetailId); if(!q) return;
const btn = document.getElementById('mkGenBtn'); btn.disabled=true; const old=btn.textContent; btn.textContent='生成中…';
document.getElementById('mkAnalysisBox').innerHTML = 'AI 正在生成解析…';
try {
const prompt = `作为${q.subject}专业教师,为以下错题生成详细清晰的解析。\n\n【题目】${q.title}\n${q.correctAnswer?'【正确答案】'+q.correctAnswer+'\n':''}${q.wrongAnswer?'【学生错误答案】'+q.wrongAnswer+'\n':''}【错误类型】${MK_ERR_MAP[q.errorType]||q.errorType}\n${(q.knowledgePoints||[]).length?'【涉及知识点】'+q.knowledgePoints.join('、')+'\n':''}\n请按以下结构:\n1. 解题思路(2-3句概括)\n2. 详细解析步骤\n3. 易错点提醒\n4. 举一反三技巧\n要求:言简意赅,逻辑清晰,适合高中生。`;
const content = await aiChatWithFallback({ model:'deepseek', max_tokens:2000, messages:[
{ role:'system', content:`你是一名经验丰富的${q.subject}教师,善于用简洁清晰的语言讲解解题思路。` },
{ role:'user', content: prompt }
]});
q.analysis = content; mkPersist();
document.getElementById('mkAnalysisBox').innerHTML = formatMarkdown(content);
btn.textContent = '✨ 重新生成解析';
} catch(e){ document.getElementById('mkAnalysisBox').innerHTML = '生成失败:'+escapeHtml(e.message)+''; btn.textContent=old; }
btn.disabled=false;
}
function mkToggleMaster(){
if(!requireMistakeBookAccess('开通会员后可以标记错题掌握状态')) return;
const q = mkList.find(x=>x.id===mkDetailId); if(!q) return;
q.mastered = !q.mastered; mkPersist(); mkOpenDetail(q.id); mkRenderList();
}
function mkDelete(){
if(!requireMistakeBookAccess('开通会员后可以管理错题本')) return;
const q = mkList.find(x=>x.id===mkDetailId); if(!q) return;
if(!confirm('确定删除这道错题?')) return;
mkList = mkList.filter(x=>x.id!==mkDetailId); mkPersist();
mkCloseDetail(); mkRenderList();
}
// ---- 学情分析 ----
function mkRenderStats(){
const total = mkList.length;
const mastered = mkList.filter(q=>q.mastered).length;
const bySubj = {}, byErr = {}, byDiff = {1:0,2:0,3:0,4:0,5:0};
mkList.forEach(q=>{
bySubj[q.subject]=(bySubj[q.subject]||0)+1;
byErr[q.errorType]=(byErr[q.errorType]||0)+1;
byDiff[q.difficulty]=(byDiff[q.difficulty]||0)+1;
});
document.getElementById('mkStatCards').innerHTML = `
${total?Math.round(mastered/total*100):0}%
掌握率
`;
if(total===0){ document.getElementById('mkStatBars').innerHTML = ''; return; }
const bars = (title, obj, mapper)=>{
const max = Math.max(...Object.values(obj), 1);
const rows = Object.entries(obj).sort((a,b)=>b[1]-a[1]).map(([k,v])=>`
${mapper?mapper(k):k}
${v} `).join('');
return `${title}
${rows}`;
};
document.getElementById('mkStatBars').innerHTML =
bars('按学科分布', bySubj) +
bars('按错误类型', byErr, k=>MK_ERR_MAP[k]||k) +
bars('按难度分布', byDiff, k=>'★'.repeat(+k));
}
async function mkDiagnose(){
if(!requireMistakeBookAccess('开通会员后可以查看错题学情诊断')) return;
if(mkList.length===0){ document.getElementById('mkDiagnoseResult').innerHTML='暂无错题数据'; return; }
const box = document.getElementById('mkDiagnoseResult');
box.innerHTML = 'AI 正在分析你的错题分布…';
const bySubj={}, byErr={};
mkList.forEach(q=>{ bySubj[q.subject]=(bySubj[q.subject]||0)+1; byErr[q.errorType]=(byErr[q.errorType]||0)+1; });
const kps = {};
mkList.forEach(q=>(q.knowledgePoints||[]).forEach(k=>kps[k]=(kps[k]||0)+1));
const topKp = Object.entries(kps).sort((a,b)=>b[1]-a[1]).slice(0,8).map(([k,v])=>k+'('+v+')').join('、');
try {
const content = await aiChatWithFallback({ model:'deepseek', max_tokens:1200, messages:[
{ role:'system', content:'你是学习规划师,善于根据错题数据给出精准、可执行的诊断和建议。' },
{ role:'user', content:`根据我的错题数据做学情诊断:\n- 各学科错题数:${Object.entries(bySubj).map(([k,v])=>k+' '+v).join('、')}\n- 错误类型分布:${Object.entries(byErr).map(([k,v])=>(MK_ERR_MAP[k]||k)+' '+v).join('、')}\n- 高频知识点:${topKp||'无'}\n请给出:1) 最薄弱的2-3个环节 2) 每个环节的针对性攻克建议 3) 一句激励。用表格/分点,简洁可执行。` }
]});
box.innerHTML = ''+formatMarkdown(content)+'
';
} catch(e){ box.innerHTML = '诊断失败:'+escapeHtml(e.message)+''; }
}
async function mkGenReviewSheet(){
if(!requireMistakeBookAccess('开通会员后可以生成错题复习卷')) return;
const items = mkCurrentFilter==='全部' ? mkList : mkList.filter(q=>q.subject===mkCurrentFilter);
if(items.length===0){ alert('当前没有可用于组卷的错题'); return; }
switchView('mistakes');
const box = document.getElementById('mkDetailCard');
document.getElementById('mkDetailOverlay').classList.add('show');
box.innerHTML = '';
const summary = items.slice(0,30).map((q,i)=>`${i+1}. [${q.subject}] ${q.title.slice(0,60)}(${MK_ERR_MAP[q.errorType]||q.errorType}, 难度${q.difficulty})`).join('\n');
try {
const content = await aiChatWithFallback({ model:'deepseek', max_tokens:2000, messages:[
{ role:'system', content:'你是命题老师,善于根据学生错题设计针对性复习卷。' },
{ role:'user', content:`根据以下${items.length>30?'(部分)':''}错题,生成一份针对性复习卷规划:\n\n${summary}\n\n请给出:1) 本卷重点考查的知识点 2) 推荐做题顺序(由易到难)3) 每类题的复习提示 4) 预计耗时。用分点+表格,简洁实用。` }
]});
document.getElementById('mkSheetBox').innerHTML = formatMarkdown(content);
} catch(e){ const el=document.getElementById('mkSheetBox'); if(el) el.innerHTML='生成失败:'+escapeHtml(e.message)+''; }
}
// ---- 工具 ----
function escapeHtml(s){ return String(s).replace(/[&<>"']/g, c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
function mkRenderAll(){ mkInitControls(); mkRenderList(); }
document.getElementById('mkDetailOverlay').addEventListener('click', e=>{ if(e.target.id==='mkDetailOverlay') mkCloseDetail(); });
// ---- 主题切换(科技蓝 ⇄ 手绘风) ----
function applyTheme(t){
const hand = (t === 'hand');
document.body.classList.toggle('theme-hand', hand);
try { localStorage.setItem('gaokao_theme', t); } catch(e){}
const b = document.getElementById('themeToggle');
if (b) b.innerHTML = '🎨 ' + (hand ? '科技蓝' : '手绘风');
}
function toggleTheme(e){
const b = document.getElementById('themeToggle');
if (b){
b.classList.remove('spinning'); void b.offsetWidth; b.classList.add('spinning');
if (e && typeof e.clientX === 'number'){
const r = b.getBoundingClientRect();
const rip = document.createElement('span'); rip.className = 'ripple';
rip.style.left = (e.clientX - r.left) + 'px'; rip.style.top = (e.clientY - r.top) + 'px';
b.appendChild(rip); setTimeout(function(){ rip.remove(); }, 520);
}
}
applyTheme(document.body.classList.contains('theme-hand') ? 'tech' : 'hand');
}
// ====== 初始化 ======
applyTheme((function(){ try { return localStorage.getItem('gaokao_theme'); } catch(e){ return null; } })() || 'tech');
(function(){ var b = document.getElementById('themeToggle'); if (b) b.addEventListener('animationend', function(){ b.classList.remove('spinning'); }); })();
initKeyUI(); initModelSelect(); renderSubjects(); updateProfileBtn(); mkInitControls();
repairPollutedChat();
updateCountdown(); setInterval(updateCountdown, 1000);
// 会员预激活(可选:卖家在 ACTIVATION_CODE 填会员码,则打开即会员)
if (ACTIVATION_CODE) { activeCode = ACTIVATION_CODE.toUpperCase(); memberActive = true; persistAccess(); }
updateBenefitBadge(); updateBenefitBanner();
// 使用说明只在用户点击问号时打开,避免手机端首屏被弹层遮挡。
if (!localStorage.getItem('gaokao_help_seen')) { localStorage.setItem('gaokao_help_seen', '1'); }
if (Object.keys(profile).length>0) { document.getElementById('profileHint').textContent = '已保存 · '+((profile.subjects||[]).join('/')||'未选科')+' · 目标'+(profile.target||'?')+'分'; }
// ====== 移动端键盘适配(visualViewport) ======
(function() {
if (!window.visualViewport) return;
var vv = window.visualViewport;
var body = document.body;
var lastHeight = 0;
function onResize() {
var h = vv.height;
body.style.setProperty('--app-height', h + 'px');
if (Math.abs(h - lastHeight) < 6) return;
lastHeight = h;
// 部分手机浏览器键盘弹起时 offsetTop 仍为 0,只看可视高度更可靠。
var keyboardOpen = h < window.innerHeight - 80;
body.classList.toggle('kb-open', keyboardOpen);
if (keyboardOpen) {
var chat = document.getElementById('chat');
if (chat) requestAnimationFrame(function(){ chat.scrollTop = chat.scrollHeight; });
}
}
vv.addEventListener('resize', onResize);
vv.addEventListener('scroll', onResize);
onResize();
})();