- 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/: 数据采集和处理工具
635 lines
35 KiB
HTML
635 lines
35 KiB
HTML
<!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> |