Files
xiaoxue_admin 8f9c98c5b5 feat: 高考志愿网站初始版本
- ai-chat.html: AI对话咨询页
- ai-tool.html: AI智能推荐页 (需 /api/recommend 路由)
- plan.html: 志愿填写助手
- school.html: 查校对比
- major.html: 查专业录取分
- qa.html: 常见问题
- diag.html: 高考诊断
- gaokao-chat-server-v2.py: Flask后端 (port 8080)
- scripts/: 数据采集和处理工具
2026-06-18 16:59:13 +08:00

635 lines
35 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 志愿填写助手 - 河南高考志愿助手</title>
<meta name="description" content="引导式志愿规划,AI 根据你的分数和偏好生成可直接填写的志愿草表。">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0a0a0f;color:#e0e0e0;min-height:100vh;line-height:1.7}
.header{background:rgba(20,20,30,0.95);backdrop-filter:blur(20px);border-bottom:1px solid rgba(255,255,255,0.08);padding:14px 0;position:sticky;top:0;z-index:100}
.header-inner{max-width:1100px;margin:0 auto;padding:0 20px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px}
.logo{font-size:1.3rem;font-weight:700;color:#fff;text-decoration:none}
.logo span{color:#667eea}
.nav-links{display:flex;gap:20px;list-style:none;flex-wrap:wrap}
.nav-links a{color:#a0a0a0;text-decoration:none;font-size:0.9rem;transition:color 0.3s}
.nav-links a:hover{color:#667eea}.nav-links a.highlight{color:#667eea;font-weight:600}
.page-hero{background:linear-gradient(135deg,#0f0f1a,#12102a);padding:40px 0 36px;border-bottom:1px solid rgba(102,126,234,0.15)}
.container{max-width:820px;margin:0 auto;padding:0 20px}
.page-hero h1{font-size:1.7rem;color:#fff;margin-bottom:6px}
.page-hero p{color:#888;font-size:0.9rem}
.step-bar{display:flex;align-items:center;gap:0;margin:32px 0 28px}
.step{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;position:relative}
.step::after{content:'';position:absolute;top:18px;left:calc(50% + 18px);right:calc(-50% + 18px);height:2px;background:rgba(255,255,255,0.1);z-index:0}
.step:last-child::after{display:none}
.step-num{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:0.85rem;font-weight:700;background:rgba(255,255,255,0.06);border:2px solid rgba(255,255,255,0.1);color:#666;z-index:1;transition:all 0.3s}
.step.active .step-num{background:#667eea;border-color:#667eea;color:#fff}
.step.done .step-num{background:rgba(102,126,234,0.3);border-color:#667eea;color:#667eea}
.step-label{font-size:0.78rem;color:#666;text-align:center;transition:color 0.3s}
.step.active .step-label{color:#667eea;font-weight:600}
.step.done .step-label{color:#888}
.card{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.08);border-radius:14px;padding:28px;margin-bottom:20px}
.card-title{font-size:1.1rem;color:#fff;margin-bottom:6px}
.card-sub{color:#888;font-size:0.85rem;margin-bottom:22px}
.form-row{display:flex;flex-direction:column;gap:16px}
.form-group{display:flex;flex-direction:column;gap:6px}
.form-group label{font-size:0.85rem;color:#aaa;font-weight:500}
.form-group input,.form-group select{padding:11px 14px;background:rgba(255,255,255,0.12);border:1px solid rgba(255,255,255,0.2);border-radius:8px;color:#e0e0e0;font-size:0.95rem;outline:none;width:100%;appearance:auto;-webkit-appearance:auto}
.form-group input:focus,.form-group select:focus{border-color:#667eea}
.form-group .hint{font-size:0.78rem;color:#666}
.radio-group{display:flex;gap:12px;flex-wrap:wrap}
.radio-opt{flex:1;min-width:120px;padding:12px 16px;background:rgba(255,255,255,0.04);border:2px solid rgba(255,255,255,0.08);border-radius:10px;cursor:pointer;transition:all 0.2s;text-align:center}
.radio-opt:hover{border-color:rgba(102,126,234,0.4)}
.radio-opt input{display:none}
.radio-opt .opt-title{font-size:0.9rem;color:#e0e0e0;font-weight:600;margin-bottom:3px}
.radio-opt .opt-desc{font-size:0.75rem;color:#666}
.radio-opt input:checked + .opt-title{color:#667eea}
.radio-opt:has(input:checked){border-color:#667eea;background:rgba(102,126,234,0.06)}
.btn-row{display:flex;gap:12px;margin-top:24px;justify-content:flex-end}
.btn{display:inline-flex;align-items:center;gap:6px;padding:11px 26px;border-radius:10px;font-size:0.9rem;font-weight:600;cursor:pointer;transition:all 0.2s;border:none}
.btn-primary{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff}
.btn-primary:hover{opacity:0.88}
.btn-primary:disabled{opacity:0.4;cursor:not-allowed}
.btn-outline{background:transparent;color:#888;border:1px solid rgba(255,255,255,0.1)}
.btn-outline:hover{color:#e0e0e0;border-color:rgba(255,255,255,0.2)}
.result-section{margin-top:28px;display:none}
.result-section.show{display:block}
.result-group{margin-bottom:28px}
.result-group-title{display:flex;align-items:center;gap:10px;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid rgba(255,255,255,0.06)}
.result-group-title .tag{padding:3px 12px;border-radius:20px;font-size:0.78rem;font-weight:700}
.tag-chong{background:rgba(251,191,36,0.15);color:#fbbf24}
.tag-wen{background:rgba(74,222,128,0.15);color:#4ade80}
.tag-bao{background:rgba(96,165,250,0.15);color:#60a5fa}
.result-group-title h4{font-size:1rem;color:#fff}
.result-table{width:100%;border-collapse:collapse}
.result-table th{text-align:left;padding:8px 12px;background:rgba(255,255,255,0.04);color:#888;font-size:0.78rem;font-weight:600;border-bottom:1px solid rgba(255,255,255,0.06)}
.result-table td{padding:10px 12px;border-bottom:1px solid rgba(255,255,255,0.04);font-size:0.88rem;color:#aaa;vertical-align:top}
.result-table td.school{color:#e0e0e0;font-weight:600}
.result-table .prob-bar{height:4px;background:rgba(255,255,255,0.08);border-radius:2px;width:80px;display:inline-block;vertical-align:middle;margin-right:8px}
.result-table .prob-fill{height:100%;border-radius:2px;background:#667eea}
.waiting{text-align:center;padding:48px 0}
.waiting-icon{font-size:2.5rem;margin-bottom:16px;animation:pulse 2s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
.waiting p{color:#888;font-size:0.9rem}
.waiting .thinking{color:#667eea;font-size:0.85rem;margin-top:8px}
.summary-box{background:rgba(102,126,234,0.08);border:1px solid rgba(102,126,234,0.2);border-radius:12px;padding:18px 20px;margin-bottom:24px}
.summary-box h4{color:#667eea;font-size:0.9rem;margin-bottom:10px}
.summary-box p{color:#ccc;font-size:0.88rem;margin-bottom:8px;line-height:1.7}
.summary-box .stat-row{display:flex;gap:20px;flex-wrap:wrap;margin-top:10px}
.summary-box .stat{text-align:center}
.summary-box .stat .n{font-size:1.4rem;font-weight:800;color:#667eea}
.summary-box .stat .l{font-size:0.75rem;color:#666}
.result-toolbar{display:flex;gap:10px;margin-bottom:16px;flex-wrap:wrap}
.result-toolbar button{display:inline-flex;align-items:center;gap:5px;padding:7px 16px;border-radius:8px;font-size:0.82rem;font-weight:600;cursor:pointer;border:none;transition:all 0.2s}
.btn-copy{background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);color:#aaa}
.btn-copy:hover{background:rgba(255,255,255,0.1);color:#fff}
.btn-copy.copied{background:rgba(74,222,128,0.15);border-color:rgba(74,222,128,0.3);color:#4ade80}
.btn-export{background:rgba(102,126,234,0.15);border:1px solid rgba(102,126,234,0.3);color:#667eea}
.btn-export:hover{background:rgba(102,126,234,0.25)}
/* 页脚 */
.footer{background:rgba(255,255,255,0.03);border-top:1px solid rgba(255,255,255,0.06);padding:30px 0;margin-top:40px;text-align:center}
.footer-logo{font-size:1rem;font-weight:700;color:#fff;margin-bottom:6px}
.footer-logo span{color:#667eea}
.footer-desc{color:#555;font-size:0.8rem;margin-bottom:14px}
.footer-contact-title{color:#888;font-size:0.82rem;margin-bottom:10px}
.footer-qr{width:80px;height:80px;background:rgba(255,255,255,0.05);border:1px dashed rgba(255,255,255,0.15);border-radius:8px;display:inline-flex;align-items:center;justify-content:center;color:#555;font-size:0.7rem;text-align:center;padding:6px;margin-bottom:14px}
.footer-bottom{color:#444;font-size:0.78rem;border-top:1px solid rgba(255,255,255,0.05);padding-top:16px;margin-top:14px}
.footer-inner{max-width:820px;margin:0 auto;padding:0 20px}
</style>
</head>
<body>
<header class="header">
<div class="header-inner">
<a href="/" class="logo">高考志愿<span>助手</span></a>
<ul class="nav-links">
<li><a href="/ai-chat" target="_blank" class="highlight">🤖 AI助手</a></li>
<li><a href="/plan.html">志愿草表</a></li>
<li><a href="/school.html">查校对比</a></li>
<li><a href="/#prob">录取概率估算</a></li>
<li><a href="/major.html">查历年录取分数和位次</a></li>
<li><a href="/#tools">官方查询渠道</a></li>
<li><a href="/#provinces">各省教育考试院入口</a></li>
<li><a href="/#resources">扩展工具 & 数据查询</a></li>
<li><a href="/qa.html">常见问题</a></li>
</ul>
</div>
</header>
<div class="page-hero"><div class="container">
<h1>📝 AI 志愿填写助手</h1>
<p>告诉 AI 你的分数和偏好,它生成可直接填写的志愿草表</p>
</div></div>
<div class="container">
<!-- 步骤条 -->
<div class="step-bar">
<div class="step active" id="step1">
<div class="step-num">1</div>
<div class="step-label">基本信息</div>
</div>
<div class="step" id="step2">
<div class="step-num">2</div>
<div class="step-label">偏好设置</div>
</div>
<div class="step" id="step3">
<div class="step-num">3</div>
<div class="step-label">生成结果</div>
</div>
</div>
<!-- Step 1: 基本信息 -->
<div class="card" id="card1">
<div class="card-title">📋 基本信息</div>
<div class="card-sub">输入高考分数和排名,AI 据此推荐院校</div>
<div class="form-row">
<div class="form-group">
<label>科类</label>
<select id="subject">
<option value="物理类">物理类(理科)</option>
<option value="历史类">历史类(文科)</option>
</select>
</div>
<div class="form-group">
<label>高考分数</label>
<input type="number" id="score" placeholder="如 580" min="200" max="750">
</div>
<div class="form-group">
<label>全省位次(已知则填,不知道可不填)</label>
<input type="number" id="rank" placeholder="如 45887(不填则系统自动通过分数估算)">
<span class="hint">位次可在成绩查询结果中找到,不确定可不填</span>
</div>
</div>
<div class="btn-row">
<button class="btn btn-primary" onclick="goStep(2)">下一步 →</button>
</div>
</div>
<!-- Step 2: 偏好设置 -->
<div class="card" id="card2" style="display:none">
<div class="card-title">⚙️ 偏好设置</div>
<div class="card-sub">告诉 AI 你的就读偏好,生成更精准的推荐</div>
<div class="form-row">
<div class="form-group">
<label>意向专业方向(可多选)</label>
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:4px">
<label class="radio-opt" style="flex:none;min-width:auto"><input type="checkbox" name="major" value="计算机" checked><span class="opt-title" style="font-size:0.82rem">计算机</span></label>
<label class="radio-opt" style="flex:none;min-width:auto"><input type="checkbox" name="major" value="电子信息"><span class="opt-title" style="font-size:0.82rem">电子信息</span></label>
<label class="radio-opt" style="flex:none;min-width:auto"><input type="checkbox" name="major" value="临床医学"><span class="opt-title" style="font-size:0.82rem">临床医学</span></label>
<label class="radio-opt" style="flex:none;min-width:auto"><input type="checkbox" name="major" value="经济金融"><span class="opt-title" style="font-size:0.82rem">经济金融</span></label>
<label class="radio-opt" style="flex:none;min-width:auto"><input type="checkbox" name="major" value="法学"><span class="opt-title" style="font-size:0.82rem">法学</span></label>
<label class="radio-opt" style="flex:none;min-width:auto"><input type="checkbox" name="major" value="师范"><span class="opt-title" style="font-size:0.82rem">师范</span></label>
<label class="radio-opt" style="flex:none;min-width:auto"><input type="checkbox" name="major" value="不限"><span class="opt-title" style="font-size:0.82rem">不限专业</span></label>
</div>
</div>
<div class="form-group">
<label>意向地区</label>
<select id="region">
<option value="不限">不限地区</option>
<option value="河南">河南省内</option>
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="江苏">江苏</option>
<option value="浙江">浙江</option>
<option value="湖北">湖北</option>
<option value="广东">广东</option>
<option value="西安">西安</option>
</select>
</div>
<div class="form-group">
<label>风险偏好</label>
<div class="radio-group">
<label class="radio-opt"><input type="radio" name="risk" value="冲" checked><div class="opt-title">偏冲</div><div class="opt-desc">愿意冲好学校,接受调剂</div></label>
<label class="radio-opt"><input type="radio" name="risk" value="稳"><div class="opt-title">偏稳</div><div class="opt-desc">优先保住录取,专业优先</div></label>
<label class="radio-opt"><input type="radio" name="risk" value="均衡"><div class="opt-title">均衡</div><div class="opt-desc">冲稳保各占一定比例</div></label>
</div>
</div>
<div class="form-group">
<label>其他要求(选填)</label>
<input type="text" id="extra" placeholder="如:希望宿舍有空调、不想出省、体检有受限项目等">
<span class="hint">告诉 AI 更多背景信息,推荐更精准(选填)</span>
</div>
</div>
<div class="btn-row">
<button class="btn btn-outline" onclick="goStep(1)">← 上一步</button>
<button class="btn btn-primary" id="genBtn" onclick="generate()">🎯 生成志愿方案</button>
</div>
</div>
<!-- Step 3: 结果 -->
<div id="resultArea">
<div class="card" id="card3" style="display:none">
<div class="card-title">📊 志愿草表</div>
<div id="summaryBox" class="summary-box" style="display:none"></div>
<div id="waitingArea" class="waiting">
<div class="waiting-icon">🤖</div>
<p>AI 正在根据你的情况生成推荐...</p>
<p class="thinking" id="thinkingText">分析分数和位次中</p>
</div>
<div id="resultContent" class="result-section"></div>
</div>
</div>
<a href="/" style="display:inline-block;margin-top:32px;color:#667eea;text-decoration:none;font-size:0.9rem">← 返回首页</a>
<br><br>
</div>
<script>
var currentStep = 1;
var formData = {};
function goStep(n){
console.log('goStep called, n=', n, 'score=', document.getElementById('score') && document.getElementById('score').value);
if(n===2){
var score = document.getElementById('score').value.trim();
if(!score || score < 200 || score > 750){
alert('请输入有效的高考分数(200-750)');
return;
}
formData.subject = document.getElementById('subject').value;
formData.score = parseInt(score);
formData.rank = document.getElementById('rank').value ? parseInt(document.getElementById('rank').value) : null;
}
// hide all cards
document.getElementById('card1').style.display = 'none';
document.getElementById('card2').style.display = 'none';
document.getElementById('card3').style.display = 'none';
// update step bar
for(var i=1;i<=3;i++){
var s = document.getElementById('step'+i);
s.className = 'step' + (i < n ? ' done' : (i === n ? ' active' : ''));
}
if(n <= 2) {
document.getElementById('card'+n).style.display = 'block';
currentStep = n;
}
}
function getSelectedMajors(){
var majors = [];
document.querySelectorAll('input[name="major"]:checked').forEach(function(el){
if(el.value !== '不限') majors.push(el.value);
});
return majors.length > 0 ? majors.join('、') : '不限专业';
}
function generate(){
formData.region = document.getElementById('region').value;
formData.major = getSelectedMajors();
formData.risk = document.querySelector('input[name="risk"]:checked').value;
formData.extra = document.getElementById('extra').value.trim();
// 保存本次输入到 localStorage
localStorage.setItem('gaokao_plan_last', JSON.stringify({
score: formData.score, subject: formData.subject,
region: formData.region, major: formData.major,
risk: formData.risk, extra: formData.extra
}));
goStep(3);
document.getElementById('card3').style.display = 'block';
var thinking = document.getElementById('thinkingText');
var waiting = document.getElementById('waitingArea');
var resultContent = document.getElementById('resultContent');
var summaryBox = document.getElementById('summaryBox');
resultContent.innerHTML = '';
resultContent.classList.remove('show');
summaryBox.style.display = 'none';
waiting.style.display = 'block';
// loading 多状态轮换
var loadingMsgs = ['正在分析分数和位次...', '正在匹配院校数据...', '正在生成志愿方案...', 'AI正在构思,请稍候...', '快要生成了...'];
var msgIdx = 0;
var msgTimer = setInterval(function(){
msgIdx = (msgIdx + 1) % loadingMsgs.length;
thinking.textContent = loadingMsgs[msgIdx];
}, 4000);
thinking.textContent = loadingMsgs[0];
// 先调用 /api/prob 获取基础数据
fetch('/api/prob', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({score:formData.score, subject:formData.subject})
})
.then(function(r){return r.json();})
.then(function(data){
thinking.textContent = '已获取院校数据,正在生成推荐...';
var rank = data.rank;
var chong = data.chong || [];
var wen = data.wen || [];
var bao = data.bao || [];
var prompt = '你是一个高考志愿填报专家。用户信息:\n' +
'科类:' + formData.subject + '\n' +
'分数:' + formData.score + '分\n' +
'位次:约' + rank.toLocaleString() + '名\n' +
'意向专业:' + formData.major + '\n' +
'意向地区:' + formData.region + '\n' +
'风险偏好:' + formData.risk + '\n' +
(formData.extra ? '其他要求:' + formData.extra + '\n' : '') +
'\n已知可用院校数据:\n' +
'【冲志愿】(较低概率,' + (chong.length) + '所)\n' +
chong.slice(0,8).map(function(s){return s.school + ' 投档分' + s.score + ' 位次' + s.rank + ' 概率' + s.prob;}).join('\n') + '\n\n' +
'【稳志愿】(中等概率,' + (wen.length) + '所)\n' +
wen.slice(0,8).map(function(s){return s.school + ' 投档分' + s.score + ' 位次' + s.rank + ' 概率' + s.prob;}).join('\n') + '\n\n' +
'【保志愿】(较高概率,' + (bao.length) + '所)\n' +
bao.slice(0,8).map(function(s){return s.school + ' 投档分' + s.score + ' 位次' + s.rank + ' 概率' + s.prob;}).join('\n') + '\n\n' +
'请根据以上数据,为用户生成一份完整的志愿填报建议,包含:\n' +
'1. 总体填报策略说明(一段话)\n' +
'2. 冲志愿推荐(' + (formData.risk==='偏冲'?'6-8所':(formData.risk==='偏稳'?'3-4所':'4-5所')) + '),附上推荐理由和志愿排序建议\n' +
'3. 稳志愿推荐(' + (formData.risk==='偏稳'?'8-10所':(formData.risk==='偏冲'?'4-5所':'6-8所')) + '),附上推荐理由\n' +
'4. 保志愿推荐(' + (formData.risk==='偏冲'?'8-10所':(formData.risk==='偏稳'?'4-5所':'5-6所')) + '),附上推荐理由\n' +
'5. 填报顺序建议(哪个学校放第一位,哪个放最后)\n' +
'6. 服从调剂建议\n\n' +
'请用清晰的格式输出,适合用户直接参考填写。';
return new Promise(function(resolve, reject){
var controller = new AbortController();
var totalTimer = setTimeout(function(){ controller.abort(); }, 65000);
var slowPromptShown = false;
var slowTimer = setTimeout(function(){
// 25s 未响应,提示用户选择
slowPromptShown = true;
var overlay = document.createElement('div');
overlay.id = 'slowOverlay';
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:999;display:flex;align-items:center;justify-content:center';
overlay.innerHTML =
'<div style="background:#1a1a2e;border:1px solid rgba(102,126,234,0.3);border-radius:16px;padding:32px;max-width:360px;text-align:center">' +
'<p style="color:#667eea;font-size:1.1rem;margin-bottom:10px">🤖 AI 还在生成中...</p>' +
'<p style="color:#888;font-size:0.88rem;margin-bottom:22px">DeepSeek 服务器较忙,请耐心等待还是取消?</p>' +
'<div style="display:flex;gap:12px;justify-content:center">' +
'<button id="keepWaitingBtn" style="flex:1;padding:10px;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border:none;border-radius:8px;font-weight:600;cursor:pointer">继续等待</button>' +
'<button id="cancelAiBtn" style="flex:1;padding:10px;background:rgba(255,255,255,0.06);color:#888;border:1px solid rgba(255,255,255,0.1);border-radius:8px;font-weight:600;cursor:pointer">取消</button>' +
'</div></div>';
document.body.appendChild(overlay);
document.getElementById('keepWaitingBtn').onclick = function(){
overlay.remove();
thinking.textContent = 'AI 响应中,请继续等待...';
};
document.getElementById('cancelAiBtn').onclick = function(){
overlay.remove();
clearTimeout(totalTimer);
reject(new Error('用户取消,DeepSeek 响应较慢,请稍后再试'));
};
}, 25000);
fetch('/ai-chat', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({message:prompt}),
signal: controller.signal
})
.then(function(r){
clearTimeout(totalTimer);
clearTimeout(slowTimer);
if(slowPromptShown){
var ov = document.getElementById('slowOverlay');
if(ov) ov.remove();
}
if(!r.ok){
throw new Error('AI 服务响应异常(状态' + r.status + '),请稍后重试');
}
return r.json();
})
.then(function(d){ resolve(d); })
.catch(function(e){
clearTimeout(totalTimer);
clearTimeout(slowTimer);
var ov = document.getElementById('slowOverlay');
if(ov) ov.remove();
if(e.name === 'AbortError'){
reject(new Error('请求超时,AI生成时间较长,请减少数据量后重试或切换网络'));
} else {
reject(e);
}
});
});
})
// r 已经是 /chat 返回的 JSON 对象(在上面 resolve(d) 时解析完成)
.then(function(chatData){
var reply = chatData.reply || chatData.response || chatData.message || 'AI 暂时无法生成回复,请稍后重试。';
waiting.style.display = 'none';
summaryBox.innerHTML = '<h4>📋 志愿填报建议</h4><p>科类:' + formData.subject + ' | 分数:' + formData.score + ' | 位次:' + (formData.rank||'(估算)') + '</p><p>专业:' + formData.major + ' | 地区:' + formData.region + ' | 风险偏好:' + formData.risk + '</p>';
summaryBox.style.display = 'block';
resultContent.classList.add('show');
// 写 history
addHistory('plan', 'AI志愿草表', '/plan.html', formData.subject + '类 · ' + formData.score + '分');
// 打字机效果
var rawReply = reply;
var resultDiv = document.createElement('div');
resultDiv.style.cssText = 'background:rgba(255,255,255,0.04);border-radius:10px;padding:20px;color:#ccc;font-size:0.9rem;line-height:2;white-space:pre-wrap;max-height:600px;overflow-y:auto';
// 工具栏(先隐藏,等打完字显示)
var toolbar = document.createElement('div');
toolbar.className = 'result-toolbar';
toolbar.style.visibility = 'hidden';
var copyBtn = document.createElement('button');
copyBtn.className = 'btn-copy';
copyBtn.innerHTML = '📋 复制文字';
copyBtn.onclick = function(){
navigator.clipboard.writeText(rawReply).then(function(){
copyBtn.textContent = '✓ 已复制';
copyBtn.classList.add('copied');
setTimeout(function(){ copyBtn.innerHTML = '📋 复制文字'; copyBtn.classList.remove('copied'); }, 2000);
});
};
var exportBtn = document.createElement('button');
exportBtn.className = 'btn-export';
exportBtn.innerHTML = '📷 导出图片';
exportBtn.onclick = function(){
exportResultAsImage(resultDiv, formData);
};
toolbar.appendChild(copyBtn);
toolbar.appendChild(exportBtn);
var reviewBtn = document.createElement('button');
reviewBtn.className = 'btn-export';
reviewBtn.innerHTML = '🔍 AI审核草表';
reviewBtn.style.background = 'rgba(74,222,128,0.12)';
reviewBtn.style.borderColor = 'rgba(74,222,128,0.3)';
reviewBtn.style.color = '#4ade80';
reviewBtn.onclick = function(){
runAiReview(rawReply, formData, resultDiv);
};
toolbar.appendChild(reviewBtn);
var cursor = document.createElement('span');
cursor.className = 'typewriter-cursor';
resultContent.innerHTML = '';
resultContent.appendChild(toolbar);
resultContent.appendChild(resultDiv);
resultContent.appendChild(cursor);
// 逐字显示
var i = 0;
var lines = rawReply.split('');
function typeNext(){
if(i < lines.length){
// 按行处理,遇到\n暂停
var ch = lines[i];
if(ch === '\n'){
resultDiv.innerHTML += '<br>';
} else {
resultDiv.innerHTML += escHtml(ch);
}
i++;
var delay = ch === '\n' ? 60 : 12;
setTimeout(typeNext, delay);
} else {
cursor.remove();
toolbar.style.visibility = 'visible';
}
}
typeNext();
})
.catch(function(e){
waiting.style.display = 'none';
resultContent.innerHTML = '<div style="color:#ef4444;padding:20px;background:rgba(239,68,68,0.08);border-radius:10px;border:1px solid rgba(239,68,68,0.2)">⚠️ 生成失败:' + e.message + '<br><span style="color:#888;font-size:0.85rem">请检查网络后重试,或联系客服</span></div>';
resultContent.classList.add('show');
});
}
function escHtml(s){
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
}
// ===== 历史记录 =====
function addHistory(type, label, url, meta) {
var key = 'gaokao_history';
var list = JSON.parse(localStorage.getItem(key) || '[]');
list = list.filter(function(item){ return !(item.type === type && item.url === url); });
list.unshift({ type: type, label: label, url: url, meta: meta, time: Date.now() });
if(list.length > 6) list = list.slice(0, 6);
localStorage.setItem(key, JSON.stringify(list));
}
// ===== 导出图片(html2canvas) =====
function exportResultAsImage(resultDiv, formData) {
var summaryEl = document.getElementById('summaryBox');
var container = document.createElement('div');
container.style.cssText = 'background:#0a0a0f;padding:32px;width:800px;font-family:-apple-system,BlinkMacSystemFont,sans-serif';
container.innerHTML =
'<div style="margin-bottom:20px"><h2 style="color:#667eea;font-size:1.3rem;margin-bottom:6px">📋 高考志愿草表</h2>' +
'<p style="color:#888;font-size:0.85rem">科类:' + formData.subject + ' | 分数:' + formData.score + '分 | 位次:' + (formData.rank||'估算') + '</p>' +
'<p style="color:#888;font-size:0.85rem">专业:' + formData.major + ' | 地区:' + formData.region + ' | 风险:' + formData.risk + '</p></div>';
var clone = resultDiv.cloneNode(true);
clone.style.overflow = 'visible';
container.appendChild(clone);
if(!window.html2canvas){
var s = document.createElement('script');
s.src = 'https://cdn.bootcdn.net/ajax/libs/html2canvas/1.4.1/html2canvas.min.js';
s.onload = function(){ doExport(); };
document.head.appendChild(s);
} else {
doExport();
}
function doExport(){
html2canvas(container, {backgroundColor:'#0a0a0f',scale:2,useCORS:true}).then(function(canvas){
var link = document.createElement('a');
link.download = '志愿草表_' + formData.score + '分_' + Date.now() + '.png';
link.href = canvas.toDataURL('image/png');
link.click();
}).catch(function(){
alert('导出失败,请尝试复制文字后截图');
});
}
}
// ===== AI审核草表 =====
function runAiReview(rawReply, formData, resultDiv) {
var reviewPrompt = '你是一个高考志愿审核专家。请仔细审核以下志愿填报草表,指出潜在风险并给出修改建议:\n\n' +
'用户信息:科类=' + formData.subject + ',分数=' + formData.score + '分,专业意向=' + formData.major + ',地区偏好=' + formData.region + ',风险偏好=' + formData.risk + '\n\n' +
'AI生成的志愿草表:\n' + rawReply + '\n\n' +
'请从以下维度审核,并给出评分(1-10分)和改进建议:\n' +
'1. 冲稳保比例是否合理\n' +
'2. 是否有分数重叠(两所学校分差过小)\n' +
'3. 是否勾选了服从调剂\n' +
'4. 志愿填报顺序是否合理\n' +
'5. 整体风险评估\n\n' +
'请用简洁的格式输出,直接指出问题和建议。';
// 显示审核加载状态
var existingReview = document.getElementById('reviewResult');
if(existingReview) existingReview.remove();
var reviewBox = document.createElement('div');
reviewBox.id = 'reviewResult';
reviewBox.style.cssText = 'margin-top:20px;background:rgba(74,222,128,0.06);border:1px solid rgba(74,222,128,0.2);border-radius:12px;padding:20px';
reviewBox.innerHTML = '<p style="color:#4ade80;font-size:0.88rem">🔍 AI正在审核草表,请稍候...</p>';
resultDiv.parentNode.insertBefore(reviewBox, resultDiv.nextSibling);
fetch('/ai-chat', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({message:reviewPrompt})
})
.then(function(r){ return r.json(); })
.then(function(d){
var review = d.reply || d.response || '审核完成,未发现问题。';
reviewBox.innerHTML = '<p style="color:#4ade80;font-size:0.9rem;font-weight:600;margin-bottom:10px">🔍 AI草表审核结果</p>' +
'<div style="color:#ccc;font-size:0.88rem;line-height:1.8;white-space:pre-wrap">' +
review.replace(/</g,'<').replace(/\n/g,'<br>') + '</div>';
})
.catch(function(){
reviewBox.innerHTML = '<p style="color:#ef4444;font-size:0.88rem">⚠️ 审核请求失败,请稍后重试</p>';
});
}
// ===== 页面加载时恢复上次输入 =====
document.addEventListener('DOMContentLoaded', function(){
var last = JSON.parse(localStorage.getItem('gaokao_plan_last') || 'null');
if(!last) return;
var scoreEl = document.getElementById('score');
if(scoreEl) scoreEl.value = last.score || '';
var regionEl = document.getElementById('region');
if(regionEl && last.region) regionEl.value = last.region;
var riskEl = document.querySelector('input[name="risk"][value="'+last.risk+'"]');
if(riskEl) riskEl.checked = true;
var extraEl = document.getElementById('extra');
if(extraEl && last.extra) extraEl.value = last.extra;
// 恢复 major checkboxes
if(last.major && last.major !== '不限专业'){
var majors = last.major.split('、');
majors.forEach(function(m){
var el = document.querySelector('input[name="major"][value="'+m+'"]');
if(el) el.checked = true;
});
}
// 显示快捷继续提示
if(last.score){
var heroSub = document.querySelector('.page-hero p');
if(heroSub) heroSub.textContent = '上次您填写了 ' + last.score + ' 分(' + (last.subject||'') + '类),直接修改后即可重新生成';
}
});
</script>
<div class="footer">
<div class="footer-inner">
<p class="footer-logo">高考志愿<span>助手</span></p>
<p class="footer-desc">数据来源:河南省教育考试院,仅供参考,不作为正式依据</p>
<p class="footer-contact-title">遇到问题?</p>
<div class="footer-qr">📷 微信二维码<br><span style="font-size:0.65rem;color:#666">待添加</span></div>
<div class="footer-bottom">© 2026 河南高考志愿助手 · 仅供辅助参考,不构成正式志愿填报依据</div>
</div>
</div>
</body>
</html>