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/: 数据采集和处理工具
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.db
|
||||
*.log
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>AI 志愿咨询 - 河南高考</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif; background: #0a0a0f; height: 100vh; display: flex; flex-direction: column; color: #e0e0e0; }
|
||||
.header { background: rgba(20,20,30,0.95); backdrop-filter: blur(20px); border-bottom: 1px solid rgba(255,255,255,0.08); color: white; padding: 14px 20px; display: flex; align-items: center; gap: 12px; position: sticky; top: 0; z-index: 100; }
|
||||
.header-back { color: #a0a0a0; text-decoration: none; font-size: 14px; display: flex; align-items: center; gap: 4px; transition: color 0.3s; }
|
||||
.header-back:hover { color: #667eea; }
|
||||
.header h1 { font-size: 17px; font-weight: 600; flex: 1; display: flex; align-items: center; gap: 8px; }
|
||||
.header-new { background: rgba(102,126,234,0.2); border: 1px solid rgba(102,126,234,0.4); color: #667eea; padding: 6px 14px; border-radius: 20px; font-size: 13px; cursor: pointer; text-decoration: none; transition: all 0.3s; }
|
||||
.header-new:hover { background: rgba(102,126,234,0.3); }
|
||||
.chat-container { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; background: #0a0a0f; }
|
||||
.message { display: flex; gap: 10px; max-width: 85%; animation: fadeIn 0.3s ease; }
|
||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
|
||||
.message.ai { align-self: flex-start; }
|
||||
.message.user { align-self: flex-end; flex-direction: row-reverse; }
|
||||
.avatar { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; }
|
||||
.message.ai .avatar { background: linear-gradient(135deg, #667eea, #764ba2); color: white; }
|
||||
.message.user .avatar { background: rgba(255,255,255,0.1); color: #a0a0a0; border: 1px solid rgba(255,255,255,0.1); }
|
||||
.bubble { padding: 12px 16px; border-radius: 16px; font-size: 15px; line-height: 1.7; word-break: break-word; }
|
||||
.message.ai .bubble { background: rgba(255,255,255,0.06); color: #d0d0d0; border: 1px solid rgba(255,255,255,0.08); border-top-left-radius: 4px; }
|
||||
.message.user .bubble { background: linear-gradient(135deg, #667eea, #764ba2); color: white; border-top-right-radius: 4px; }
|
||||
.bubble h1,.bubble h2,.bubble h3,.bubble h4 { color: #e8e8e8; margin: 12px 0 6px; font-size: 15px; font-weight: 600; }
|
||||
.bubble h1:first-child,.bubble h2:first-child,.bubble h3:first-child { margin-top: 0; }
|
||||
.bubble h3 { color: #c0c0c0; font-size: 14px; }
|
||||
.bubble hr { border: none; border-top: 1px solid rgba(255,255,255,0.1); margin: 12px 0; }
|
||||
.bubble p { margin: 6px 0; }
|
||||
.bubble p:first-child { margin-top: 0; }
|
||||
.bubble p:last-child { margin-bottom: 0; }
|
||||
.bubble strong { color: #fff; font-weight: 600; }
|
||||
.bubble em { color: #a0a0a0; font-style: italic; }
|
||||
.bubble ul,.bubble ol { padding-left: 20px; margin: 6px 0; }
|
||||
.bubble li { margin: 3px 0; }
|
||||
.bubble .tag { display: inline-block; font-size: 11px; padding: 1px 6px; border-radius: 4px; margin-right: 4px; }
|
||||
.bubble .tag.冲 { background: rgba(251,191,36,0.15); color: #fbbf24; }
|
||||
.bubble .tag.稳 { background: rgba(34,197,94,0.15); color: #4ade80; }
|
||||
.bubble .tag.保 { background: rgba(59,130,246,0.15); color: #60a5fa; }
|
||||
.bubble table { border-collapse: collapse; width: 100%; margin: 8px 0; font-size: 13px; }
|
||||
.bubble th { color: #667eea; padding: 4px 8px; border: 1px solid rgba(255,255,255,0.08); text-align: left; }
|
||||
.bubble td { color: #ccc; padding: 4px 8px; border: 1px solid rgba(255,255,255,0.06); }
|
||||
.bubble code { background: rgba(255,255,255,0.08); padding: 1px 5px; border-radius: 4px; font-size: 13px; }
|
||||
.typing { display: flex; gap: 10px; align-self: flex-start; max-width: 85%; }
|
||||
.typing .avatar { background: linear-gradient(135deg, #667eea, #764ba2); color: white; }
|
||||
.typing-dots { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08); padding: 12px 16px; border-radius: 16px; border-top-left-radius: 4px; display: flex; gap: 5px; align-items: center; }
|
||||
.typing-dots span { width: 7px; height: 7px; background: #667eea; border-radius: 50%; animation: typing 1.4s infinite; }
|
||||
.typing-dots span:nth-child(2) { animation-delay: 0.2s; }
|
||||
.typing-dots span:nth-child(3) { animation-delay: 0.4s; }
|
||||
@keyframes typing { 0%,60%,100% { transform: translateY(0); opacity: 0.4; } 30% { transform: translateY(-6px); opacity: 1; } }
|
||||
.input-area { background: rgba(20,20,30,0.95); backdrop-filter: blur(20px); padding: 14px 16px; display: flex; gap: 10px; border-top: 1px solid rgba(255,255,255,0.08); }
|
||||
.input-area input { flex: 1; padding: 12px 16px; border: 1.5px solid rgba(255,255,255,0.12); border-radius: 24px; font-size: 15px; outline: none; background: rgba(255,255,255,0.06); color: #fff; transition: border-color 0.2s; }
|
||||
.input-area input:focus { border-color: #667eea; }
|
||||
.input-area input::placeholder { color: #555; }
|
||||
.input-area button { background: linear-gradient(135deg, #667eea, #764ba2); color: white; border: none; padding: 10px 22px; border-radius: 24px; font-size: 15px; cursor: pointer; font-weight: 600; transition: opacity 0.2s; }
|
||||
.input-area button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.start-screen { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; text-align: center; gap: 24px; background: #0a0a0f; }
|
||||
.start-screen .icon { background: linear-gradient(135deg, #667eea, #764ba2); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-size: 80px; line-height: 1; }
|
||||
.start-screen h2 { font-size: 22px; color: #fff; }
|
||||
.start-screen p { color: #888; font-size: 15px; line-height: 1.7; max-width: 340px; }
|
||||
.start-btn { background: linear-gradient(135deg, #667eea, #764ba2); color: white; border: none; padding: 14px 40px; border-radius: 30px; font-size: 16px; cursor: pointer; font-weight: 600; box-shadow: 0 4px 20px rgba(102,126,234,0.35); transition: transform 0.2s, box-shadow 0.2s; }
|
||||
.start-btn:hover { transform: scale(1.03); box-shadow: 0 6px 25px rgba(102,126,234,0.5); }
|
||||
.start-features { display: flex; gap: 20px; flex-wrap: wrap; justify-content: center; margin-top: 5px; }
|
||||
.start-features span { color: #888; font-size: 13px; display: flex; align-items: center; gap: 5px; }
|
||||
.start-features span::before { content: '✓'; color: #667eea; }
|
||||
.hidden { display: none !important; }
|
||||
.msg-time { text-align: center; color: #444; font-size: 12px; margin: 10px 0; }
|
||||
@media (max-width: 480px) { .chat-container { padding: 12px; gap: 12px; } .bubble { font-size: 15px; padding: 10px 14px; } .avatar { width: 30px; height: 30px; font-size: 14px; } .start-screen { padding: 30px 15px; } .start-screen .icon { font-size: 64px; } .input-area { padding: 10px 12px; padding-bottom: max(10px, env(safe-area-inset-bottom)); gap: 8px; } .input-area input { padding: 10px 14px; font-size: 16px; } .input-area button { padding: 10px 16px; font-size: 14px; } .header h1 { font-size: 15px; } .message { max-width: 90%; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<a href="/" class="header-back">← 返回首页</a>
|
||||
<h1>🤖 AI 志愿咨询</h1>
|
||||
<a href="#" class="header-new" onclick="startNew(); return false;">新对话</a>
|
||||
</div>
|
||||
<div id="startScreen" class="start-screen">
|
||||
<div class="icon">🎓</div>
|
||||
<h2>河南高考志愿推荐</h2>
|
||||
<p>AI 智能对话,收集分数、意向专业和地区偏好,为你推荐冲 / 稳 / 保院校方案</p>
|
||||
<button class="start-btn" onclick="startChat()">开始咨询</button>
|
||||
<div class="start-features"><span>分数分析</span><span>智能推荐</span><span>冲稳保方案</span></div>
|
||||
</div>
|
||||
<div id="chatContainer" class="chat-container hidden"></div>
|
||||
<div id="typingIndicator" class="typing hidden" style="padding: 0 20px;">
|
||||
<div class="avatar">🤖</div>
|
||||
<div class="typing-dots"><span></span><span></span><span></span></div>
|
||||
</div>
|
||||
<div id="inputArea" class="input-area hidden">
|
||||
<input type="text" id="userInput" placeholder="输入你的回答…" onkeydown="if(event.key==='Enter')sendMessage()" autocomplete="off">
|
||||
<button id="sendBtn" onclick="sendMessage()">发送</button>
|
||||
</div>
|
||||
<script>
|
||||
marked.setOptions({ breaks: true, gfm: true });
|
||||
|
||||
let sessionId = '';
|
||||
let loading = false;
|
||||
|
||||
async function startChat() {
|
||||
document.getElementById('startScreen').classList.add('hidden');
|
||||
document.getElementById('chatContainer').classList.remove('hidden');
|
||||
document.getElementById('inputArea').classList.remove('hidden');
|
||||
const res = await fetch('/api/chat/start', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
sessionId = data.session_id;
|
||||
addMessage('ai', data.message);
|
||||
document.getElementById('userInput').focus();
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
if (loading) return;
|
||||
const input = document.getElementById('userInput');
|
||||
const text = input.value.trim();
|
||||
if (!text) return;
|
||||
loading = true;
|
||||
document.getElementById('sendBtn').disabled = true;
|
||||
addMessage('user', text);
|
||||
input.value = '';
|
||||
document.getElementById('typingIndicator').classList.remove('hidden');
|
||||
scrollBottom();
|
||||
try {
|
||||
const res = await fetch('/api/chat/respond', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ session_id: sessionId, message: text }),
|
||||
});
|
||||
const data = await res.json();
|
||||
document.getElementById('typingIndicator').classList.add('hidden');
|
||||
if (data.error) { addMessage('ai', '⚠️ ' + data.error); }
|
||||
else {
|
||||
addMessage('ai', data.message);
|
||||
if (data.done) { addMessage('ai', '📋 以上就是为你推荐的院校方案。如有其他问题(如同专业院校对比、录取规则等)欢迎继续咨询!'); }
|
||||
}
|
||||
} catch(e) {
|
||||
document.getElementById('typingIndicator').classList.add('hidden');
|
||||
addMessage('ai', '⚠️ 网络异常,请检查网络连接后重试。');
|
||||
}
|
||||
loading = false;
|
||||
document.getElementById('sendBtn').disabled = false;
|
||||
input.focus();
|
||||
}
|
||||
|
||||
function addMessage(role, content) {
|
||||
const container = document.getElementById('chatContainer');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'message ' + role;
|
||||
const avatar = role === 'ai' ? '🤖' : '👤';
|
||||
const formatted = role === 'ai' ? marked.parse(content) : content;
|
||||
div.innerHTML = '<div class="avatar">'+avatar+'</div><div class="bubble">'+formatted+'</div>';
|
||||
container.appendChild(div);
|
||||
scrollBottom();
|
||||
}
|
||||
|
||||
function scrollBottom() {
|
||||
const container = document.getElementById('chatContainer');
|
||||
requestAnimationFrame(() => { container.scrollTop = 1e9; });
|
||||
}
|
||||
|
||||
async function startNew() {
|
||||
sessionId = '';
|
||||
document.getElementById('chatContainer').innerHTML = '';
|
||||
document.getElementById('startScreen').classList.remove('hidden');
|
||||
document.getElementById('chatContainer').classList.add('hidden');
|
||||
document.getElementById('inputArea').classList.add('hidden');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
<!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>
|
||||
<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.6; }
|
||||
.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 { 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; }
|
||||
.container { max-width: 800px; margin: 0 auto; padding: 0 20px; }
|
||||
.header h1 { font-size: 1.1rem; color: #fff; }
|
||||
|
||||
.hero { text-align: center; margin-bottom: 40px; }
|
||||
.hero h2 { font-size: 2rem; color: #fff; margin-bottom: 10px; }
|
||||
.hero p { color: #888; font-size: 1rem; }
|
||||
.hero .tag { display: inline-block; background: linear-gradient(135deg, #667eea, #764ba2); color: #fff; padding: 4px 14px; border-radius: 20px; font-size: 0.85rem; margin-bottom: 15px; }
|
||||
|
||||
.form-card { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 20px; padding: 35px; margin-bottom: 30px; }
|
||||
.form-title { font-size: 1.1rem; color: #fff; margin-bottom: 25px; border-bottom: 1px solid rgba(255,255,255,0.06); padding-bottom: 15px; }
|
||||
|
||||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; }
|
||||
.form-row.full { grid-template-columns: 1fr; }
|
||||
|
||||
.form-group { display: flex; flex-direction: column; gap: 8px; }
|
||||
.form-group label { font-size: 0.9rem; color: #aaa; }
|
||||
.form-group input, .form-group select {
|
||||
padding: 14px 18px; border: 1px solid rgba(255,255,255,0.12);
|
||||
border-radius: 12px; background: rgba(255,255,255,0.04);
|
||||
color: #fff; font-size: 1rem; outline: none;
|
||||
transition: border-color 0.3s; appearance: none;
|
||||
}
|
||||
.form-group input:focus, .form-group select:focus { border-color: #667eea; }
|
||||
.form-group input::placeholder { color: #555; }
|
||||
.form-group select option { background: #1a1a2e; color: #fff; }
|
||||
|
||||
.score-input-wrap { position: relative; }
|
||||
.score-input-wrap input { width: 100%; padding-right: 50px; }
|
||||
.score-unit { position: absolute; right: 18px; top: 50%; transform: translateY(-50%); color: #667eea; font-size: 1rem; pointer-events: none; }
|
||||
|
||||
.submit-btn {
|
||||
width: 100%; padding: 16px; border: none; border-radius: 14px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
color: #fff; font-size: 1.05rem; font-weight: 600;
|
||||
cursor: pointer; margin-top: 10px; transition: opacity 0.3s;
|
||||
}
|
||||
.submit-btn:hover { opacity: 0.9; }
|
||||
.submit-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
.result-card { display: none; margin-top: 30px; }
|
||||
.result-card.show { display: block; }
|
||||
|
||||
.result-summary {
|
||||
background: rgba(102,126,234,0.1); border: 1px solid rgba(102,126,234,0.25);
|
||||
border-radius: 16px; padding: 24px; margin-bottom: 25px;
|
||||
}
|
||||
.result-summary h3 { color: #667eea; font-size: 0.9rem; margin-bottom: 8px; }
|
||||
.result-summary p { color: #c0c0c0; font-size: 1rem; line-height: 1.7; }
|
||||
|
||||
.result-section { margin-bottom: 25px; }
|
||||
.result-section-title { font-size: 1rem; color: #fff; margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
|
||||
.result-section-title .badge {
|
||||
font-size: 0.75rem; padding: 3px 10px; border-radius: 10px; font-weight: 600;
|
||||
}
|
||||
.badge-chong { background: rgba(239,68,68,0.15); color: #ef4444; }
|
||||
.badge-wen { background: rgba(245,158,11,0.15); color: #f59e0b; }
|
||||
.badge-bao { background: rgba(34,197,94,0.15); color: #22c55e; }
|
||||
|
||||
.school-item {
|
||||
background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.07);
|
||||
border-radius: 12px; padding: 18px 20px; margin-bottom: 10px;
|
||||
}
|
||||
.school-item h4 { color: #fff; font-size: 1rem; margin-bottom: 6px; }
|
||||
.school-item .reason { color: #aaa; font-size: 0.88rem; margin-bottom: 5px; }
|
||||
.school-item .risk { color: #f59e0b; font-size: 0.85rem; }
|
||||
.tuition-tag {
|
||||
display: inline-block;
|
||||
background: rgba(102,126,234,0.18);
|
||||
color: #a5b4fc;
|
||||
border: 1px solid rgba(102,126,234,0.3);
|
||||
border-radius: 20px;
|
||||
padding: 2px 10px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
vertical-align: middle;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.tips-section { margin-top: 25px; }
|
||||
.tips-title { color: #fff; font-size: 1rem; margin-bottom: 12px; }
|
||||
.tips-list { list-style: none; }
|
||||
.tips-list li { color: #aaa; font-size: 0.9rem; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.04); display: flex; gap: 10px; }
|
||||
.tips-list li:before { content: '💡'; flex-shrink: 0; }
|
||||
|
||||
.loading { display: none; text-align: center; padding: 50px 0; }
|
||||
.loading.show { display: block; }
|
||||
.spinner {
|
||||
width: 40px; height: 40px; border: 3px solid rgba(102,126,234,0.2);
|
||||
border-top-color: #667eea; border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite; margin: 0 auto 15px;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.loading p { color: #888; font-size: 0.95rem; }
|
||||
|
||||
.error-msg { display: none; background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3); border-radius: 12px; padding: 16px; color: #ef4444; text-align: center; margin-top: 15px; }
|
||||
.error-msg.show { display: block; }
|
||||
|
||||
.footer { text-align: center; padding: 40px 20px 20px; color: #555; font-size: 0.85rem; border-top: 1px solid rgba(255,255,255,0.05); margin-top: 60px; }
|
||||
|
||||
@media (max-width: 600px) { .form-row { grid-template-columns: 1fr; } .form-card { padding: 24px 20px; } .hero h2 { font-size: 1.6rem; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<div class="header-inner">
|
||||
<a href="/" class="logo">高考志愿<span>助手</span></a>
|
||||
<nav>
|
||||
<ul class="nav-links">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li><a href="/school.html">查院校</a></li>
|
||||
<li><a href="/major.html">查专业</a></li>
|
||||
<li><a href="/qa.html">招生章程</a></li>
|
||||
<li><a href="/ai-chat.html">AI对话</a></li>
|
||||
<li><a href="/ai-tool.html" class="highlight">AI推荐</a></li>
|
||||
<li><a href="/plan.html">志愿草表</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div class="hero">
|
||||
<div class="tag">🎯 AI驱动</div>
|
||||
<h2>智能推荐志愿方案</h2>
|
||||
<p>输入分数,AI分析冲/稳/保院校组合</p>
|
||||
</div>
|
||||
|
||||
<div class="form-card">
|
||||
<div class="form-title">填写考生信息</div>
|
||||
|
||||
<div class="form-row full">
|
||||
<div class="form-group">
|
||||
<label>高考分数(理科/文科)</label>
|
||||
<div class="score-input-wrap">
|
||||
<input type="number" id="scoreInput" min="100" max="800" placeholder="请输入高考分数" />
|
||||
<span class="score-unit">分</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>科类</label>
|
||||
<select id="categorySelect">
|
||||
<option value="理科">理科</option>
|
||||
<option value="文科">文科</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>报考批次</label>
|
||||
<select id="batchSelect">
|
||||
<option value="本科一批">本科一批</option>
|
||||
<option value="本科二批">本科二批</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="submit-btn" id="submitBtn" onclick="getRecommendation()">
|
||||
🚀 获取AI推荐方案
|
||||
</button>
|
||||
|
||||
<div class="error-msg" id="errorMsg"></div>
|
||||
</div>
|
||||
|
||||
<div class="loading" id="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>AI正在分析院校数据…</p>
|
||||
<p style="color:#555;font-size:0.85rem;margin-top:8px">预计10-30秒完成</p>
|
||||
</div>
|
||||
|
||||
<div class="result-card" id="resultCard">
|
||||
<div class="result-summary">
|
||||
<h3>📊 分数段分析</h3>
|
||||
<p id="summaryText"></p>
|
||||
</div>
|
||||
|
||||
<div class="result-section" id="chongSection">
|
||||
<div class="result-section-title">
|
||||
<span class="badge badge-chong">冲</span>
|
||||
<span style="color:#aaa;font-size:0.85rem">录取概率30%-50%,高于分数5-15分</span>
|
||||
</div>
|
||||
<div id="chongList"></div>
|
||||
</div>
|
||||
|
||||
<div class="result-section" id="wenSection">
|
||||
<div class="result-section-title">
|
||||
<span class="badge badge-wen">稳</span>
|
||||
<span style="color:#aaa;font-size:0.85rem">录取概率60%-80%,分数附近</span>
|
||||
</div>
|
||||
<div id="wenList"></div>
|
||||
</div>
|
||||
|
||||
<div class="result-section" id="baoSection">
|
||||
<div class="result-section-title">
|
||||
<span class="badge badge-bao">保</span>
|
||||
<span style="color:#aaa;font-size:0.85rem">录取概率85%以上,低于分数15分以上</span>
|
||||
</div>
|
||||
<div id="baoList"></div>
|
||||
</div>
|
||||
|
||||
<div class="tips-section">
|
||||
<div class="tips-title">📌 志愿填报注意事项</div>
|
||||
<ul class="tips-list" id="tipsList"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>AI推荐仅供参考,最终以官方公布数据为准</p>
|
||||
<p style="margin-top:5px;color:#444">Powered by NVIDIA NIM · minimax-m2.7</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function getRecommendation() {
|
||||
var score = parseInt(document.getElementById('scoreInput').value);
|
||||
var category = document.getElementById('categorySelect').value;
|
||||
var batch = document.getElementById('batchSelect').value;
|
||||
var btn = document.getElementById('submitBtn');
|
||||
var loading = document.getElementById('loading');
|
||||
var resultCard = document.getElementById('resultCard');
|
||||
var errorMsg = document.getElementById('errorMsg');
|
||||
|
||||
if (!score || score < 100 || score > 800) {
|
||||
showError('请输入有效的分数(100-800)');
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = '分析中…';
|
||||
loading.classList.add('show');
|
||||
resultCard.classList.remove('show');
|
||||
errorMsg.classList.remove('show');
|
||||
|
||||
try {
|
||||
var resp = await fetch('/api/recommend', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ score: score, category: category, batch: batch })
|
||||
});
|
||||
var data = await resp.json();
|
||||
|
||||
if (!resp.ok) {
|
||||
showError(data.error || '服务暂时不可用,请稍后重试');
|
||||
return;
|
||||
}
|
||||
|
||||
renderResult(data);
|
||||
resultCard.classList.add('show');
|
||||
} catch (e) {
|
||||
showError('网络错误,请检查网络连接后重试');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = '🚀 获取AI推荐方案';
|
||||
loading.classList.remove('show');
|
||||
}
|
||||
}
|
||||
|
||||
function renderResult(data) {
|
||||
document.getElementById('summaryText').textContent = data.summary || '';
|
||||
|
||||
renderSchoolList('chongList', data.chong || []);
|
||||
renderSchoolList('wenList', data.wen || []);
|
||||
renderSchoolList('baoList', data.bao || []);
|
||||
|
||||
var tipsList = document.getElementById('tipsList');
|
||||
tipsList.innerHTML = '';
|
||||
(data.tips || []).forEach(function(tip) {
|
||||
var li = document.createElement('li');
|
||||
li.textContent = tip;
|
||||
tipsList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function renderSchoolList(containerId, schools) {
|
||||
var container = document.getElementById(containerId);
|
||||
container.innerHTML = '';
|
||||
schools.forEach(function(s) {
|
||||
var div = document.createElement('div');
|
||||
div.className = 'school-item';
|
||||
// 计算概率颜色
|
||||
var probColor = s.prob >= 80 ? '#22c55e' : s.prob >= 50 ? '#f59e0b' : '#ef4444';
|
||||
var probLabel = s.prob >= 80 ? '较稳' : s.prob >= 50 ? '可冲' : '有风险';
|
||||
// 学费显示
|
||||
var tuitionHtml = s.tuition_annual
|
||||
? '<span class="tuition-tag">' + s.tuition_annual.toLocaleString() + '元/年</span>'
|
||||
: '';
|
||||
div.innerHTML =
|
||||
'<h4>' + s.school + ' ' + tuitionHtml + '</h4>' +
|
||||
'<div class="reason">' +
|
||||
'<span>' + (s.req || '') + '</span> · ' +
|
||||
'<span>' + s.score + '分</span> · ' +
|
||||
'<span>位次' + (s.rank || '') + '</span> · ' +
|
||||
'<span style="color:' + probColor + ';font-weight:600">' +
|
||||
s.prob + '% ' + probLabel +
|
||||
'</span>' +
|
||||
'</div>' +
|
||||
(s.diff !== undefined ? '<div class="risk">批次线差:+' + s.diff + '分</div>' : '');
|
||||
container.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
var el = document.getElementById('errorMsg');
|
||||
el.textContent = msg;
|
||||
el.classList.add('show');
|
||||
document.getElementById('loading').classList.remove('show');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,134 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>功能自检 - 高考志愿助手</title>
|
||||
<style>
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0a0a0f;color:#e0e0e0;padding:30px;line-height:1.8}
|
||||
h1{color:#667eea;font-size:1.5rem}
|
||||
.test{margin:20px 0;padding:16px;background:rgba(255,255,255,0.05);border-radius:10px;border:1px solid rgba(255,255,255,0.1)}
|
||||
.test h3{color:#fff;margin:0 0 10px;font-size:1rem}
|
||||
.result{padding:10px;border-radius:6px;margin:8px 0;font-size:0.9rem}
|
||||
.ok{background:rgba(74,222,128,0.1);border:1px solid rgba(74,222,128,0.3);color:#4ade80}
|
||||
.err{background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.3);color:#ef4444}
|
||||
.pending{background:rgba(251,191,36,0.1);border:1px solid rgba(251,191,36,0.3);color:#fbbf24}
|
||||
.school-item{padding:6px 0;border-bottom:1px solid rgba(255,255,255,0.05);font-size:0.88rem;color:#ccc}
|
||||
button{padding:8px 16px;background:#667eea;color:#fff;border:none;border-radius:6px;cursor:pointer;margin:4px}
|
||||
button:hover{opacity:0.85}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🔧 功能自检</h1>
|
||||
|
||||
<div class="test">
|
||||
<h3>1. 页面基础信息</h3>
|
||||
<div id="pageInfo" class="result pending">检测中...</div>
|
||||
</div>
|
||||
|
||||
<div class="test">
|
||||
<h3>2. 学校搜索 API</h3>
|
||||
<button onclick="testSchool()">测试搜索「郑州大学」</button>
|
||||
<div id="schoolResult" class="result pending">点击按钮开始测试</div>
|
||||
</div>
|
||||
|
||||
<div class="test">
|
||||
<h3>3. 概率计算 API</h3>
|
||||
<button onclick="testProb()">测试 580分 物理类</button>
|
||||
<div id="probResult" class="result pending">点击按钮开始测试</div>
|
||||
</div>
|
||||
|
||||
<div class="test">
|
||||
<h3>4. AI 对话 API</h3>
|
||||
<button onclick="testChat()">测试发送消息</button>
|
||||
<div id="chatResult" class="result pending">点击按钮开始测试</div>
|
||||
</div>
|
||||
|
||||
<div class="test">
|
||||
<h3>5. CSS 下拉框可见性测试</h3>
|
||||
<label style="display:block;margin:10px 0;font-weight:500">科类选择:</label>
|
||||
<select style="padding:12px 16px;background:rgba(255,255,255,0.12);border:2px solid rgba(255,255,255,0.3);border-radius:8px;color:#e0e0e0;font-size:1rem;width:100%;max-width:400px">
|
||||
<option>物理类(理科)</option>
|
||||
<option>历史类(文科)</option>
|
||||
</select>
|
||||
<div style="margin-top:8px;font-size:0.85rem;color:#888">如果上面这个下拉框的文字和箭头都清晰可见,说明CSS已修复</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('pageInfo').innerHTML =
|
||||
'URL: ' + location.href + '<br>' +
|
||||
'User Agent: ' + navigator.userAgent.substring(0, 50) + '...<br>' +
|
||||
'屏幕宽度: ' + screen.width + 'px';
|
||||
|
||||
function testSchool(){
|
||||
var el = document.getElementById('schoolResult');
|
||||
el.className = 'result pending';
|
||||
el.textContent = '请求中...';
|
||||
var kw = '郑州大学';
|
||||
fetch('/api/school/search?q=' + encodeURIComponent(kw) + '&subject=' + encodeURIComponent('物理类'))
|
||||
.then(function(r){
|
||||
if(!r.ok) throw new Error('HTTP ' + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(function(d){
|
||||
var schools = d.schools || [];
|
||||
if(schools.length === 0){
|
||||
el.className = 'result err';
|
||||
el.innerHTML = '❌ API返回0条结果。请截图反馈给管理员。';
|
||||
return;
|
||||
}
|
||||
el.className = 'result ok';
|
||||
el.innerHTML = '✅ 搜索正常!找到 ' + schools.length + ' 条结果:<br>' +
|
||||
schools.slice(0,5).map(function(s){
|
||||
return '<div class="school-item">'+s.school+' | '+s.score+'分 | 位次'+Number(s.rank).toLocaleString()+'</div>';
|
||||
}).join('');
|
||||
})
|
||||
.catch(function(e){
|
||||
el.className = 'result err';
|
||||
el.textContent = '❌ 请求失败: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
function testProb(){
|
||||
var el = document.getElementById('probResult');
|
||||
el.className = 'result pending';
|
||||
el.textContent = '请求中...';
|
||||
fetch('/api/prob', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({score:580, subject:'物理类'})
|
||||
})
|
||||
.then(function(r){ if(!r.ok) throw new Error('HTTP '+r.status); return r.json(); })
|
||||
.then(function(d){
|
||||
el.className = 'result ok';
|
||||
el.innerHTML = '✅ 正常!rank='+d.rank+'<br>冲:'+d.chong.length+'所 稳:'+d.wen.length+'所 保:'+d.bao.length+'所';
|
||||
})
|
||||
.catch(function(e){
|
||||
el.className = 'result err';
|
||||
el.textContent = '❌ 失败: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
function testChat(){
|
||||
var el = document.getElementById('chatResult');
|
||||
el.className = 'result pending';
|
||||
el.textContent = '请求中(AI响应需要几秒)...';
|
||||
fetch('/chat', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({message:'说hello'})
|
||||
})
|
||||
.then(function(r){ if(!r.ok) throw new Error('HTTP '+r.status); return r.json(); })
|
||||
.then(function(d){
|
||||
var reply = d.reply || d.message || '';
|
||||
el.className = 'result ok';
|
||||
el.innerHTML = '✅ AI正常!回复'+reply.length+'字:<br><div style="margin-top:8px;padding:10px;background:rgba(255,255,255,0.04);border-radius:6px;font-size:0.88rem;color:#ccc">'+reply.substring(0,150)+'</div>';
|
||||
})
|
||||
.catch(function(e){
|
||||
el.className = 'result err';
|
||||
el.textContent = '❌ 失败: ' + e.message;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,533 @@
|
||||
"""
|
||||
高考AI服务 v3 — 数据库驱动推荐 + 概率计算器
|
||||
"""
|
||||
import sqlite3, os, json, re, uuid
|
||||
from flask import Flask, request, jsonify, send_file
|
||||
from datetime import datetime
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# ===== 配置 =====
|
||||
DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions"
|
||||
DEEPSEEK_API_KEY = os.environ.get('DEEPSEEK_API_KEY', '')
|
||||
DB_PATH = os.environ.get('GAOKAO_DB', '/www/wwwroot/gaokao/gaokao_henan.db')
|
||||
STATIC_DIR = "/www/wwwroot/gaokao"
|
||||
|
||||
SESSIONS = {}
|
||||
|
||||
# ===== SYSTEM PROMPT =====
|
||||
SYSTEM_PROMPT = """你是一个专业、温暖的高考志愿填报助手,专门为河南省考生服务。
|
||||
|
||||
【重要说明】
|
||||
- 当前年份:2026年,2025年河南高考投档已完成,以下数据是最有价值的参考。
|
||||
- 科目改革:2025年起河南新高考采用「3+1+2」模式,物理/历史分开招生。
|
||||
- 2025年河南分数线:物理类特控535/本科427;历史类特控552/本科471;专科185。
|
||||
|
||||
【数据来源】
|
||||
你有真实的院校和专业录取数据(2022-2025年),包括:
|
||||
- 院校最低分 / 最低位次(按选科分组)
|
||||
- 专业录取分
|
||||
- 招生计划数
|
||||
- 一分一段表(2025年)
|
||||
当考生提供分数时,你应该结合这些真实数据给出推荐,而非凭空估算。
|
||||
|
||||
【推荐输出格式】(每组9项,缺一不可)
|
||||
1. 院校名称:(选科要求)| 分数 | 位次 | 批次线差
|
||||
2. 推荐理由:为什么适合考生(具体、个性化)
|
||||
3. 报考策略:志愿梯度建议(冲/稳/保的具体填法)
|
||||
4. 推荐专业:该院校的王牌专业及考生意向专业的录取情况说明
|
||||
5. 城市实况:校区所在城市的气候、交通、经济发展水平(1-2句)
|
||||
6. 食堂餐饮:食堂数量、菜系丰富度、消费水平(1-2句)
|
||||
7. 住宿条件:宿舍几人间、有无空调热水、翻新情况(1-2句)
|
||||
8. 就读体验:学风氛围、升学/就业资源、学术氛围(1-2句)
|
||||
9. 风险提示:该院校/专业需要特别注意的风险(招生批次、专业分流、选科限制等,1-2句)
|
||||
|
||||
【分组规则】
|
||||
- 冲:考生分数比学校最低分低0~15分(踩线,热档志愿)
|
||||
- 稳:考生分数比学校最低分高0~20分(志愿组合核心区)
|
||||
- 保:考生分数比学校最低分高20分以上(安全垫)
|
||||
概率参考:冲≈30%、稳≈65%、保≈90%
|
||||
|
||||
【注意事项】
|
||||
1. 优先参考2025年投档数据,2024年数据作为趋势参考。
|
||||
2. 选科要求必须匹配,否则该院校不适合该考生。
|
||||
3. 新高考「1+2」选科组合直接影响可报院校范围,务必确认选科是否满足。
|
||||
4. 对于临床医学等长学制专业,提醒考生关注学制年限。
|
||||
5. 对话风格:直接给出推荐,不要反问,不要列举选项,不要说"以下是参考"。
|
||||
"""
|
||||
|
||||
# ===== 学费辅助 =====
|
||||
def get_school_tuition(school_name, cur):
|
||||
"""查询学校的学费信息,优先从 tuition 列取,兜底用 tuition_data 模块"""
|
||||
# 优先从 DB 字段取
|
||||
row = cur.execute(
|
||||
"SELECT tuition FROM schools WHERE school_name=? AND tuition IS NOT NULL AND tuition!='' LIMIT 1",
|
||||
(school_name,)
|
||||
).fetchone()
|
||||
if row and row[0]:
|
||||
try:
|
||||
import json as _json
|
||||
return _json.loads(row[0])
|
||||
except Exception:
|
||||
pass
|
||||
# 兜底:用 tuition_data 模块(内嵌,不依赖外部文件)
|
||||
return _get_tuition_fallback(school_name)
|
||||
|
||||
def _get_tuition_fallback(school_name):
|
||||
"""内嵌版学费计算逻辑(与 tuition_data.py 保持一致)"""
|
||||
import json as _json
|
||||
sn = school_name or ""
|
||||
# 精确匹配表
|
||||
EXACT = {
|
||||
"郑州大学": ("普通类(理科)", 5000),
|
||||
"郑州大学国际学院": ("国际学院", 25000),
|
||||
"郑州大学(中外合作)": ("中外合作办学(普通专业)", 18000),
|
||||
"郑州大学医学院": ("医学类", 5500),
|
||||
"河南大学": ("普通类(文科)", 4400),
|
||||
"河南大学(中外合作)": ("中外合作办学(普通专业)", 18000),
|
||||
"河南师范大学": ("普通类(文科)", 4400),
|
||||
"河南师范大学(中外合作)": ("中外合作办学(普通专业)", 18000),
|
||||
"河南开封科技传媒学院": ("独立学院(普通专业)", 12000),
|
||||
"河南大学民生学院": ("独立学院(普通专业)", 12000),
|
||||
"河南师范大学新联学院": ("独立学院(普通专业)", 12000),
|
||||
"中原工学院信息商务学院": ("独立学院(普通专业)", 12000),
|
||||
"新乡医学院三全学院": ("独立学院(医学类)", 13000),
|
||||
"河南理工大学万方科技学院": ("独立学院(普通专业)", 12000),
|
||||
"郑州大学西亚斯国际学院": ("中外合作办学(普通专业)", 18000),
|
||||
"河南科技大学": ("普通类(理科)", 5000),
|
||||
"河南理工大学": ("普通类(理科)", 5000),
|
||||
"河南农业大学": ("农林类", 4000),
|
||||
"河南工业大学": ("普通类(理科)", 5000),
|
||||
"华北水利水电大学": ("普通类(理科)", 5000),
|
||||
"郑州轻工业大学": ("普通类(理科)", 5000),
|
||||
"中原工学院": ("普通类(理科)", 5000),
|
||||
"河南财经政法大学": ("普通类(文科)", 4400),
|
||||
"郑州航空工业管理学院": ("普通类(文科)", 4400),
|
||||
"河南中医药大学": ("医学类", 5500),
|
||||
"新乡医学院": ("医学类", 5500),
|
||||
"河南警察学院": ("普通类(文科)", 4400),
|
||||
"郑州师范学院": ("普通类(文科)", 4400),
|
||||
"洛阳师范学院": ("普通类(文科)", 4400),
|
||||
"信阳师范学院": ("普通类(文科)", 4400),
|
||||
"南阳师范学院": ("普通类(文科)", 4400),
|
||||
"商丘师范学院": ("普通类(文科)", 4400),
|
||||
"安阳师范学院": ("普通类(文科)", 4400),
|
||||
"周口师范学院": ("普通类(文科)", 4400),
|
||||
"许昌学院": ("普通类(文科)", 4400),
|
||||
"洛阳理工学院": ("普通类(理科)", 5000),
|
||||
"河南工程学院": ("普通类(理科)", 5000),
|
||||
"南阳理工学院": ("普通类(理科)", 5000),
|
||||
"河南城建学院": ("普通类(理科)", 5000),
|
||||
"平顶山学院": ("普通类(文科)", 4400),
|
||||
"河南牧业经济学院": ("农林类", 4000),
|
||||
"黄河科技学院": ("独立学院(普通专业)", 12000),
|
||||
"商丘学院": ("独立学院(普通专业)", 12000),
|
||||
"郑州科技学院": ("独立学院(普通专业)", 12000),
|
||||
"郑州升达经贸管理学院": ("独立学院(普通专业)", 12000),
|
||||
"新乡工程学院": ("独立学院(普通专业)", 12000),
|
||||
}
|
||||
if sn in EXACT:
|
||||
cat, amt = EXACT[sn]
|
||||
return {"category": cat, "annual": amt, "remark": f"{cat} | 河南省发改委标准", "source": "fallback"}
|
||||
# 关键字匹配
|
||||
kw_joint = ["中外合作", "国际学院", "合作办学", "香港", "台湾"]
|
||||
kw_private = ["独立学院", "民生学院", "信息商务学院", "应用技术"]
|
||||
kw_arts = ["艺术", "音乐", "美术", "设计", "舞蹈", "传媒", "戏剧", "影视"]
|
||||
kw_medical = ["医学", "药学", "护理", "临床", "口腔", "中医", "医科", "医大", "军医"]
|
||||
kw_agri = ["农业", "林业", "园艺", "畜牧", "兽医", "水产"]
|
||||
for kw in kw_joint:
|
||||
if kw in sn:
|
||||
if any(a in sn for a in kw_arts):
|
||||
return {"category": "中外合作办学(艺术/医学类)", "annual": 22000, "remark": "中外合作办学(艺术/医学类) | 河南省发改委标准", "source": "fallback"}
|
||||
return {"category": "中外合作办学(普通专业)", "annual": 18000, "remark": "中外合作办学 | 河南省发改委标准", "source": "fallback"}
|
||||
for kw in kw_private:
|
||||
if kw in sn:
|
||||
if any(a in sn for a in kw_arts):
|
||||
return {"category": "独立学院(艺术类)", "annual": 15000, "remark": "独立学院(艺术类) | 河南省发改委标准", "source": "fallback"}
|
||||
return {"category": "独立学院(普通专业)", "annual": 12000, "remark": "独立学院 | 河南省发改委标准", "source": "fallback"}
|
||||
for kw in kw_arts:
|
||||
if kw in sn:
|
||||
return {"category": "艺术类", "annual": 8000, "remark": "艺术类 | 河南省发改委标准", "source": "fallback"}
|
||||
for kw in kw_medical:
|
||||
if kw in sn:
|
||||
return {"category": "医学类", "annual": 5500, "remark": "医学类 | 河南省发改委标准", "source": "fallback"}
|
||||
for kw in kw_agri:
|
||||
if kw in sn:
|
||||
return {"category": "农林类", "annual": 4000, "remark": "农林类 | 河南省发改委标准", "source": "fallback"}
|
||||
if "师范" in sn:
|
||||
return {"category": "普通类(文科)", "annual": 4400, "remark": "师范类 | 河南省发改委标准", "source": "fallback"}
|
||||
if "体育" in sn:
|
||||
return {"category": "体育类", "annual": 5000, "remark": "体育类 | 河南省发改委标准", "source": "fallback"}
|
||||
# 高职/专科
|
||||
if any(k in sn for k in ["职业", "技术", "专科", "高职"]):
|
||||
return {"category": "高职(普通专业)", "annual": 4200, "remark": "高职 | 河南省发改委标准", "source": "fallback"}
|
||||
# 默认
|
||||
return {"category": "普通类(理科)", "annual": 5000, "remark": "公办本科 | 河南省发改委标准", "source": "fallback"}
|
||||
|
||||
|
||||
def get_db():
|
||||
return sqlite3.connect(DB_PATH)
|
||||
|
||||
def score_to_rank(conn, subject, score):
|
||||
cur = conn.execute(
|
||||
'SELECT rank FROM yiyi WHERE year=2025 AND subject=? AND score<=? ORDER BY score DESC LIMIT 1',
|
||||
(subject, score))
|
||||
r = cur.fetchone()
|
||||
return r[0] if r else None
|
||||
|
||||
def recommend(score, subject, batch='本科批', top_n=4):
|
||||
conn = get_db()
|
||||
student_rank = score_to_rank(conn, subject, score)
|
||||
if not student_rank:
|
||||
conn.close()
|
||||
return None
|
||||
|
||||
cur = conn.execute('''
|
||||
SELECT school_name, subject_req, min_score, min_rank, batch_diff
|
||||
FROM schools
|
||||
WHERE year=2025 AND subject=? AND batch=? AND min_rank IS NOT NULL AND min_score IS NOT NULL
|
||||
ORDER BY min_score DESC
|
||||
''', (subject, batch))
|
||||
|
||||
chong, wen, bao = [], [], []
|
||||
for row in cur.fetchall():
|
||||
sch_name, req, sch_score, sch_rank, diff = row
|
||||
score_gap = sch_score - score
|
||||
entry = {'学校': sch_name, '选科': req, '分数': sch_score, '位次': sch_rank, '线差': diff}
|
||||
if 0 <= score_gap <= 15:
|
||||
chong.append(entry)
|
||||
elif -20 <= score_gap < 0:
|
||||
wen.append(entry)
|
||||
elif score_gap < -20:
|
||||
bao.append(entry)
|
||||
|
||||
def dedup(items):
|
||||
seen = {}
|
||||
for it in items:
|
||||
key = (it['学校'], it['分数'])
|
||||
if key not in seen:
|
||||
seen[key] = it
|
||||
return list(seen.values())
|
||||
|
||||
chong, wen, bao = dedup(chong), dedup(wen), dedup(bao)
|
||||
chong.sort(key=lambda x: x['分数'], reverse=True)
|
||||
wen.sort(key=lambda x: x['分数'], reverse=True)
|
||||
bao.sort(key=lambda x: x['分数'])
|
||||
|
||||
conn.close()
|
||||
return {'student_rank': student_rank, 'student_score': score,
|
||||
'subject': subject, 'batch': batch,
|
||||
'chong': chong[:top_n], 'wen': wen[:top_n], 'bao': bao[:top_n]}
|
||||
|
||||
def format_recommend(r):
|
||||
if not r:
|
||||
return "(数据库暂无该分数段推荐数据,请结合往年经验分析)"
|
||||
parts = []
|
||||
parts.append(f"【数据库实时推荐】考生{r['student_score']}分(位次{r['student_rank']},{r['subject']}类)推荐如下:")
|
||||
for label, items in [('冲志愿(考生分数比学校最低分低0-15分,风险较高)', r['chong']),
|
||||
('稳志愿(考生分数比学校最低分高0-20分,稳妥之选)', r['wen']),
|
||||
('保志愿(考生分数比学校最低分高20+,安全垫充足)', r['bao'])]:
|
||||
parts.append(f"\n{label}")
|
||||
for it in items:
|
||||
parts.append(f" - {it['学校']}({it['选科']})| {it['分数']}分 | 位次{it['位次']} | 批次线差{it['线差']}")
|
||||
return '\n'.join(parts)
|
||||
|
||||
def call_deepseek(messages, timeout=90):
|
||||
data = json.dumps({
|
||||
"model": "deepseek-v4-flash",
|
||||
"messages": messages,
|
||||
"max_tokens": 2500,
|
||||
"temperature": 0.3,
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
DEEPSEEK_API_URL, data=data,
|
||||
headers={"Authorization": f"Bearer {DEEPSEEK_API_KEY}", "Content-Type": "application/json"},
|
||||
method="POST"
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
result = json.loads(resp.read().decode("utf-8"))
|
||||
return result["choices"][0]["message"]["content"]
|
||||
|
||||
def extract_score_subject(text):
|
||||
m = re.search(r'(\d{2,3})\s*[分]', text)
|
||||
score = int(m.group(1)) if m else None
|
||||
subject = '物理类' if any(k in text for k in ['物理', '理科', '物理类']) else \
|
||||
'历史类' if any(k in text for k in ['历史', '文科', '历史类']) else None
|
||||
return score, subject
|
||||
|
||||
def build_reply(user_message, history_msgs):
|
||||
score, subject = extract_score_subject(user_message)
|
||||
db_context = ""
|
||||
if score and subject:
|
||||
r = recommend(score, subject)
|
||||
if r:
|
||||
db_context = "\n\n" + format_recommend(r)
|
||||
prompt = SYSTEM_PROMPT + db_context
|
||||
messages = [{"role": "system", "content": prompt}]
|
||||
for h in history_msgs[-10:]:
|
||||
messages.append({"role": "user", "content": h.get("user", "")})
|
||||
if h.get("assistant"):
|
||||
messages.append({"role": "assistant", "content": h.get("assistant")})
|
||||
messages.append({"role": "user", "content": user_message})
|
||||
return call_deepseek(messages), bool(db_context)
|
||||
|
||||
# ===== 路由 =====
|
||||
|
||||
@app.route("/health")
|
||||
def health():
|
||||
return jsonify({"status": "ok", "time": datetime.now().isoformat()})
|
||||
|
||||
@app.route("/chat", methods=["GET", "POST"])
|
||||
@app.route("/ai-tool-chat", methods=["GET", "POST"])
|
||||
def chat():
|
||||
if request.method == "GET":
|
||||
return send_file(f"{STATIC_DIR}/ai-chat.html")
|
||||
body = request.get_json() or {}
|
||||
user_message = body.get("message", "")
|
||||
if not user_message:
|
||||
for m in body.get("messages", []):
|
||||
if isinstance(m, dict) and m.get("role") == "user":
|
||||
user_message = m.get("content", "")
|
||||
break
|
||||
history = body.get("history", [])
|
||||
try:
|
||||
reply, db_used = build_reply(user_message, history)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e), "reply": f"错误:{e}"}), 500
|
||||
return jsonify({"reply": reply, "db_used": db_used})
|
||||
|
||||
@app.route("/api/chat/start", methods=["POST"])
|
||||
def chat_start():
|
||||
sid = str(uuid.uuid4())
|
||||
SESSIONS[sid] = []
|
||||
welcome = ("您好!我是高考志愿填报 AI 助手 🎓\n\n"
|
||||
"请告诉我以下信息,我来帮你推荐院校:\n\n"
|
||||
"1. **高考分数**(如:600分)\n"
|
||||
"2. **科类**(物理类 / 历史类)\n"
|
||||
"3. **想学的专业**(如:计算机、医学、法律等)\n\n"
|
||||
"您也可以简单说:*物理类 600 分想学计算机*,我会直接给出推荐方案。")
|
||||
SESSIONS[sid].append({"role": "assistant", "content": welcome})
|
||||
return jsonify({"session_id": sid, "message": welcome})
|
||||
|
||||
@app.route("/api/chat/respond", methods=["POST"])
|
||||
def chat_respond():
|
||||
body = request.get_json() or {}
|
||||
sid = body.get("session_id", "")
|
||||
user_message = body.get("message", "")
|
||||
if sid not in SESSIONS:
|
||||
return jsonify({"error": "会话已过期,请刷新页面重新开始。"}), 400
|
||||
history = SESSIONS[sid]
|
||||
history.append({"role": "user", "content": user_message})
|
||||
try:
|
||||
reply, db_used = build_reply(user_message, history)
|
||||
except Exception as e:
|
||||
SESSIONS[sid].append({"role": "assistant", "content": f"抱歉,出了点问题:{e}"})
|
||||
return jsonify({"message": f"抱歉,出了点问题:{e}", "done": False}), 200
|
||||
history.append({"role": "assistant", "content": reply})
|
||||
return jsonify({"message": reply, "done": False, "db_used": db_used})
|
||||
|
||||
@app.route("/api/prob", methods=["POST"])
|
||||
def prob_calc():
|
||||
body = request.get_json() or {}
|
||||
score = int(body.get('score', 0))
|
||||
subject = body.get('subject', '物理类')
|
||||
major = body.get('major', '不限')
|
||||
region = body.get('region', '不限')
|
||||
|
||||
conn = get_db()
|
||||
student_rank = score_to_rank(conn, subject, score)
|
||||
if not student_rank:
|
||||
conn.close()
|
||||
return jsonify({'error': '该分数超出数据库范围'}), 400
|
||||
|
||||
# 专业/地区过滤:先从 majors 表查出符合条件的学校列表
|
||||
allowed_schools = None
|
||||
if major != '不限' or region != '不限':
|
||||
# 城市→省份映射(majors表只有省份字段)
|
||||
city_to_province = {'郑州':'河南','开封':'河南','洛阳':'河南','新乡':'河南',
|
||||
'南阳':'河南','安阳':'河南','焦作':'河南','平顶山':'河南',
|
||||
'信阳':'河南','周口':'河南','驻马店':'河南','许昌':'河南',
|
||||
'漯河':'河南','三门峡':'河南','商丘':'河南','鹤壁':'河南',
|
||||
'济源':'河南','省内':'河南','河南':'河南'}
|
||||
prov = city_to_province.get(region, region) if region != '不限' else None
|
||||
filter_clauses = []
|
||||
params = []
|
||||
if major != '不限':
|
||||
kw_map = {'计算机':'计算机','电子信息':'电子信息','临床医学':'临床医学',
|
||||
'经济金融':'经济','法学':'法学','师范':'师范'}
|
||||
kw = kw_map.get(major, major)
|
||||
filter_clauses.append("major LIKE ?")
|
||||
params.append('%' + kw + '%')
|
||||
if prov and prov != '不限':
|
||||
filter_clauses.append("location = ?")
|
||||
params.append(prov)
|
||||
if filter_clauses:
|
||||
cur2 = conn.execute(
|
||||
"SELECT DISTINCT school_name FROM majors WHERE year=2025 AND subject=? AND " + " AND ".join(filter_clauses),
|
||||
[subject] + params)
|
||||
allowed_schools = set(r[0] for r in cur2.fetchall())
|
||||
|
||||
cur = conn.execute(
|
||||
"SELECT school_name, subject_req, min_score, min_rank, batch_diff "
|
||||
"FROM schools WHERE year=2025 AND subject=? AND batch='本科批' "
|
||||
"AND min_rank IS NOT NULL AND min_score IS NOT NULL "
|
||||
"ORDER BY min_score DESC",
|
||||
(subject,))
|
||||
rows = cur.fetchall()
|
||||
conn.close()
|
||||
|
||||
chong, wen, bao = [], [], []
|
||||
for row in rows:
|
||||
sch_name, req, sch_score, sch_rank, diff = row
|
||||
if allowed_schools is not None and sch_name not in allowed_schools:
|
||||
continue
|
||||
gap = sch_score - score
|
||||
entry = {'school': sch_name, 'req': req, 'score': sch_score,
|
||||
'rank': sch_rank, 'diff': diff}
|
||||
if 0 <= gap <= 15:
|
||||
chong.append(entry)
|
||||
elif -20 <= gap < 0:
|
||||
wen.append(entry)
|
||||
elif gap < -20:
|
||||
bao.append(entry)
|
||||
|
||||
def top(items, n=6, reverse=True):
|
||||
return sorted(items, key=lambda x: x['score'], reverse=reverse)[:n]
|
||||
|
||||
def with_prob(items, score):
|
||||
result = []
|
||||
for it in items:
|
||||
score_gap = it['score'] - score # 学校最低分 - 考生分数,正=冲,负=保
|
||||
# 概率基于分差:直接反映风险,摆脱 rank 换算失真
|
||||
if score_gap >= 15:
|
||||
prob = 15 + min(15, score_gap - 15) * 1.0 # 15-30%
|
||||
elif score_gap >= 0:
|
||||
prob = 15 + score_gap * 1.0 # 冲区 15-30%
|
||||
elif score_gap >= -20:
|
||||
prob = 30 + (-score_gap) * 1.75 # 稳区 30-65%
|
||||
else:
|
||||
prob = 65 + min(25, -score_gap - 20) * 0.5 # 保区 65-90%
|
||||
prob = min(95, max(5, prob))
|
||||
|
||||
tui = _get_tuition_fallback(it['school'])
|
||||
|
||||
result.append({
|
||||
**it,
|
||||
'prob': round(prob, 1),
|
||||
'tuition_annual': tui.get('annual', 5000),
|
||||
'tuition_category': tui.get('category', '普通类(理科)'),
|
||||
'tuition_source': tui.get('source', 'fallback'),
|
||||
})
|
||||
return result
|
||||
|
||||
return jsonify({
|
||||
'score': score, 'subject': subject,
|
||||
'rank': student_rank,
|
||||
'major_filter': major if major != '不限' else None,
|
||||
'region_filter': region if region != '不限' else None,
|
||||
'chong': with_prob(top(chong), score),
|
||||
'wen': with_prob(top(wen), score),
|
||||
'bao': with_prob(top(bao), score),
|
||||
})
|
||||
|
||||
@app.route("/api/school/search", methods=["GET"])
|
||||
def school_search():
|
||||
q = request.args.get("q", "").strip()
|
||||
subject = request.args.get("subject", "物理类")
|
||||
if not q:
|
||||
return jsonify({"schools": []})
|
||||
cur = get_db().cursor()
|
||||
rows = cur.execute("""
|
||||
SELECT school_name, subject, min_score, min_rank, plan_count, plan_type, subject_req, subject_group
|
||||
FROM schools
|
||||
WHERE school_name LIKE ? AND subject = ?
|
||||
ORDER BY min_score DESC LIMIT 20
|
||||
""", ("%" + q + "%", subject)).fetchall()
|
||||
schools = []
|
||||
for r in rows:
|
||||
tui = get_school_tuition(r[0], cur)
|
||||
schools.append({
|
||||
"school": r[0],
|
||||
"subject": r[1],
|
||||
"score": r[2],
|
||||
"rank": r[3] or 0,
|
||||
"num": r[4],
|
||||
"type": r[5] or "普通类",
|
||||
"req": r[6] or "",
|
||||
"group": r[7] or "",
|
||||
"tuition_annual": tui.get("annual", 5000),
|
||||
"tuition_category": tui.get("category", "普通类(理科)"),
|
||||
})
|
||||
return jsonify({"schools": schools})
|
||||
|
||||
@app.route("/api/major/search", methods=["GET"])
|
||||
def major_search():
|
||||
school = request.args.get("school", "").strip()
|
||||
major = request.args.get("major", "").strip()
|
||||
subject = request.args.get("subject", "")
|
||||
year = request.args.get("year", "2025")
|
||||
batch = request.args.get("batch", "")
|
||||
limit = min(int(request.args.get("limit", 50)), 100)
|
||||
offset = int(request.args.get("offset", 0))
|
||||
|
||||
if not school and not major:
|
||||
return jsonify({"majors": []})
|
||||
|
||||
cur = get_db().cursor()
|
||||
conditions = []
|
||||
params = []
|
||||
|
||||
if school:
|
||||
conditions.append("school_name LIKE ?")
|
||||
params.append("%" + school + "%")
|
||||
if major:
|
||||
conditions.append("major LIKE ?")
|
||||
params.append("%" + major + "%")
|
||||
if subject:
|
||||
conditions.append("subject = ?")
|
||||
params.append(subject)
|
||||
if year:
|
||||
conditions.append("year = ?")
|
||||
params.append(year)
|
||||
if batch:
|
||||
conditions.append("batch = ?")
|
||||
params.append(batch)
|
||||
|
||||
where = " AND ".join(conditions) if conditions else "1=1"
|
||||
params.extend([limit, offset])
|
||||
|
||||
rows = cur.execute(f"""
|
||||
SELECT school_name, major, major_code, subject_group, subject_req,
|
||||
plan_count, min_score, min_rank, location, school_type,
|
||||
is_985, is_211, subject, batch, year
|
||||
FROM majors
|
||||
WHERE {where}
|
||||
ORDER BY min_score DESC
|
||||
LIMIT ? OFFSET ?
|
||||
""", params).fetchall()
|
||||
|
||||
majors = []
|
||||
for r in rows:
|
||||
majors.append({
|
||||
"school": r[0],
|
||||
"major": r[1],
|
||||
"major_code": r[2] or "",
|
||||
"group": r[3] or "",
|
||||
"req": r[4] or "",
|
||||
"plan": r[5] or 0,
|
||||
"score": r[6] or 0,
|
||||
"rank": r[7] or 0,
|
||||
"location": r[8] or "",
|
||||
"school_type": r[9] or "",
|
||||
"is_985": bool(r[10]) if r[10] is not None else False,
|
||||
"is_211": bool(r[11]) if r[11] is not None else False,
|
||||
"subject": r[12],
|
||||
"batch": r[13],
|
||||
"year": r[14]
|
||||
})
|
||||
return jsonify({"majors": majors, "total": len(majors)})
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8080, threaded=True)
|
||||
+923
@@ -0,0 +1,923 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>河南高考志愿助手 - 2026</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.6; }
|
||||
.container { max-width: 1100px; margin: 0 auto; padding: 0 20px; }
|
||||
.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 { 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; }
|
||||
.nav-links a.highlight-new { color: #a5b4fc; font-weight: 600; }
|
||||
.section { padding: 60px 0; border-bottom: 1px solid rgba(255,255,255,0.05); }
|
||||
|
||||
/* 最近使用 */
|
||||
.recent-bar { background: rgba(102,126,234,0.06); border-bottom: 1px solid rgba(102,126,234,0.12); padding: 10px 0; font-size: 0.82rem; }
|
||||
.recent-bar .container { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||
.recent-bar .recent-label { color: #667eea; font-weight: 600; white-space: nowrap; }
|
||||
.recent-bar .recent-item { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1); border-radius: 20px; padding: 3px 12px; color: #aaa; text-decoration: none; transition: all 0.2s; white-space: nowrap; }
|
||||
.recent-bar .recent-item:hover { background: rgba(102,126,234,0.15); border-color: rgba(102,126,234,0.3); color: #fff; }
|
||||
.recent-bar .recent-empty { color: #555; }
|
||||
|
||||
/* AI Hero 首屏 */
|
||||
.ai-hero { background: linear-gradient(135deg, #0f0f1a 0%, #12102a 50%, #0d1020 100%); padding: 52px 0 48px; border-bottom: 1px solid rgba(102,126,234,0.15); }
|
||||
.ai-hero-inner { max-width: 1100px; margin: 0 auto; padding: 0 20px; display: grid; grid-template-columns: 1fr 1fr; gap: 40px; align-items: center; }
|
||||
.ai-hero-l h1 { font-size: 2rem; color: #fff; margin-bottom: 12px; line-height: 1.3; }
|
||||
.ai-hero-l h1 em { font-style: normal; color: #667eea; }
|
||||
.ai-hero-l p { color: #888; font-size: 1rem; margin-bottom: 22px; line-height: 1.6; }
|
||||
.ai-hero-btn { display: inline-flex; align-items: center; gap: 8px; background: linear-gradient(135deg, #667eea, #764ba2); color: #fff; text-decoration: none; padding: 13px 30px; border-radius: 30px; font-weight: 600; font-size: 1rem; transition: opacity 0.2s; }
|
||||
.ai-hero-btn:hover { opacity: 0.88; }
|
||||
.ai-hero-stats { display: flex; gap: 24px; margin-top: 24px; }
|
||||
.ai-hero-stat { text-align: center; }
|
||||
.ai-hero-stat .num { font-size: 1.4rem; font-weight: 800; color: #667eea; }
|
||||
.ai-hero-stat .desc { font-size: 0.75rem; color: #666; margin-top: 2px; }
|
||||
.ai-demo { background: rgba(255,255,255,0.04); border: 1px solid rgba(102,126,234,0.2); border-radius: 16px; padding: 22px; }
|
||||
.ai-demo-title { color: #667eea; font-size: 0.78rem; font-weight: 600; margin-bottom: 12px; display: flex; align-items: center; gap: 6px; }
|
||||
.ai-msg { margin-bottom: 10px; }
|
||||
.ai-msg-r { background: rgba(102,126,234,0.12); border-radius: 12px 12px 12px 4px; padding: 10px 14px; font-size: 0.85rem; color: #ccc; line-height: 1.5; }
|
||||
.ai-msg-u { background: rgba(255,255,255,0.06); border-radius: 12px 12px 4px 12px; padding: 8px 12px; font-size: 0.83rem; color: #999; margin-top: 8px; }
|
||||
.ai-msg-u::before { content: '👤 '; }
|
||||
.ai-msg-r::before { content: '🤖 '; }
|
||||
.ai-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
||||
.ai-tag { background: rgba(102,126,234,0.1); border: 1px solid rgba(102,126,234,0.2); color: #667eea; font-size: 0.7rem; padding: 2px 8px; border-radius: 6px; }
|
||||
.ai-tag::before { content: '✓ '; }
|
||||
.section-title { text-align: center; margin-bottom: 35px; }
|
||||
.section-title h2 { font-size: 1.65rem; color: #fff; margin-bottom: 8px; }
|
||||
.section-title p { color: #888; }
|
||||
|
||||
/* 高考日历 */
|
||||
.calendar-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; margin-top: 25px; }
|
||||
.calendar-item { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 18px; border-left: 4px solid #667eea; }
|
||||
.calendar-item.done { border-left-color: #4ade80; opacity: 0.7; }
|
||||
.calendar-item.upcoming { border-left-color: #fbbf24; }
|
||||
.calendar-item .month { color: #667eea; font-size: 0.78rem; font-weight: 600; margin-bottom: 5px; }
|
||||
.calendar-item.done .month { color: #4ade80; }
|
||||
.calendar-item.upcoming .month { color: #fbbf24; }
|
||||
.calendar-item h4 { color: #e0e0e0; font-size: 0.95rem; margin-bottom: 5px; }
|
||||
.calendar-item p { color: #888; font-size: 0.82rem; }
|
||||
.calendar-item .status { display: inline-block; margin-top: 8px; font-size: 0.72rem; padding: 2px 8px; border-radius: 10px; }
|
||||
.calendar-item.done .status { background: rgba(74,222,128,0.15); color: #4ade80; }
|
||||
.calendar-item.upcoming .status { background: rgba(251,191,36,0.15); color: #fbbf24; }
|
||||
.calendar-item .status.neutral { background: rgba(255,255,255,0.06); color: #888; }
|
||||
|
||||
/* 工具导航卡片 */
|
||||
.tool-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; margin-top: 25px; }
|
||||
.province-grid { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 25px; }
|
||||
.province-tag { display: inline-flex; align-items: center; justify-content: center; padding: 8px 16px; background: rgba(255,255,255,0.07); border: 1px solid rgba(255,255,255,0.12); border-radius: 8px; color: #aaa; text-decoration: none; font-size: 0.88rem; transition: all 0.2s; }
|
||||
.province-tag:hover { background: rgba(255,255,255,0.12); color: #fff; border-color: rgba(255,255,255,0.25); }
|
||||
.province-tag.highlight { background: rgba(102,126,234,0.2); border-color: rgba(102,126,234,0.5); color: #a5b4fc; }
|
||||
.tool-card { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 14px; padding: 22px 18px; transition: all 0.3s; }
|
||||
.tool-card:hover { border-color: rgba(102,126,234,0.4); transform: translateY(-2px); }
|
||||
.tool-card .tool-icon { font-size: 1.8rem; margin-bottom: 10px; }
|
||||
.tool-card h4 { color: #fff; font-size: 0.95rem; margin-bottom: 6px; }
|
||||
.tool-card p { color: #777; font-size: 0.82rem; margin-bottom: 12px; line-height: 1.5; }
|
||||
.tool-card .tool-link { color: #667eea; text-decoration: none; font-size: 0.85rem; }
|
||||
.tool-card .tool-link:hover { text-decoration: underline; }
|
||||
.tool-card .tag { display: inline-block; font-size: 0.7rem; padding: 2px 7px; border-radius: 6px; margin-bottom: 8px; }
|
||||
.tool-card .tag.official { background: rgba(239,68,68,0.15); color: #ef4444; }
|
||||
.tool-card .tag.ai-tag { background: rgba(102,126,234,0.2); color: #667eea; border: 1px solid rgba(102,126,234,0.3); }
|
||||
.tool-card .tag.hot { background: rgba(251,191,36,0.15); color: #fbbf24; }
|
||||
|
||||
/* 历年分数线 */
|
||||
.score-section { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; }
|
||||
.score-col h4 { font-size: 0.88rem; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid rgba(255,255,255,0.06); }
|
||||
.score-col h4.sci { color: #667eea; }
|
||||
.score-col h4.art { color: #f472b6; }
|
||||
.score-table { width: 100%; border-collapse: collapse; }
|
||||
.score-table th { color: #666; font-size: 0.78rem; font-weight: 500; padding: 7px 10px; border-bottom: 1px solid rgba(255,255,255,0.06); text-align: left; }
|
||||
.score-table td { color: #bbb; font-size: 0.85rem; padding: 7px 10px; border-bottom: 1px solid rgba(255,255,255,0.04); }
|
||||
.score-table tr:hover td { background: rgba(255,255,255,0.02); }
|
||||
.score-table .sname { color: #ddd; font-weight: 500; }
|
||||
|
||||
/* 批次线 */
|
||||
.batch-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-top: 20px; }
|
||||
.batch-item { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 22px 14px; text-align: center; }
|
||||
.batch-item.理科 { border-color: rgba(102,126,234,0.25); }
|
||||
.batch-item.文科 { border-color: rgba(244,114,182,0.25); }
|
||||
.batch-item .批次 { color: #666; font-size: 0.8rem; margin-bottom: 8px; }
|
||||
.batch-item.理科 .批次 { color: #667eea; }
|
||||
.batch-item.文科 .批次 { color: #f472b6; }
|
||||
.batch-item .分数线 { font-size: 2rem; font-weight: 800; }
|
||||
.batch-item.理科 .分数线 { color: #667eea; }
|
||||
.batch-item.文科 .分数线 { color: #f472b6; }
|
||||
.batch-item .位次 { color: #666; font-size: 0.78rem; margin-top: 6px; }
|
||||
.note-box { background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.07); border-radius: 14px; padding: 22px; margin-top: 20px; }
|
||||
.note-box p { color: #888; font-size: 0.85rem; line-height: 1.8; }
|
||||
.note-box strong { color: #aaa; }
|
||||
|
||||
/* 概率计算器 */
|
||||
.calc-box { background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.08); border-radius: 20px; padding: 35px; margin-top: 25px; }
|
||||
.calc-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; max-width: 860px; margin: 0 auto; }
|
||||
.form-group { display: flex; flex-direction: column; gap: 6px; }
|
||||
.form-group label { color: #888; font-size: 0.82rem; }
|
||||
.form-group input, .form-group select { padding: 12px 13px; border: 1px solid rgba(255,255,255,0.1); border-radius: 10px; background: rgba(255,255,255,0.05); color: #fff; font-size: 0.92rem; outline: none; }
|
||||
.form-group input:focus, .form-group select:focus { border-color: #667eea; }
|
||||
.form-group select option { background: #1a1a2e; }
|
||||
.calc-btn { display: block; width: 100%; max-width: 280px; margin: 22px auto 0; background: linear-gradient(135deg, #667eea, #764ba2); border: none; color: #fff; padding: 14px; border-radius: 30px; cursor: pointer; font-size: 0.95rem; font-weight: 600; }
|
||||
.calc-btn:hover { opacity: 0.9; }
|
||||
.prob-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 22px; }
|
||||
.prob-item { background: rgba(255,255,255,0.04); border-radius: 12px; padding: 16px; text-align: center; }
|
||||
.prob-item .label { color: #888; font-size: 0.8rem; margin-bottom: 6px; }
|
||||
.prob-item .value { font-size: 1.5rem; font-weight: 700; }
|
||||
.prob-item .value.hi { color: #4ade80; }
|
||||
.prob-item .value.mid { color: #fbbf24; }
|
||||
.prob-item .value.lo { color: #f87171; }
|
||||
.prob-item .note { color: #666; font-size: 0.75rem; margin-top: 4px; }
|
||||
.school-result { margin-top: 22px; }
|
||||
.school-result h4 { color: #fff; margin-bottom: 12px; font-size: 1rem; }
|
||||
.school-row { display: flex; align-items: center; gap: 10px; padding: 10px 14px; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); border-radius: 10px; margin-bottom: 8px; }
|
||||
.school-row .pl { font-size: 0.72rem; padding: 2px 8px; border-radius: 6px; font-weight: 600; }
|
||||
.school-row .pl.冲 { background: rgba(251,191,36,0.15); color: #fbbf24; }
|
||||
.school-row .pl.稳 { background: rgba(34,197,94,0.15); color: #4ade80; }
|
||||
.school-row .pl.保 { background: rgba(59,130,246,0.15); color: #60a5fa; }
|
||||
.school-row .sname { color: #e0e0e0; font-size: 0.88rem; flex: 1; }
|
||||
.school-row .smeta { color: #777; font-size: 0.8rem; }
|
||||
|
||||
/* 专项计划 */
|
||||
.special-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; margin-top: 25px; }
|
||||
.special-card { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 14px; padding: 24px; }
|
||||
.special-card h4 { color: #fff; font-size: 1rem; margin-bottom: 10px; display: flex; align-items: center; gap: 8px; }
|
||||
.special-card .tag { font-size: 0.7rem; padding: 2px 8px; border-radius: 20px; font-weight: normal; }
|
||||
.special-card .tag.guo { background: rgba(239,68,68,0.15); color: #ef4444; }
|
||||
.special-card .tag.xx { background: rgba(34,197,94,0.15); color: #22c55e; }
|
||||
.special-card .tag.di { background: rgba(59,130,246,0.15); color: #3b82f6; }
|
||||
.special-card p { color: #888; font-size: 0.85rem; margin-bottom: 14px; line-height: 1.7; }
|
||||
.special-card ul { list-style: none; }
|
||||
.special-card ul li { color: #aaa; font-size: 0.85rem; padding: 5px 0; border-bottom: 1px solid rgba(255,255,255,0.04); }
|
||||
.special-card ul li:last-child { border-bottom: none; }
|
||||
.special-card ul li::before { content: '✓ '; color: #667eea; }
|
||||
|
||||
/* 平行志愿规则 */
|
||||
.rule-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px; margin-top: 25px; }
|
||||
.rule-card { background: rgba(102,126,234,0.06); border: 1px solid rgba(102,126,234,0.18); border-radius: 12px; padding: 20px; }
|
||||
.rule-card h4 { color: #667eea; margin-bottom: 10px; font-size: 0.95rem; }
|
||||
.rule-card p { color: #bbb; font-size: 0.85rem; line-height: 1.7; }
|
||||
.rule-tip { background: rgba(251,191,36,0.06); border: 1px solid rgba(251,191,36,0.2); border-radius: 12px; padding: 16px 20px; margin-top: 20px; }
|
||||
.rule-tip h5 { color: #fbbf24; margin-bottom: 8px; font-size: 0.88rem; }
|
||||
.rule-tip p { color: #ccc; font-size: 0.85rem; }
|
||||
|
||||
/* FAQ */
|
||||
.faq-list { margin-top: 25px; }
|
||||
.faq-item { border: 1px solid rgba(255,255,255,0.07); border-radius: 12px; margin-bottom: 10px; overflow: hidden; }
|
||||
.faq-q { background: rgba(255,255,255,0.03); padding: 16px 20px; color: #e0e0e0; font-size: 0.92rem; cursor: pointer; display: flex; justify-content: space-between; align-items: center; font-weight: 500; }
|
||||
.faq-q:hover { background: rgba(255,255,255,0.05); }
|
||||
.faq-q::after { content: '+'; color: #667eea; font-size: 1.2rem; }
|
||||
.faq-item.open .faq-q::after { content: '−'; }
|
||||
.faq-a { display: none; padding: 0 20px 16px; color: #999; font-size: 0.88rem; line-height: 1.7; }
|
||||
.faq-item.open .faq-a { display: block; }
|
||||
|
||||
/* AI Banner (工具区里) */
|
||||
.ai-banner { background: linear-gradient(135deg, rgba(102,126,234,0.12), rgba(118,75,162,0.08)); border: 1px solid rgba(102,126,234,0.25); border-radius: 14px; padding: 28px; margin-top: 20px; }
|
||||
.ai-banner h3 { color: #fff; font-size: 1.1rem; margin-bottom: 6px; }
|
||||
.ai-banner p { color: #999; font-size: 0.85rem; margin-bottom: 14px; }
|
||||
.ai-banner a { display: inline-flex; align-items: center; gap: 6px; background: linear-gradient(135deg, #667eea, #764ba2); color: #fff; text-decoration: none; padding: 10px 24px; border-radius: 24px; font-size: 0.9rem; font-weight: 600; }
|
||||
.ai-banner a:hover { opacity: 0.9; }
|
||||
|
||||
.footer { text-align: center; padding: 28px 20px; border-top: 1px solid rgba(255,255,255,0.07); color: #555; font-size: 0.82rem; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header-inner { flex-direction: column; align-items: flex-start; gap: 8px; }
|
||||
.nav-links { gap: 12px; }
|
||||
.batch-row { grid-template-columns: 1fr 1fr; }
|
||||
.score-section { grid-template-columns: 1fr; }
|
||||
.prob-grid { grid-template-columns: 1fr; }
|
||||
.calc-box { padding: 22px 16px; }
|
||||
.ai-hero-inner { grid-template-columns: 1fr; gap: 24px; }
|
||||
.ai-hero-l h1 { font-size: 1.5rem; }
|
||||
.ai-hero-stats { gap: 16px; }
|
||||
.ai-hero-stat .num { font-size: 1.2rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="header">
|
||||
<div class="container 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="/ai-tool.html">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="recent-bar" id="recentBar">
|
||||
<div class="container">
|
||||
<span class="recent-label">📌 最近使用</span>
|
||||
<span id="recentItems"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Hero 首屏 -->
|
||||
<div class="ai-hero">
|
||||
<div class="ai-hero-inner">
|
||||
<div class="ai-hero-l">
|
||||
<h1>你的分数,<br>能上<em>哪所大学</em>?</h1>
|
||||
<p>输入分数和意向,AI 智能推荐冲/稳/保院校,<br>同步提供城市、食宿、就读体验等真实背景信息</p>
|
||||
<a href="/ai-chat" target="_blank" class="ai-hero-btn">🤖 立即体验 AI 推荐</a>
|
||||
<div class="ai-hero-stats">
|
||||
<div class="ai-hero-stat"><div class="num">2329</div><div class="desc">全国高校数据</div></div>
|
||||
<div class="ai-hero-stat"><div class="num">4年</div><div class="desc">历年分数线</div></div>
|
||||
<div class="ai-hero-stat"><div class="num">2025</div><div class="desc">新高考数据</div></div>
|
||||
<div class="ai-hero-stat"><div class="num">9维</div><div class="desc">院校信息评估</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ai-demo">
|
||||
<div class="ai-demo-title">🎯 AI 对话示例 · 609分河南理科</div>
|
||||
<div class="ai-msg">
|
||||
<div class="ai-msg-u">河南理科609分,排名16524,先推荐</div>
|
||||
</div>
|
||||
<div class="ai-msg">
|
||||
<div class="ai-msg-r">
|
||||
根据你的分数和排名,以下是定制推荐:<br><br>
|
||||
<strong style="color:#fbbf24">🏃 冲(30-50%)</strong>:吉林大学(985)<br>
|
||||
城市实况:长春,新一线城市,地铁1号线覆盖主要区域<br>
|
||||
食堂餐饮:均价10-13元/顿,窗口数15+,东北菜为主<br>
|
||||
住宿:4人间,年收费800-1200元,空调/热水器齐全<br><br>
|
||||
<strong style="color:#4ade80">✅ 稳(60-80%)</strong>:郑州大学(211)<br>
|
||||
城市实况:郑州,河南省会,地铁线路发达<br>
|
||||
食堂餐饮:均价11-14元/顿,窗口数30+,品种丰富<br>
|
||||
住宿:4-6人间,年收费1000-1500元,有空调暖气<br><br>
|
||||
<div class="ai-tags">
|
||||
<span class="ai-tag">院校名称</span>
|
||||
<span class="ai-tag">推荐理由</span>
|
||||
<span class="ai-tag">报考策略</span>
|
||||
<span class="ai-tag">推荐专业</span>
|
||||
<span class="ai-tag">城市实况</span>
|
||||
<span class="ai-tag">食堂餐饮</span>
|
||||
<span class="ai-tag">住宿条件</span>
|
||||
<span class="ai-tag">就读体验</span>
|
||||
<span class="ai-tag">风险提示</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 1. 高考日历 -->
|
||||
<section class="section" id="calendar">
|
||||
<div class="container">
|
||||
<div class="section-title">
|
||||
<h2>📅 2026年高考日历</h2>
|
||||
<p>关键时间节点,建议收藏</p>
|
||||
</div>
|
||||
<div class="calendar-grid">
|
||||
<div class="calendar-item done">
|
||||
<div class="month">6月7-8日</div>
|
||||
<h4>全国统一高考</h4>
|
||||
<p>语文、数学、综合、外语四门考试(已完成)</p>
|
||||
<span class="status">已完成</span>
|
||||
</div>
|
||||
<div class="calendar-item">
|
||||
<div class="month">6月25日</div>
|
||||
<h4>成绩公布</h4>
|
||||
<p>河南高考成绩及各批次控制分数线公布</p>
|
||||
<span class="status neutral">待通知</span>
|
||||
</div>
|
||||
<div class="calendar-item">
|
||||
<div class="month">6月26日起</div>
|
||||
<h4>志愿填报</h4>
|
||||
<p>提前批、国家专项:6月26-28日<br>本科批:6月30日-7月4日</p>
|
||||
<span class="status neutral">待通知</span>
|
||||
</div>
|
||||
<div class="calendar-item">
|
||||
<div class="month">6月26日</div>
|
||||
<h4>军队/司法类院校面试</h4>
|
||||
<p>报考军队院校考生面试体检</p>
|
||||
<span class="status neutral">待通知</span>
|
||||
</div>
|
||||
<div class="calendar-item">
|
||||
<div class="month">7月中旬</div>
|
||||
<h4>本科批录取</h4>
|
||||
<p>普通类本科批投档录取</p>
|
||||
<span class="status neutral">待通知</span>
|
||||
</div>
|
||||
<div class="calendar-item">
|
||||
<div class="month">8月上旬</div>
|
||||
<h4>专科志愿填报</h4>
|
||||
<p>专科批次志愿填报</p>
|
||||
<span class="status neutral">待通知</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. 官方查询渠道 -->
|
||||
<section class="section" id="tools">
|
||||
<div class="container">
|
||||
<div class="section-title">
|
||||
<h2>🔗 官方查询渠道</h2>
|
||||
<p>志愿填报唯一官方入口,谨防假冒</p>
|
||||
</div>
|
||||
<div class="tool-grid">
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">🔗</div>
|
||||
<span class="tag official">官方入口</span>
|
||||
<h4>河南省志愿填报系统</h4>
|
||||
<p>河南省教育考试院官方志愿填报唯一入口,志愿填报在此系统完成</p>
|
||||
<a href="https://pzwb.haeea.cn" target="_blank" class="tool-link">访问系统 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">📊</div>
|
||||
<span class="tag official">官方</span>
|
||||
<h4>河南省教育考试院</h4>
|
||||
<p>成绩公布、分数线、政策文件、招生计划官方发布渠道</p>
|
||||
<a href="https://www.haeea.cn" target="_blank" class="tool-link">访问官网 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">📋</div>
|
||||
<span class="tag ai-tag">教育部</span>
|
||||
<h4>阳光高考信息平台</h4>
|
||||
<p>教育部官方招生信息平台,查询各高校招生章程、录取规则</p>
|
||||
<a href="https://gaokao.chsi.com.cn" target="_blank" class="tool-link">访问 chsi ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">🔍</div>
|
||||
<span class="tag">必备</span>
|
||||
<h4>一分一段表</h4>
|
||||
<p>各分数段考生人数统计,用于精确计算全省排名</p>
|
||||
<a href="https://gaokao.chsi.com.cn/gkxx/ss/202506/20250623/2293390396.html" target="_blank" class="tool-link">查询位次 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">🏫</div>
|
||||
<h4>高校招生章程</h4>
|
||||
<p>各高校招生办联系方式、收费标准、单科要求、体检要求</p>
|
||||
<a href="https://gaokao.chsi.com.cn/zsgs/zhangcheng/listVerifedZszc--method-index,lb-1.dhtml" target="_blank" class="tool-link">查询章程 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">🏫</div>
|
||||
<h4>院校库</h4>
|
||||
<p>按地区、批次、选科筛选高校,查看历年录取数据</p>
|
||||
<a href="https://gaokao.chsi.com.cn/sch/search--ss-on,option-qg,searchType-1,start-0.dhtml" target="_blank" class="tool-link">查询院校 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">⭐</div>
|
||||
<h4>专业推荐</h4>
|
||||
<p>基于就业方向、考研方向、考公需求的智能专业推荐</p>
|
||||
<a href="https://gaokao.chsi.com.cn/zyk/pub/zytj/recommendTop" target="_blank" class="tool-link">专业推荐 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">📖</div>
|
||||
<h4>专业知识库</h4>
|
||||
<p>专业解读、就业前景、报考要求一览</p>
|
||||
<a href="https://gaokao.chsi.com.cn/zyk/zybk/" target="_blank" class="tool-link">知识库 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">🎯</div>
|
||||
<span class="tag ai-tag">AI工具</span>
|
||||
<h4>网易有道升学规划</h4>
|
||||
<p>AI辅助志愿规划,综合分数、位次、偏好智能推荐</p>
|
||||
<a href="https://ydzy.ydshengxue.com/" target="_blank" class="tool-link">立即使用 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">🎓</div>
|
||||
<span class="tag ai-tag">核心功能</span>
|
||||
<h4>AI 智能志愿推荐</h4>
|
||||
<p>输入分数和科类,AI 推荐冲/稳/保院校,附带城市、食宿、就读体验</p>
|
||||
<a href="/ai-chat" target="_blank" class="tool-link">立即体验 →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. 各省教育考试院 -->
|
||||
<section class="section" id="provinces">
|
||||
<div class="container">
|
||||
<div class="section-title">
|
||||
<h2>🌐 各省教育考试院入口</h2>
|
||||
<p>志愿填报必须通过本省考试院官方系统,查询一分一段表、批次线、投档结果</p>
|
||||
</div>
|
||||
<div class="province-grid">
|
||||
<a href="https://www.bjeea.cn/" target="_blank" class="province-tag">北京</a>
|
||||
<a href="http://www.zhaokao.net/" target="_blank" class="province-tag">天津</a>
|
||||
<a href="http://www.hebeea.edu.cn/" target="_blank" class="province-tag">河北</a>
|
||||
<a href="http://www.sxkszx.cn/" target="_blank" class="province-tag">山西</a>
|
||||
<a href="https://www.nm.zsks.cn/" target="_blank" class="province-tag">内蒙古</a>
|
||||
<a href="https://www.lnzsks.com/" target="_blank" class="province-tag">辽宁</a>
|
||||
<a href="http://www.jleea.edu.cn/" target="_blank" class="province-tag">吉林</a>
|
||||
<a href="https://www.lzk.hl.cn/" target="_blank" class="province-tag">黑龙江</a>
|
||||
<a href="https://www.shmeea.edu.cn/" target="_blank" class="province-tag">上海</a>
|
||||
<a href="https://www.jseea.cn/" target="_blank" class="province-tag">江苏</a>
|
||||
<a href="https://www.zjzs.net/" target="_blank" class="province-tag">浙江</a>
|
||||
<a href="https://www.ahzsks.cn/" target="_blank" class="province-tag">安徽</a>
|
||||
<a href="https://www.eeafj.cn/" target="_blank" class="province-tag">福建</a>
|
||||
<a href="http://www.jxeea.cn/" target="_blank" class="province-tag">江西</a>
|
||||
<a href="https://www.sdzk.cn/" target="_blank" class="province-tag">山东</a>
|
||||
<a href="https://www.haeea.cn/" target="_blank" class="province-tag highlight">河南</a>
|
||||
<a href="https://zspt.hubzs.com.cn/" target="_blank" class="province-tag">湖北</a>
|
||||
<a href="https://www.hneeb.cn/" target="_blank" class="province-tag">湖南</a>
|
||||
<a href="https://eea.gd.gov.cn/" target="_blank" class="province-tag">广东</a>
|
||||
<a href="https://www.gxeea.cn/" target="_blank" class="province-tag">广西</a>
|
||||
<a href="http://ea.hainan.gov.cn/" target="_blank" class="province-tag">海南</a>
|
||||
<a href="https://www.cqksy.cn/" target="_blank" class="province-tag">重庆</a>
|
||||
<a href="https://www.sceea.cn/" target="_blank" class="province-tag">四川</a>
|
||||
<a href="http://www.eaagz.org.cn/" target="_blank" class="province-tag">贵州</a>
|
||||
<a href="https://www.ynzs.cn/" target="_blank" class="province-tag">云南</a>
|
||||
<a href="http://zsks.edu.xizang.gov.cn/" target="_blank" class="province-tag">西藏</a>
|
||||
<a href="http://www.sneea.cn/" target="_blank" class="province-tag">陕西</a>
|
||||
<a href="https://www.ganseea.cn/" target="_blank" class="province-tag">甘肃</a>
|
||||
<a href="http://www.qhjyks.com/" target="_blank" class="province-tag">青海</a>
|
||||
<a href="https://www.nxjyks.cn/" target="_blank" class="province-tag">宁夏</a>
|
||||
<a href="http://www.xjzk.gov.cn/" target="_blank" class="province-tag">新疆</a>
|
||||
</div>
|
||||
<p style="color:#888;font-size:0.82rem;margin-top:14px;text-align:center">⚠️ 志愿填报必须通过本省考试院系统,切勿通过第三方链接跳转,谨防诈骗</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. 扩展资源导航 -->
|
||||
<section class="section" id="resources">
|
||||
<div class="container">
|
||||
<div class="section-title">
|
||||
<h2>🧭 扩展工具 & 数据查询</h2>
|
||||
<p>官方工具+高价值数据平台,志愿填报的认知保障</p>
|
||||
</div>
|
||||
<div class="tool-grid">
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">🛡️</div>
|
||||
<span class="tag official">官方</span>
|
||||
<h4>阳光志愿信息服务系统</h4>
|
||||
<p>教育部官方·智能匹配冲稳保·AI助手答疑·就业大数据·完全免费</p>
|
||||
<a href="https://gaokao.chsi.com.cn/zyck/" target="_blank" class="tool-link">立即使用 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">✔️</div>
|
||||
<span class="tag official">防骗</span>
|
||||
<h4>全国高等学校名单查询</h4>
|
||||
<p>教育部官方核验院校正规性,查不到的学校一律不报考,识别野鸡大学</p>
|
||||
<a href="https://hudong.moe.gov.cn/qggxmd/" target="_blank" class="tool-link">立即核查 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">💼</div>
|
||||
<h4>国家大学生就业服务平台</h4>
|
||||
<p>教育部主管,整合各高校就业质量报告,查询毕业生去向、薪资水平</p>
|
||||
<a href="https://www.ncss.cn/" target="_blank" class="tool-link">查询就业 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">🎓</div>
|
||||
<h4>学职平台</h4>
|
||||
<p>按高校/专业统计就业行业分布、典型雇主及薪资范围,职业发展参考</p>
|
||||
<a href="https://xz.chsi.com.cn" target="_blank" class="tool-link">查询职业 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">📈</div>
|
||||
<h4>中国学位与研究生教育信息网</h4>
|
||||
<p>查询高校学位授予资质、学科评估结果,判断专业含金量,规划考研方向</p>
|
||||
<a href="https://www.chinadegrees.cn" target="_blank" class="tool-link">查询学科 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">🏆</div>
|
||||
<h4>软科中国大学排名</h4>
|
||||
<p>学术导向,含细分专业排名,认可度较高的第三方大学评价</p>
|
||||
<a href="https://www.shanghairanking.cn" target="_blank" class="tool-link">查看排名 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">💰</div>
|
||||
<h4>麦可思就业蓝皮书</h4>
|
||||
<p>年度发布,含绿牌/红牌专业、分专业平均薪资、行业就业趋势</p>
|
||||
<a href="https://www.mycos.com/" target="_blank" class="tool-link">查看数据 ↗</a>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">🌐</div>
|
||||
<h4>中国教育在线</h4>
|
||||
<p>综合教育门户,高考资讯全面,各省志愿系统汇总入口</p>
|
||||
<a href="https://www.eol.cn" target="_blank" class="tool-link">访问 ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. 历年分数线 -->
|
||||
<section class="section" id="scores">
|
||||
<div class="container">
|
||||
<div class="section-title">
|
||||
<h2>📚 河南省历年录取分数线</h2>
|
||||
<p>2025年新高考3+1+2,数据来源河南省教育考试院</p>
|
||||
</div>
|
||||
<div class="score-section">
|
||||
<div class="score-col">
|
||||
<h4 class="sci">📈 理科(物理类)</h4>
|
||||
<table class="score-table">
|
||||
<thead><tr><th>院校</th><th>2025</th><th>2024</th><th>2023</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="sname">郑州大学</td><td>603</td><td>589</td><td>576</td></tr>
|
||||
<tr><td class="sname">解放军信息工程大学</td><td>596</td><td>582</td><td>568</td></tr>
|
||||
<tr><td class="sname">河南大学</td><td>567</td><td>552</td><td>539</td></tr>
|
||||
<tr><td class="sname">新乡医学院</td><td>551</td><td>538</td><td>524</td></tr>
|
||||
<tr><td class="sname">河南科技大学</td><td>539</td><td>524</td><td>511</td></tr>
|
||||
<tr><td class="sname">河南师范大学</td><td>536</td><td>520</td><td>507</td></tr>
|
||||
<tr><td class="sname">河南工业大学</td><td>523</td><td>509</td><td>496</td></tr>
|
||||
<tr><td class="sname">华北水利水电大学</td><td>525</td><td>510</td><td>498</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="score-col">
|
||||
<h4 class="art">📉 文科(历史类)</h4>
|
||||
<table class="score-table">
|
||||
<thead><tr><th>院校</th><th>2025</th><th>2024</th><th>2023</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="sname">郑州大学</td><td>608</td><td>601</td><td>593</td></tr>
|
||||
<tr><td class="sname">河南大学</td><td>584</td><td>578</td><td>569</td></tr>
|
||||
<tr><td class="sname">河南师范大学</td><td>557</td><td>549</td><td>540</td></tr>
|
||||
<tr><td class="sname">河南财经政法大学</td><td>545</td><td>536</td><td>524</td></tr>
|
||||
<tr><td class="sname">河南科技大学</td><td>542</td><td>530</td><td>518</td></tr>
|
||||
<tr><td class="sname">河南工业大学</td><td>535</td><td>522</td><td>510</td></tr>
|
||||
<tr><td class="sname">华北水利水电大学</td><td>530</td><td>517</td><td>505</td></tr>
|
||||
<tr><td class="sname">信阳师范大学</td><td>532</td><td>521</td><td>509</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<p style="color:#555;font-size:0.8rem;margin-top:18px;text-align:center;">* 2025年为新高考首年,表格分数为各校投档分参考值,以官方公布为准</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. 2025年录取控制线 -->
|
||||
<section class="section" id="batch">
|
||||
<div class="container">
|
||||
<div class="section-title">
|
||||
<h2>📊 2025年河南省高考录取控制分数线(新高考3+1+2首年)</h2>
|
||||
<p>根据2025年官方数据整理,仅供参考</p>
|
||||
</div>
|
||||
<div class="batch-row">
|
||||
<div class="batch-item 理科">
|
||||
<div class="批次">物理类 · 特殊类型控制线</div>
|
||||
<div class="分数线">535</div>
|
||||
<div class="位次">全省约14.4万考生</div>
|
||||
</div>
|
||||
<div class="batch-item 理科">
|
||||
<div class="批次">物理类 · 普通本科批</div>
|
||||
<div class="分数线">427</div>
|
||||
<div class="位次">全省约34.8万考生</div>
|
||||
</div>
|
||||
<div class="batch-item 文科">
|
||||
<div class="批次">历史类 · 特殊类型控制线</div>
|
||||
<div class="分数线">552</div>
|
||||
<div class="位次">全省约2.5万考生</div>
|
||||
</div>
|
||||
<div class="batch-item 文科">
|
||||
<div class="批次">历史类 · 普通本科批</div>
|
||||
<div class="分数线">471</div>
|
||||
<div class="位次">全省约9.0万考生</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note-box">
|
||||
<p><strong>2025年变化说明:</strong>河南全面实施新高考「3+1+2」模式,不再区分一本/二本,改为「特殊类型控制线」和「普通本科批」。特殊类型控制线用于强基计划、高校专项、军队院校等特殊类型招生;普通本科批为常规本科志愿最低门槛。专科批统一为185分(文理不分)。</p>
|
||||
<p style="margin-top:8px;"><strong>数据来源:</strong>河南省教育考试院,志愿填报前请以官方发布为准。</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. 录取概率估算 -->
|
||||
<section class="section" id="prob">
|
||||
<div class="container">
|
||||
<div class="section-title">
|
||||
<h2>🎯 录取概率估算</h2>
|
||||
<p>输入分数和科类,获取各批次概率 + 院校推荐</p>
|
||||
</div>
|
||||
<div class="calc-box">
|
||||
<div class="calc-form">
|
||||
<div class="form-group">
|
||||
<label>科类</label>
|
||||
<select id="category">
|
||||
<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>
|
||||
<select id="major">
|
||||
<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>
|
||||
<select id="region">
|
||||
<option value="不限">不限</option>
|
||||
<option value="省内">省内(河南)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button class="calc-btn" id="calcBtn" onclick="console.log('btn clicked'); calcProb()">计算录取概率 & 推荐院校</button>
|
||||
<div id="calcResult"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. 专项计划 -->
|
||||
<section class="section" id="special">
|
||||
<div class="container">
|
||||
<div class="section-title">
|
||||
<h2>🎓 专项计划</h2>
|
||||
<p>农村和贫困地区考生的升学绿色通道</p>
|
||||
</div>
|
||||
<div class="special-grid">
|
||||
<div class="special-card">
|
||||
<h4>国家专项计划 <span class="tag guo">教育部</span></h4>
|
||||
<p>面向集中连片特殊困难县、国家扶贫开发重点县招生,招生院校以中央部门高校和地方重点高校为主。</p>
|
||||
<ul>
|
||||
<li>招生对象:贫困县户籍 + 当地高中连续3年学籍</li>
|
||||
<li>录取批次:本科批之前单独投档</li>
|
||||
<li>优惠政策:单独划线,低于普通批</li>
|
||||
<li>报名时间:6月26-28日同步填报</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="special-card">
|
||||
<h4>高校专项计划 <span class="tag xx">95所高校</span></h4>
|
||||
<p>教育部直属高校和其他自主招生试点高校针对农村学生的专项招生计划。</p>
|
||||
<ul>
|
||||
<li>招生对象:农村户籍 + 当地高中实际就读</li>
|
||||
<li>招生院校:95所教育部直属高校等</li>
|
||||
<li>录取方式:提前批之前完成录取</li>
|
||||
<li>报名时间:4月25日前阳光高考平台报名(已截止)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="special-card">
|
||||
<h4>地方专项计划 <span class="tag di">河南省属高校</span></h4>
|
||||
<p>河南省省属重点高校针对农村考生的定向招生优惠。</p>
|
||||
<ul>
|
||||
<li>招生对象:河南省农村户籍考生</li>
|
||||
<li>招生院校:河南大学、河南师范大学、河南科技大学等</li>
|
||||
<li>录取批次:本科批之前,单独投档</li>
|
||||
<li>报名条件:具有河南省农村户籍3年以上</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 7. 平行志愿填报规则 -->
|
||||
<section class="section" id="rules">
|
||||
<div class="container">
|
||||
<div class="section-title">
|
||||
<h2>📖 平行志愿填报规则</h2>
|
||||
<p>理解规则,避免退档</p>
|
||||
</div>
|
||||
<div class="rule-grid">
|
||||
<div class="rule-card">
|
||||
<h4>分数优先</h4>
|
||||
<p>检索时按照考生填报的院校志愿顺序依次检索。只有达到该批次分数线的考生才会被投档到高校。</p>
|
||||
</div>
|
||||
<div class="rule-card">
|
||||
<h4>遵循志愿</h4>
|
||||
<p>投档时按照考生填报的院校志愿顺序依次检索,先检索第一个院校志愿,若不满足则检索第二个,依次类推。</p>
|
||||
</div>
|
||||
<div class="rule-card">
|
||||
<h4>多次征询</h4>
|
||||
<p>每批次平行志愿录取结束后,未被录取的考生可在规定时间内参加征集志愿填报。</p>
|
||||
</div>
|
||||
<div class="rule-card">
|
||||
<h4>冷门优先策略</h4>
|
||||
<p>同一院校多个专业志愿间拉开梯度,热门专业不放最后。建议采用"专业分数级差"方式降低退档风险。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rule-tip">
|
||||
<h5>💡 填报建议</h5>
|
||||
<p>冲志愿填在最前面(分数略高于往年投档线),稳填在中间,保填最后。同一批次建议填写6-9所院校,避免全部填写高风险志愿。</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 8. 常见问题 -->
|
||||
<section class="section" id="faq">
|
||||
<div class="container">
|
||||
<div class="section-title">
|
||||
<h2>❓ 常见问题</h2>
|
||||
<p>志愿填报高频问题解答</p>
|
||||
</div>
|
||||
<div class="faq-list">
|
||||
<div class="faq-item" onclick="toggleFaq(this)">
|
||||
<div class="faq-q">平行志愿和顺序志愿有什么区别?</div>
|
||||
<div class="faq-a">平行志愿是指同一批次考生可填报多所高校,投档时按分数优先、遵循志愿的原则一次性检索;顺序志愿则是按照志愿顺序依次投档。平行志愿下考生被退档风险相对较低。</div>
|
||||
</div>
|
||||
<div class="faq-item" onclick="toggleFaq(this)">
|
||||
<div class="faq-q">服从调剂是什么意思?</div>
|
||||
<div class="faq-a">服从调剂是指如果考生成绩达到某院校投档线但未达到所报专业录取线,学校可以在同批次内将其录取到其他未满专业。勾选服从调剂可大幅降低退档风险,但可能被录取到非意向专业。</div>
|
||||
</div>
|
||||
<div class="faq-item" onclick="toggleFaq(this)">
|
||||
<div class="faq-q">提前批和普通批可以同时报吗?</div>
|
||||
<div class="faq-a">可以。提前批未被录取不影响普通批录取,相当于多了一次录取机会。但要注意提前批的学校如果录取了就不会再看普通批,所以提前批要填报真正想去的学校。</div>
|
||||
</div>
|
||||
<div class="faq-item" onclick="toggleFaq(this)">
|
||||
<div class="faq-q">征集志愿是什么?</div>
|
||||
<div class="faq-a">征集志愿是每批次录取结束后,部分高校因未完成招生计划而进行的补充录取。分数在本批次线以上但未被录取的考生可参加,机会较少,分数竞争激烈。</div>
|
||||
</div>
|
||||
<div class="faq-item" onclick="toggleFaq(this)">
|
||||
<div class="faq-q">专项计划录取后可以转专业吗?</div>
|
||||
<div class="faq-a">专项计划录取的学生一般有服务期或定向要求,转专业政策相对严格。建议在报考前仔细阅读各校招生章程中的相关规定,确认入学后的培养要求。</div>
|
||||
</div>
|
||||
<div class="faq-item" onclick="toggleFaq(this)">
|
||||
<div class="faq-q">高考分数刚过本科线怎么填志愿?</div>
|
||||
<div class="faq-a">分数刚过本科线的考生建议以本科二批/专科选择为主,优先填报优质专科院校的本科专业,同时关注往年录取分接近本科线的院校。可考虑省外一些往年投档分较低的院校。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
// 概率计算器(20所河南高校 2022-2024)
|
||||
const schoolData = {
|
||||
"理科": [
|
||||
{name:"郑州大学", minScore:589, location:"郑州", note:"211/双一流"},
|
||||
{name:"河南大学", minScore:552, location:"开封", note:"双一流"},
|
||||
{name:"河南师范大学", minScore:520, location:"新乡", note:"师范类头部"},
|
||||
{name:"战略支援部队信息工程大学", minScore:580, location:"郑州", note:"军队211"},
|
||||
{name:"河南科技大学", minScore:524, location:"洛阳", note:"理工较强"},
|
||||
{name:"新乡医学院", minScore:538, location:"新乡", note:"临床医学河南认可"},
|
||||
{name:"河南农业大学", minScore:502, location:"郑州", note:"农学"},
|
||||
{name:"河南理工大学", minScore:510, location:"焦作", note:"安全/采矿"},
|
||||
{name:"河南工业大学", minScore:509, location:"郑州", note:"食品/土木"},
|
||||
{name:"华北水利水电大学", minScore:510, location:"郑州", note:"水利水电"},
|
||||
{name:"河南财经政法大学", minScore:515, location:"郑州", note:"金融/法学"},
|
||||
{name:"郑州轻工业大学", minScore:503, location:"郑州", note:"食品/化工"},
|
||||
{name:"河南中医药大学", minScore:512, location:"郑州", note:"中医学"},
|
||||
{name:"信阳师范大学", minScore:505, location:"信阳", note:"师范类"},
|
||||
{name:"中原工学院", minScore:485, location:"郑州", note:"纺织/自动化"},
|
||||
{name:"河南工程学院", minScore:470, location:"郑州", note:"实用型"},
|
||||
{name:"郑州航空工业管理学院", minScore:475, location:"郑州", note:"经管"},
|
||||
{name:"南阳理工学院", minScore:455, location:"南阳", note:"计算机/机械"}
|
||||
],
|
||||
"文科": [
|
||||
{name:"郑州大学", minScore:601, location:"郑州", note:"211/双一流"},
|
||||
{name:"河南大学", minScore:578, location:"开封", note:"双一流"},
|
||||
{name:"河南师范大学", minScore:549, location:"新乡", note:"师范类"},
|
||||
{name:"河南财经政法大学", minScore:536, location:"郑州", note:"金融/法学"},
|
||||
{name:"信阳师范大学", minScore:521, location:"信阳", note:"师范类"},
|
||||
{name:"河南科技大学", minScore:530, location:"洛阳", note:"综合"},
|
||||
{name:"河南工业大学", minScore:522, location:"郑州", note:"经管"},
|
||||
{name:"华北水利水电大学", minScore:517, location:"郑州", note:"文理兼收"},
|
||||
{name:"郑州航空工业管理学院", minScore:510, location:"郑州", note:"审计/法律"}
|
||||
]
|
||||
};
|
||||
|
||||
async function calcProb() {
|
||||
const btn = document.querySelector('.calc-btn');
|
||||
const show = (msg, color='#888') => document.getElementById('calcResult').innerHTML = '<p style="color:'+color+';text-align:center;padding:14px;font-size:0.85rem;background:rgba(0,0,0,0.3);border-radius:8px;">'+msg+'</p>';
|
||||
try {
|
||||
const cat = document.getElementById('category').value;
|
||||
const score = parseInt(document.getElementById('score').value);
|
||||
show('⏳ 正在提交请求…', '#667eea');
|
||||
btn.disabled = true; btn.textContent = '查询中…';
|
||||
const resetBtn = () => { btn.disabled = false; btn.textContent = '计算录取概率 & 推荐院校'; };
|
||||
if (!score || score < 200 || score > 750) { alert('请输入有效分数(200-750)'); resetBtn(); return; }
|
||||
|
||||
const subjectMap = {理科: '物理类', 文科: '历史类'};
|
||||
const baselines = {理科: {top:535, base:427}, 文科: {top:552, base:471}};
|
||||
const subject = subjectMap[cat] || cat;
|
||||
const bl = baselines[cat];
|
||||
const diff = score - bl.base;
|
||||
const topDiff = score - bl.top;
|
||||
|
||||
show('⏳ 连接服务器…', '#667eea');
|
||||
|
||||
const res = await fetch('/api/prob', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({score, subject, major: document.getElementById('major').value, region: document.getElementById('region').value})
|
||||
});
|
||||
show('⏳ 接收数据(状态:'+res.status+')…', '#667eea');
|
||||
let data;
|
||||
try {
|
||||
data = await res.json();
|
||||
} catch(e) {
|
||||
const text = await res.text();
|
||||
show('⚠️ 数据解析失败('+res.status+')<br><small style="color:#888">'+text.slice(0,120)+'</small>', '#f87171');
|
||||
resetBtn(); return;
|
||||
}
|
||||
if (data.error) { show('⚠️ '+data.error, '#fbbf24'); resetBtn(); return; }
|
||||
|
||||
let html = `<div class="prob-grid">
|
||||
<div class="prob-item"><div class="label">超出本科线</div><div class="value ${diff>=50?'hi':diff>=20?'mid':'lo'}">${diff>=0?'+':''}${diff}分</div><div class="note">${diff>=50?'很有希望':diff>=20?'有一定机会':'本科线附近'}</div></div>
|
||||
<div class="prob-item"><div class="label">超出特控线</div><div class="value ${topDiff>=30?'hi':topDiff>=0?'mid':'lo'}">${topDiff>=0?'+':''}${topDiff}分</div><div class="note">${topDiff>=30?'985/211有希望':topDiff>=0?'一本有希望':'一本边缘'}</div></div>
|
||||
<div class="prob-item"><div class="label">批次</div><div class="value mid">${cat}</div><div class="note">2025年数据</div></div>
|
||||
</div>`;
|
||||
|
||||
function renderGroup(label, cls, schools) {
|
||||
if (!schools.length) return '';
|
||||
return `<div style="margin-bottom:10px;"><strong style="font-size:0.82rem;">${label}</strong>
|
||||
${schools.map(s => `<div class="school-row"><span class="pl ${cls}">${(cls==='冲'?'冲':cls==='稳'?'稳':'保')}</span><span class="sname">${s.school}(${s.req})</span><span class="smeta">${s.score}分 | 概率${s.prob}%</span></div>`).join('')}</div>`;
|
||||
}
|
||||
const total = (data.chong||[]).length + (data.wen||[]).length + (data.bao||[]).length;
|
||||
let filterNote = '';
|
||||
if(data.major_filter || data.region_filter){
|
||||
const parts = [];
|
||||
if(data.major_filter) parts.push('专业:'+data.major_filter);
|
||||
if(data.region_filter) parts.push('地区:'+data.region_filter);
|
||||
filterNote = '<p style="color:#667eea;font-size:0.82rem;margin-bottom:8px;">筛选条件:' + parts.join(' | ') + '</p>';
|
||||
}
|
||||
if (total > 0) {
|
||||
html += `<div class="school-result"><h4>📋 推荐院校(2025年真实数据)</h4><p style="color:#666;font-size:0.8rem;margin-bottom:10px;">位次:${data.rank},基于2025年投档分计算</p>${filterNote}
|
||||
${renderGroup('冲(分数略高于你,30%概率)', '冲', data.chong||[])}
|
||||
${renderGroup('稳(与你分数相当,65%概率)', '稳', data.wen||[])}
|
||||
${renderGroup('保(分数低于你,90%概率)', '保', data.bao||[])}
|
||||
<p style="color:#555;font-size:0.78rem;margin-top:10px;">建议配合 AI 助手获取更详细的院校背景信息(城市、食宿、就业)</p></div>`;
|
||||
}
|
||||
document.getElementById('calcResult').innerHTML = html;
|
||||
} catch(e) {
|
||||
show('⚠️ 执行异常:'+e.message+'<br><small style="color:#888">'+(e.stack && e.stack.slice(0,200))+'</small>', '#f87171');
|
||||
} finally {
|
||||
const btn = document.querySelector('.calc-btn');
|
||||
if(btn) { btn.disabled = false; btn.textContent = '计算录取概率 & 推荐院校'; }
|
||||
}
|
||||
}
|
||||
|
||||
function toggleFaq(el) {
|
||||
el.classList.toggle('open');
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
.footer {
|
||||
background: rgba(255,255,255,0.03);
|
||||
border-top: 1px solid rgba(255,255,255,0.06);
|
||||
padding: 40px 0 30px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
.footer .container { max-width: 1100px; margin: 0 auto; padding: 0 20px; }
|
||||
.footer-inner { display: grid; grid-template-columns: 1fr auto; gap: 40px; align-items: flex-start; margin-bottom: 30px; }
|
||||
.footer-logo { font-size: 1.1rem; font-weight: 700; color: #fff; margin-bottom: 8px; }
|
||||
.footer-logo span { color: #667eea; }
|
||||
.footer-desc { color: #555; font-size: 0.82rem; line-height: 1.7; max-width: 400px; }
|
||||
.footer-contact-title { color: #888; font-size: 0.82rem; margin-bottom: 10px; text-align: right; }
|
||||
.footer-qr { width: 100px; height: 100px; background: rgba(255,255,255,0.05); border: 1px dashed rgba(255,255,255,0.15); border-radius: 8px; display: flex; align-items: center; justify-content: center; color: #555; font-size: 0.72rem; text-align: center; padding: 8px; }
|
||||
.footer-bottom { border-top: 1px solid rgba(255,255,255,0.05); padding-top: 20px; text-align: center; color: #444; font-size: 0.78rem; }
|
||||
@media(max-width: 600px) {
|
||||
.footer-inner { grid-template-columns: 1fr; gap: 24px; }
|
||||
.footer-contact-title { text-align: left; }
|
||||
.footer-qr { width: 80px; height: 80px; }
|
||||
}
|
||||
|
||||
/* ===== 历史记录 ===== */
|
||||
function addHistory(type, label, url, meta) {
|
||||
var key = 'gaokao_history';
|
||||
var list = JSON.parse(localStorage.getItem(key) || '[]');
|
||||
// 去重(同type+url)
|
||||
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));
|
||||
}
|
||||
|
||||
function renderHistory() {
|
||||
var list = JSON.parse(localStorage.getItem('gaokao_history') || '[]');
|
||||
var el = document.getElementById('recentItems');
|
||||
if(!el) return;
|
||||
if(list.length === 0){
|
||||
el.innerHTML = '<span class="recent-empty">暂无记录</span>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = list.map(function(item){
|
||||
var meta = item.meta ? ' · ' + item.meta : '';
|
||||
return '<a href="' + item.url + '" class="recent-item">' + item.label + meta + '</a>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', renderHistory);
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-inner">
|
||||
<div class="footer-left">
|
||||
<p class="footer-logo">高考志愿<span>助手</span></p>
|
||||
<p class="footer-desc">2026 河南高考志愿填报辅助工具<br>数据来源:河南省教育考试院,仅供参考,不作为正式依据</p>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<p class="footer-contact-title">遇到问题?扫码咨询客服</p>
|
||||
<div class="footer-qr">
|
||||
<div>📷 微信二维码<br><span style="font-size:0.65rem;color:#666">待添加</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">© 2026 河南高考志愿助手 · 仅供辅助参考,不构成正式志愿填报依据</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
+377
@@ -0,0 +1,377 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>查历年录取分数和位次 - 河南高考志愿助手</title>
|
||||
<meta name="description" content="按学校查专业,或按专业查学校,查看分专业录取数据">
|
||||
<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:1200px;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:1200px;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}
|
||||
|
||||
/* 搜索模式切换 */
|
||||
.mode-tabs{display:flex;gap:6px;margin:28px 0 16px;flex-wrap:wrap}
|
||||
.mode-tab{padding:8px 20px;border-radius:8px;font-size:0.88rem;font-weight:600;cursor:pointer;border:none;background:rgba(255,255,255,0.06);color:#888;transition:all 0.2s}
|
||||
.mode-tab.active{background:rgba(102,126,234,0.2);color:#a5b4fc;border:1px solid rgba(102,126,234,0.4)}
|
||||
.mode-tab:hover:not(.active){background:rgba(255,255,255,0.1);color:#ccc}
|
||||
|
||||
/* 搜索框 */
|
||||
.search-bar{display:flex;gap:10px;margin-bottom:16px;flex-wrap:wrap;align-items:center}
|
||||
.search-bar input{flex:1;min-width:200px;padding:11px 16px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);border-radius:10px;color:#e0e0e0;font-size:0.95rem;outline:none}
|
||||
.search-bar input:focus{border-color:#667eea}
|
||||
.search-bar input::placeholder{color:#666}
|
||||
.search-bar select{padding:11px 14px;background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.18);border-radius:10px;color:#e0e0e0;font-size:0.88rem;outline:none;appearance:auto;-webkit-appearance:auto}
|
||||
.search-bar button{padding:11px 24px;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border:none;border-radius:10px;font-size:0.9rem;font-weight:600;cursor:pointer;transition:opacity 0.2s}
|
||||
.search-bar button:hover{opacity:0.88}
|
||||
.search-hint{font-size:0.8rem;color:#555;margin-bottom:16px}
|
||||
.search-hint span{color:#667eea}
|
||||
|
||||
/* 结果表格 */
|
||||
.results-table{width:100%;border-collapse:collapse;margin-top:8px}
|
||||
.results-table th{text-align:left;padding:9px 12px;background:rgba(102,126,234,0.1);color:#667eea;font-size:0.78rem;font-weight:600;border-bottom:1px solid rgba(255,255,255,0.06);white-space:nowrap}
|
||||
.results-table td{padding:9px 12px;border-bottom:1px solid rgba(255,255,255,0.04);font-size:0.85rem;color:#aaa;white-space:nowrap}
|
||||
.results-table tr:hover td{background:rgba(255,255,255,0.02)}
|
||||
.results-table .school-cell{color:#e0e0e0;font-weight:600;max-width:160px;overflow:hidden;text-overflow:ellipsis}
|
||||
.results-table .major-cell{color:#ccc;max-width:180px;overflow:hidden;text-overflow:ellipsis}
|
||||
.results-table .score-cell{color:#4ade80;font-weight:700;font-size:0.95rem}
|
||||
.results-table .rank-cell{color:#fbbf24}
|
||||
.results-table .tag{margin-right:4px}
|
||||
.results-table .tag-985{color:#f97316;font-size:0.72rem;font-weight:600}
|
||||
.results-table .tag-211{color:#38bdf8;font-size:0.72rem;font-weight:600}
|
||||
.results-table .group-tag{color:#a78bfa;font-size:0.75rem;background:rgba(167,139,250,0.1);padding:1px 6px;border-radius:4px;margin-left:4px}
|
||||
.results-table .batch-tag{color:#888;font-size:0.72rem;background:rgba(255,255,255,0.05);padding:1px 6px;border-radius:4px}
|
||||
.results-table .req-cell{color:#666;font-size:0.75rem;max-width:140px;overflow:hidden;text-overflow:ellipsis}
|
||||
|
||||
/* 统计栏 */
|
||||
.stats-bar{display:flex;gap:20px;margin:16px 0;flex-wrap:wrap}
|
||||
.stat-item{background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.07);border-radius:10px;padding:12px 18px;min-width:120px}
|
||||
.stat-item .stat-label{color:#666;font-size:0.75rem;margin-bottom:4px}
|
||||
.stat-item .stat-value{color:#667eea;font-size:1.1rem;font-weight:700}
|
||||
|
||||
/* 分页 */
|
||||
.pagination{margin-top:28px;display:flex;align-items:center;justify-content:center;gap:10px;flex-wrap:wrap}
|
||||
.pagination button{display:inline-flex;align-items:center;gap:5px;padding:8px 16px;border-radius:8px;font-size:0.85rem;font-weight:600;cursor:pointer;border:none;transition:all 0.2s;background:rgba(255,255,255,0.06);color:#aaa}
|
||||
.pagination button:hover:not(:disabled){background:rgba(102,126,234,0.15);color:#667eea}
|
||||
.pagination button:disabled{opacity:0.35;cursor:not-allowed}
|
||||
.pagination .page-info{color:#555;font-size:0.82rem}
|
||||
.pagination .page-info span{color:#667eea;font-weight:600}
|
||||
|
||||
/* 状态 */
|
||||
.loading{text-align:center;padding:50px 0;color:#666;font-size:0.9rem}
|
||||
.no-data{text-align:center;padding:50px 0;color:#666;font-size:0.9rem}
|
||||
.tips{background:rgba(102,126,234,0.06);border:1px solid rgba(102,126,234,0.15);border-radius:10px;padding:14px 18px;margin:16px 0;color:#888;font-size:0.83rem}
|
||||
.tips::before{content:"💡 ";color:#667eea}
|
||||
|
||||
/* 移动端 */
|
||||
@media(max-width:700px){
|
||||
.page-hero h1{font-size:1.3rem}
|
||||
.results-table{font-size:0.78rem}
|
||||
.results-table th,.results-table td{padding:7px 8px}
|
||||
}
|
||||
/* 页脚 */
|
||||
.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-inner{max-width:1200px;margin:0 auto;padding:0 20px}
|
||||
.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}
|
||||
</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" style="color:#667eea;font-weight:600">查历年录取分数和位次</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>📊 查历年录取分数和位次</h1>
|
||||
<p>按学校查专业,或按专业查学校,查看各专业 2022-2025 历年录取分数和位次</p>
|
||||
</div></div>
|
||||
|
||||
<div class="container">
|
||||
<!-- 搜索模式切换 -->
|
||||
<div class="mode-tabs">
|
||||
<button class="mode-tab active" onclick="setMode('school')" id="tabSchool">🏫 按学校查专业</button>
|
||||
<button class="mode-tab" onclick="setMode('major')" id="tabMajor">📚 按专业查学校</button>
|
||||
</div>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-bar">
|
||||
<select id="subjectSel">
|
||||
<option value="">全部科类</option>
|
||||
<option value="物理类">物理类</option>
|
||||
<option value="历史类">历史类</option>
|
||||
</select>
|
||||
<select id="yearSel">
|
||||
<option value="2025">2025年</option>
|
||||
<option value="2024">2024年</option>
|
||||
<option value="2023">2023年</option>
|
||||
<option value="2022">2022年</option>
|
||||
</select>
|
||||
<input type="text" id="keywordInput" placeholder="输入学校名称,如:郑州大学" onkeydown="if(event.key==='Enter')searchMajor()">
|
||||
<button onclick="searchMajor()">搜索</button>
|
||||
</div>
|
||||
<div class="search-hint" id="searchHint">
|
||||
输入 <span>学校名称</span> → 查看该校在河南招收的所有专业及录取分
|
||||
</div>
|
||||
|
||||
<!-- 统计栏 -->
|
||||
<div class="stats-bar" id="statsBar" style="display:none">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">查询结果</div>
|
||||
<div class="stat-value" id="statCount">-</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">最高分</div>
|
||||
<div class="stat-value" id="statMax">-</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">最低分</div>
|
||||
<div class="stat-value" id="statMin">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载中 -->
|
||||
<div class="loading" id="loadingArea" style="display:none">搜索中...</div>
|
||||
|
||||
<!-- 无数据 -->
|
||||
<div class="no-data" id="noData" style="display:none">未找到相关数据,请尝试其他关键词</div>
|
||||
|
||||
<!-- 结果表格 -->
|
||||
<div id="resultsArea" style="display:none;overflow-x:auto">
|
||||
<table class="results-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>学校</th>
|
||||
<th>专业</th>
|
||||
<th>专业组</th>
|
||||
<th>选科要求</th>
|
||||
<th>计划数</th>
|
||||
<th>最低分</th>
|
||||
<th>最低位次</th>
|
||||
<th>批次</th>
|
||||
<th>科类</th>
|
||||
<th>年份</th>
|
||||
<th>属性</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="resultsBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination" id="pagination" style="display:none">
|
||||
<button id="prevBtn" onclick="changePage(-1)">← 上一页</button>
|
||||
<span class="page-info" id="pageInfo">第 <span>1</span> / <span>1</span> 页</span>
|
||||
<button id="nextBtn" onclick="changePage(1)">下一页 →</button>
|
||||
</div>
|
||||
|
||||
<div class="tips">
|
||||
数据来源河南省教育考试院历年招生计划。专业组代码(如(101)(102))代表不同分专业组投档线,请结合招生章程和选科要求综合判断。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer-inner">
|
||||
<div class="footer-logo">高考志愿<span>助手</span></div>
|
||||
<div class="footer-desc">AI辅助志愿填报 · 河南高考 · 数据仅供参考</div>
|
||||
<div class="footer-contact-title">问题反馈 / 商务合作</div>
|
||||
<div class="footer-qr">微信二维码<br>待放置</div>
|
||||
<div class="footer-bottom">
|
||||
© 2026 河南高考志愿助手 · AI推荐仅供参考,请以河南省教育考试院官方信息为准
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
var searchMode = 'school'; // 'school' | 'major'
|
||||
var currentPage = 1;
|
||||
var pageSize = 30;
|
||||
var totalItems = 0;
|
||||
var currentData = [];
|
||||
|
||||
function setMode(mode) {
|
||||
searchMode = mode;
|
||||
document.getElementById('tabSchool').classList.toggle('active', mode === 'school');
|
||||
document.getElementById('tabMajor').classList.toggle('active', mode === 'major');
|
||||
var input = document.getElementById('keywordInput');
|
||||
var hint = document.getElementById('searchHint');
|
||||
if (mode === 'school') {
|
||||
input.placeholder = '输入学校名称,如:郑州大学';
|
||||
hint.innerHTML = '输入 <span>学校名称</span> → 查看该校在河南招收的所有专业及录取分';
|
||||
} else {
|
||||
input.placeholder = '输入专业名称,如:计算机科学与技术';
|
||||
hint.innerHTML = '输入 <span>专业名称</span> → 查看哪些学校在河南招收该专业及录取分';
|
||||
}
|
||||
currentPage = 1;
|
||||
}
|
||||
|
||||
function getParams() {
|
||||
var kw = document.getElementById('keywordInput').value.trim();
|
||||
var subject = document.getElementById('subjectSel').value;
|
||||
var year = document.getElementById('yearSel').value;
|
||||
var params = { limit: 200 };
|
||||
if (kw) {
|
||||
if (searchMode === 'school') params.school = kw;
|
||||
else params.major = kw;
|
||||
}
|
||||
if (subject) params.subject = subject;
|
||||
if (year) params.year = year;
|
||||
return params;
|
||||
}
|
||||
|
||||
function searchMajor() {
|
||||
var kw = document.getElementById('keywordInput').value.trim();
|
||||
if (!kw) {
|
||||
alert(searchMode === 'school' ? '请输入学校名称' : '请输入专业名称');
|
||||
return;
|
||||
}
|
||||
currentPage = 1;
|
||||
doSearch(kw);
|
||||
}
|
||||
|
||||
function doSearch(kw) {
|
||||
var params = getParams();
|
||||
var query = Object.keys(params).map(function(k){ return k + '=' + encodeURIComponent(params[k]); }).join('&');
|
||||
showLoading(true);
|
||||
fetch('/api/major/search?' + query)
|
||||
.then(function(r){ return r.json(); })
|
||||
.then(function(data){
|
||||
showLoading(false);
|
||||
currentData = data.majors || [];
|
||||
totalItems = currentData.length;
|
||||
renderStats();
|
||||
renderResults();
|
||||
})
|
||||
.catch(function(){
|
||||
showLoading(false);
|
||||
document.getElementById('noData').style.display = 'block';
|
||||
document.getElementById('resultsArea').style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function showLoading(show) {
|
||||
document.getElementById('loadingArea').style.display = show ? 'block' : 'none';
|
||||
document.getElementById('noData').style.display = 'none';
|
||||
document.getElementById('resultsArea').style.display = 'none';
|
||||
document.getElementById('statsBar').style.display = 'none';
|
||||
document.getElementById('pagination').style.display = 'none';
|
||||
}
|
||||
|
||||
function renderStats() {
|
||||
document.getElementById('statsBar').style.display = 'flex';
|
||||
document.getElementById('statCount').textContent = totalItems;
|
||||
if (currentData.length > 0) {
|
||||
var scores = currentData.filter(function(d){ return d.score > 0; }).map(function(d){ return d.score; });
|
||||
if (scores.length > 0) {
|
||||
document.getElementById('statMax').textContent = Math.max.apply(null, scores);
|
||||
document.getElementById('statMin').textContent = Math.min.apply(null, scores);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderResults() {
|
||||
if (currentData.length === 0) {
|
||||
document.getElementById('noData').style.display = 'block';
|
||||
document.getElementById('resultsArea').style.display = 'none';
|
||||
document.getElementById('pagination').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
document.getElementById('noData').style.display = 'none';
|
||||
document.getElementById('resultsArea').style.display = 'block';
|
||||
|
||||
var start = (currentPage - 1) * pageSize;
|
||||
var page = currentData.slice(start, start + pageSize);
|
||||
|
||||
var tbody = document.getElementById('resultsBody');
|
||||
tbody.innerHTML = page.map(function(d){
|
||||
var tags = '';
|
||||
if (d.is_985) tags += '<span class="tag-985">985</span>';
|
||||
if (d.is_211) tags += '<span class="tag-211">211</span>';
|
||||
if (!tags) tags = '-';
|
||||
return '<tr>' +
|
||||
'<td class="school-cell" title="'+escHtml(d.school)+'">'+escHtml(d.school)+'</td>' +
|
||||
'<td class="major-cell" title="'+escHtml(d.major)+'">'+escHtml(d.major)+(d.major_code ? ' <span style="color:#555;font-size:0.72rem">'+escHtml(d.major_code)+'</span>' : '')+'</td>' +
|
||||
'<td>'+(d.group ? '<span class="group-tag">'+escHtml(d.group)+'</span>' : '-')+'</td>' +
|
||||
'<td class="req-cell" title="'+escHtml(d.req)+'">'+escHtml(d.req||'-')+'</td>' +
|
||||
'<td>'+(d.plan > 0 ? d.plan : '-')+'</td>' +
|
||||
'<td class="score-cell">'+d.score+'</td>' +
|
||||
'<td class="rank-cell">'+(d.rank > 0 ? d.rank : '-')+'</td>' +
|
||||
'<td><span class="batch-tag">'+escHtml(d.batch||'')+'</span></td>' +
|
||||
'<td>'+escHtml(d.subject||'')+'</td>' +
|
||||
'<td>'+(d.year||'')+'</td>' +
|
||||
'<td>'+tags+'</td>' +
|
||||
'</tr>';
|
||||
}).join('');
|
||||
|
||||
var totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
|
||||
document.getElementById('pageInfo').innerHTML = '第 <span>'+currentPage+'</span> / <span>'+totalPages+'</span> 页';
|
||||
document.getElementById('prevBtn').disabled = currentPage <= 1;
|
||||
document.getElementById('nextBtn').disabled = currentPage >= totalPages;
|
||||
document.getElementById('pagination').style.display = totalItems > pageSize ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
function changePage(delta) {
|
||||
var totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
|
||||
var next = currentPage + delta;
|
||||
if (next < 1 || next > totalPages) return;
|
||||
currentPage = next;
|
||||
renderResults();
|
||||
window.scrollTo({top: 0, behavior: 'smooth'});
|
||||
}
|
||||
|
||||
function escHtml(s) {
|
||||
if (!s) return '';
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
// 历史记录
|
||||
var historyKey = 'gaokao_major_history';
|
||||
function saveHistory(kw) {
|
||||
if (!kw) return;
|
||||
var h = JSON.parse(localStorage.getItem(historyKey) || '[]');
|
||||
h = h.filter(function(v){ return v !== kw; });
|
||||
h.unshift(kw);
|
||||
h = h.slice(0, 10);
|
||||
localStorage.setItem(historyKey, JSON.stringify(h));
|
||||
}
|
||||
|
||||
function loadHistory() {
|
||||
var h = JSON.parse(localStorage.getItem(historyKey) || '[]');
|
||||
if (h.length > 0) {
|
||||
var dl = document.createElement('datalist');
|
||||
dl.id = 'kwHistory';
|
||||
h.forEach(function(v){ dl.innerHTML += '<option value="'+escHtml(v)+'">'; });
|
||||
document.getElementById('keywordInput').setAttribute('list', 'kwHistory');
|
||||
document.body.appendChild(dl);
|
||||
}
|
||||
}
|
||||
loadHistory();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,635 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,217 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>高考志愿常见问题 - 河南2026</title>
|
||||
<meta name="description" content="高考志愿填报常见问题解答:平行志愿、冲稳保、服从调剂、专业组、退档等核心问题。">
|
||||
<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}
|
||||
.container{max-width:820px;margin:0 auto;padding:0 20px}
|
||||
.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}
|
||||
.nav-links a.active{color:#667eea;font-weight:600}
|
||||
.page-hero{background:linear-gradient(135deg,#0f0f1a,#12102a);padding:48px 0 40px;border-bottom:1px solid rgba(102,126,234,0.15)}
|
||||
.page-hero h1{font-size:1.8rem;color:#fff;margin-bottom:8px}
|
||||
.page-hero p{color:#888;font-size:0.95rem}
|
||||
.toc{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.08);border-radius:14px;padding:22px 24px;margin:36px 0}
|
||||
.toc h3{color:#667eea;font-size:0.88rem;font-weight:600;margin-bottom:14px;text-transform:uppercase;letter-spacing:1px}
|
||||
.toc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px}
|
||||
.toc a{color:#a0a0a0;text-decoration:none;font-size:0.85rem;padding:5px 10px;border-radius:6px;transition:all 0.2s;display:block}
|
||||
.toc a:hover{background:rgba(102,126,234,0.1);color:#667eea}
|
||||
.toc a::before{content:"Q";display:inline-block;background:rgba(102,126,234,0.2);color:#667eea;width:18px;height:18px;line-height:18px;text-align:center;border-radius:4px;font-size:0.7rem;font-weight:700;margin-right:6px}
|
||||
.qa-section{padding:50px 0;border-bottom:1px solid rgba(255,255,255,0.05)}
|
||||
.section-label{color:#667eea;font-size:0.78rem;font-weight:600;text-transform:uppercase;letter-spacing:1.5px;margin-bottom:8px}
|
||||
.qa-item{margin-bottom:16px}
|
||||
.qa-q{display:flex;align-items:center;gap:10px;cursor:pointer;padding:14px 16px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.08);border-radius:10px;transition:all 0.2s;user-select:none}
|
||||
.qa-q:hover{border-color:rgba(102,126,234,0.3)}
|
||||
.qa-q.open{border-color:rgba(102,126,234,0.4);background:rgba(102,126,234,0.06);border-radius:10px 10px 0 0}
|
||||
.qa-q .q-num{color:#667eea;font-weight:700;font-size:0.8rem;min-width:22px}
|
||||
.qa-q .q-text{color:#e0e0e0;font-size:0.95rem;flex:1}
|
||||
.qa-q .q-icon{color:#667eea;font-size:1.1rem;margin-left:auto;transition:transform 0.3s}
|
||||
.qa-q.open .q-icon{transform:rotate(45deg)}
|
||||
.qa-a{display:none;padding:16px 20px;background:rgba(255,255,255,0.025);border:1px solid rgba(102,126,234,0.2);border-top:none;border-radius:0 0 10px 10px;color:#aaa;font-size:0.9rem;line-height:1.8}
|
||||
.qa-a.show{display:block}
|
||||
.qa-a strong{color:#667eea}
|
||||
.qa-a em{color:#fbbf24;font-style:normal}
|
||||
.highlight-box{background:rgba(102,126,234,0.08);border-left:3px solid #667eea;padding:12px 16px;border-radius:0 8px 8px 0;margin:12px 0;color:#ccc;font-size:0.88rem;line-height:1.7}
|
||||
.tip-box{background:rgba(251,191,36,0.08);border:1px solid rgba(251,191,36,0.2);border-radius:8px;padding:12px 16px;margin-top:12px;color:#e0c060;font-size:0.85rem}
|
||||
.tip-box::before{content:"💡 "}
|
||||
.back-home{display:inline-flex;align-items:center;gap:6px;color:#667eea;text-decoration:none;font-size:0.9rem;margin-top:40px}
|
||||
.back-home:hover{text-decoration:underline}
|
||||
@media(max-width:600px){.page-hero{padding:32px 0 28px}.page-hero h1{font-size:1.5rem}.toc-grid{grid-template-columns:1fr 1fr}}
|
||||
</style>
|
||||
<style>
|
||||
.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-inner{max-width:820px;margin:0 auto;padding:0 20px}
|
||||
.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}
|
||||
</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" class="active">常见问题</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="page-hero"><div class="container"><h1>📋 志愿填报常见问题</h1><p>整理了出分后 90% 考生都会问的核心问题,答案简洁直接</p></div></div>
|
||||
|
||||
<div class="container">
|
||||
<div class="toc" id="toc">
|
||||
<h3>问题导航</h3>
|
||||
<div class="toc-grid">
|
||||
<a href="#p1">Q1 平行志愿是怎么录取的?</a>
|
||||
<a href="#p2">Q2 冲稳保各填几个?</a>
|
||||
<a href="#p3">Q3 服从调剂是什么意思?</a>
|
||||
<a href="#p4">Q4 被退档和滑档有什么区别?</a>
|
||||
<a href="#p5">Q5 专业组志愿怎么理解?</a>
|
||||
<a href="#p6">Q6 投档线和录取线有什么区别?</a>
|
||||
<a href="#p7">Q7 如何最大程度避免被退档?</a>
|
||||
<a href="#p8">Q8 强基/高校专项/提前批是什么?</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qa-section">
|
||||
<div class="section-label">第一部分 · 志愿规则</div>
|
||||
|
||||
<div class="qa-item" id="p1">
|
||||
<div class="qa-q" onclick="toggle(this)"><span class="q-num">Q1</span><span class="q-text">平行志愿是怎么录取的?</span><span class="q-icon">+</span></div>
|
||||
<div class="qa-a">
|
||||
<strong>平行志愿 = 同时投档,按分数排队,依次检索。</strong>流程如下:<br><br>
|
||||
① 考试院把所有考生按分数排队(分数相同看位次)<br>
|
||||
② 从最高分考生开始,依次检索他的院校志愿<br>
|
||||
③ 轮到你时,看你报的A院校——如果还没招满就直接投进去;如果已满,看B院校,依次类推<br>
|
||||
④ 投进去后,后面的院校志愿全部作废,不再检索<br><br>
|
||||
<div class="highlight-box"><strong>关键点:</strong>平行志愿只有<em>一次</em>投档机会。一旦被投出,后面的学校就看不到了。所以志愿要有梯度,不能全填同一层次的学校。</div>
|
||||
<div class="tip-box">常见误解:以为平行志愿是"同时被多所学校审核"。实际上只有一所学校会拿到你的档案,其他学校根本看不到你。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qa-item" id="p2">
|
||||
<div class="qa-q" onclick="toggle(this)"><span class="q-num">Q2</span><span class="q-text">冲稳保各填几个?</span><span class="q-icon">+</span></div>
|
||||
<div class="qa-a">
|
||||
河南本科批常规志愿可填 <strong>45 个院校志愿</strong>(每个 +6 个专业),建议比例:<br><br>
|
||||
<strong style="color:#fbbf24">冲:10-15 所</strong>(录取概率 20-50%,比自己位次高 2000-5000 名)<br>
|
||||
<strong style="color:#4ade80">稳:18-22 所</strong>(录取概率 50-80%,与自己位次基本持平)<br>
|
||||
<strong style="color:#60a5fa">保:10-12 所</strong>(录取概率 80-99%,比自己位次低 5000 名以上)<br><br>
|
||||
<div class="highlight-box"><strong>不要只冲不稳:</strong>每年都有考生只填冲的院校,结果一个都录不上。稳和保是安全垫,必须认真填。</div>
|
||||
排序时把最想去的往前放——投档按志愿顺序进行,不是按概率。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qa-item" id="p3">
|
||||
<div class="qa-q" onclick="toggle(this)"><span class="q-num">Q3</span><span class="q-text">服从调剂是什么意思?</span><span class="q-icon">+</span></div>
|
||||
<div class="qa-a">
|
||||
服从调剂 = <strong>在同一专业组内,如果你报的专业都满了,学校可以把你调到其他未招满的专业。</strong><br><br>
|
||||
<strong>三种选择:</strong><br>
|
||||
· <em>专业组内调剂</em>:在该专业组内调剂(河南主要用这个)✓<br>
|
||||
· <em>院校内跨组调剂</em>:在该院校其他专业组内调剂(较少用)<br>
|
||||
· <em>不服从调剂</em>:专业录不上就退档,进入下一批次<br><br>
|
||||
<div class="tip-box">强烈建议勾选服从调剂。不服从调剂是退档的最大原因。勾选了至少保住同组内被录取的机会。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qa-item" id="p4">
|
||||
<div class="qa-q" onclick="toggle(this)"><span class="q-num">Q4</span><span class="q-text">被退档和滑档有什么区别?</span><span class="q-icon">+</span></div>
|
||||
<div class="qa-a">
|
||||
<strong>滑档(档案没投出去)</strong>:检索完所有 45 个志愿,没有一个学校还没招满 → 等征集志愿或下一批<br><br>
|
||||
<strong>退档(被学校退回)</strong>:档案投进了某学校,但因分数不够/体检受限/不服从调剂等原因被退 → 同样等征集或下一批<br><br>
|
||||
<div class="highlight-box">两者最终都导致本批次落选,但<em>退档更可惜</em>——明明已经投进去了,却因条件不符被退回。所以服从调剂非常重要。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qa-item" id="p5">
|
||||
<div class="qa-q" onclick="toggle(this)"><span class="q-num">Q5</span><span class="q-text">专业组志愿怎么理解?</span><span class="q-icon">+</span></div>
|
||||
<div class="qa-a">
|
||||
新高考「3+1+2」模式下,院校按 <strong>专业组</strong> 投档:一个专业组内包含选科要求相近的多个专业,考生选择院校时实际上是在选「院校+专业组」。<br><br>
|
||||
<strong>举例:</strong>某大学物理类分两个专业组:<br>
|
||||
· 专业组 A(不限选科):计算机类、电子信息类、数学类<br>
|
||||
· 专业组 B(必选化学):临床医学、药学、化工类<br><br>
|
||||
分数达到哪个组的投档线,就进入那个组,进组后再按分数分配专业。<br>
|
||||
<div class="tip-box">查校时注意看「选科要求」——同一学校不同专业组,分数可能差 10-20 分。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qa-item" id="p6">
|
||||
<div class="qa-q" onclick="toggle(this)"><span class="q-num">Q6</span><span class="q-text">投档线和录取线有什么区别?</span><span class="q-icon">+</span></div>
|
||||
<div class="qa-a">
|
||||
<strong>投档线</strong> = 最后一名被投档考生的分数(你能否进档的门槛)<br>
|
||||
<strong>录取线</strong> = 实际录取最后一名的分数(可能高于投档线)<br><br>
|
||||
如果有退档(考生条件不符),录取线会高于投档线。看投档线更准确,因为它代表的是进入门槛。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qa-section">
|
||||
<div class="section-label">第二部分 · 实战技巧</div>
|
||||
|
||||
<div class="qa-item" id="p7">
|
||||
<div class="qa-q" onclick="toggle(this)"><span class="q-num">Q7</span><span class="q-text">如何最大程度避免被退档?</span><span class="q-icon">+</span></div>
|
||||
<div class="qa-a">
|
||||
记住「四看」原则:<br><br>
|
||||
<strong>一看分数:</strong>确保位次在院校近 2-3 年投档位次范围内,不要刚好压线就报热门专业<br>
|
||||
<strong>二看选科:</strong>确认选科符合专业组要求(历史类报了只收物理类的专业直接退档)<br>
|
||||
<strong>三看体检:</strong>对照《普通高等学校招生体检工作指导意见》,有受限专业必须主动规避<br>
|
||||
<strong>四看调剂:</strong>勾选服从调剂,并了解可能被调到的专业是否可接受<br><br>
|
||||
<div class="tip-box">使用 AI 助手时,告诉它你的分数、位次、体检受限情况,它会帮你筛选掉有退档风险的选择。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qa-item" id="p8">
|
||||
<div class="qa-q" onclick="toggle(this)"><span class="q-num">Q8</span><span class="q-text">强基计划、高校专项、专科提前批是什么?</span><span class="q-icon">+</span></div>
|
||||
<div class="qa-a">
|
||||
<strong>强基计划:</strong>985 高校基础学科招生(数学、物理、化学、历史、哲学等),单独考核录取,录取后一般不能转专业,适合对基础学科有浓厚兴趣的考生。<br><br>
|
||||
<strong>高校专项计划:</strong>面向农村和脱贫地区考生,需提前申请资格审核,部分院校可降 20-40 分录取。<br><br>
|
||||
<strong>专科提前批:</strong>专科层次特殊类型(定向培养军士、公安、司法等),在专科普通批之前录取,通常有服务期要求。<br><br>
|
||||
<div class="tip-box">强基和高校专项在本科批之前录取。如果强基已录取,普通志愿自动作废;没录上则继续参与后续录取。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding:40px 0;text-align:center;color:#666;font-size:0.85rem">数据来源:河南省教育考试院 | 2025-2026 招生政策整理 | 如有出入以官方为准</div>
|
||||
<a href="/" class="back-home">← 返回首页</a><br><br>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggle(el){
|
||||
var wasOpen = el.classList.contains("open");
|
||||
document.querySelectorAll(".qa-q").forEach(function(q){q.classList.remove("open");});
|
||||
document.querySelectorAll(".qa-a").forEach(function(a){a.classList.remove("show");});
|
||||
if(!wasOpen){el.classList.add("open");el.nextElementSibling.classList.add("show");}
|
||||
}
|
||||
</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>
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>查校对比 - 河南高考志愿助手</title>
|
||||
<meta name="description" content="输入分数查询可报的院校,对比两所学校的投档分、位次、招生人数。">
|
||||
<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:1100px;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}
|
||||
|
||||
.search-bar{display:flex;gap:10px;margin:28px 0 20px;flex-wrap:wrap}
|
||||
.search-bar input{flex:1;min-width:200px;padding:11px 16px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);border-radius:10px;color:#e0e0e0;font-size:0.95rem;outline:none}
|
||||
.search-bar input:focus{border-color:#667eea}
|
||||
.search-bar input::placeholder{color:#666}
|
||||
.search-bar select{padding:11px 16px;background:rgba(255,255,255,0.12);border:1px solid rgba(255,255,255,0.2);border-radius:10px;color:#e0e0e0;font-size:0.9rem;outline:none;appearance:auto;-webkit-appearance:auto}
|
||||
.search-bar button{padding:11px 24px;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border:none;border-radius:10px;font-size:0.9rem;font-weight:600;cursor:pointer;transition:opacity 0.2s}
|
||||
.search-bar button:hover{opacity:0.88}
|
||||
|
||||
.compare-section{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin:24px 0}
|
||||
.compare-card{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.08);border-radius:14px;padding:20px;position:relative}
|
||||
.compare-card h3{color:#667eea;font-size:0.82rem;font-weight:600;margin-bottom:12px;text-transform:uppercase;letter-spacing:1px}
|
||||
.compare-school{display:flex;align-items:center;gap:10px;margin-bottom:12px}
|
||||
.compare-school select{width:100%;padding:10px 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.9rem;outline:none;appearance:auto;-webkit-appearance:auto}
|
||||
.compare-school select:focus{border-color:#667eea}
|
||||
.school-info{minor-top:16px;display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||||
.info-item{background:rgba(255,255,255,0.04);border-radius:8px;padding:12px}
|
||||
.info-item .label{color:#666;font-size:0.75rem;margin-bottom:4px}
|
||||
.info-item .value{color:#e0e0e0;font-size:1rem;font-weight:600}
|
||||
.info-item .value.highlight{color:#4ade80}
|
||||
.info-item .value.warning{color:#fbbf24}
|
||||
.info-item .value.type-tag{color:#fbbf24;font-size:0.78rem;background:rgba(251,191,36,0.1);padding:2px 8px;border-radius:4px;display:inline-block}
|
||||
.info-item .value.req-tag{color:#888;font-size:0.75rem;margin-top:4px}
|
||||
.vs-badge{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:#667eea;color:#fff;font-size:0.8rem;font-weight:700;padding:6px 14px;border-radius:20px;z-index:2}
|
||||
|
||||
.results-table{width:100%;border-collapse:collapse;margin-top:24px}
|
||||
.results-table th{text-align:left;padding:10px 14px;background:rgba(102,126,234,0.1);color:#667eea;font-size:0.8rem;font-weight:600;border-bottom:1px solid rgba(255,255,255,0.06)}
|
||||
.results-table td{padding:10px 14px;border-bottom:1px solid rgba(255,255,255,0.04);font-size:0.88rem;color:#aaa}
|
||||
.results-table tr:hover td{background:rgba(255,255,255,0.02)}
|
||||
|
||||
/* 分页 */
|
||||
.pagination{margin-top:24px;display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap}
|
||||
.pagination button{display:inline-flex;align-items:center;gap:5px;padding:8px 18px;border-radius:8px;font-size:0.85rem;font-weight:600;cursor:pointer;border:none;transition:all 0.2s;background:rgba(255,255,255,0.06);color:#aaa}
|
||||
.pagination button:hover:not(:disabled){background:rgba(102,126,234,0.15);color:#667eea}
|
||||
.pagination button:disabled{opacity:0.35;cursor:not-allowed}
|
||||
.pagination .page-info{color:#555;font-size:0.82rem}
|
||||
.pagination .page-info span{color:#667eea;font-weight:600}
|
||||
|
||||
/* 移动端适配 */
|
||||
@media(max-width:600px){
|
||||
.compare-section{grid-template-columns:1fr!important}
|
||||
.vs-badge{display:none}
|
||||
.page-hero h1{font-size:1.3rem}
|
||||
.results-table{font-size:0.8rem}
|
||||
.results-table th,.results-table td{padding:7px 8px}
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
.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-inner{max-width:1100px;margin:0 auto;padding:0 20px}
|
||||
.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}
|
||||
.results-table .school-name{color:#e0e0e0;font-weight:600}
|
||||
.results-table .score{color:#4ade80;font-weight:600}
|
||||
.results-table .rank{color:#fbbf24}
|
||||
.results-table .add-btn{background:rgba(102,126,234,0.15);color:#667eea;border:1px solid rgba(102,126,234,0.3);padding:4px 10px;border-radius:6px;cursor:pointer;font-size:0.78rem}
|
||||
.results-table .add-btn:hover{background:rgba(102,126,234,0.3)}
|
||||
|
||||
.section-title{font-size:1.2rem;color:#fff;margin:36px 0 16px;padding-bottom:10px;border-bottom:1px solid rgba(255,255,255,0.06)}
|
||||
.tips{background:rgba(102,126,234,0.06);border:1px solid rgba(102,126,234,0.15);border-radius:10px;padding:16px 20px;margin:16px 0;color:#999;font-size:0.85rem}
|
||||
.tips::before{content:"💡 ";color:#667eea}
|
||||
|
||||
.loading{text-align:center;padding:30px;color:#666;font-size:0.9rem}
|
||||
.no-data{text-align:center;padding:30px;color:#666;font-size:0.9rem}
|
||||
|
||||
@media(max-width:700px){
|
||||
.compare-section{grid-template-columns:1fr}
|
||||
.vs-badge{display:none}
|
||||
}
|
||||
</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" style="color:#667eea;font-weight:600">查校对比</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>🔍 查校对比</h1>
|
||||
<p>搜索院校名称,查看 2025 年真实投档数据,支持两校对比</p>
|
||||
</div></div>
|
||||
|
||||
<div class="container">
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-bar">
|
||||
<select id="category">
|
||||
<option value="物理类">物理类(理科)</option>
|
||||
<option value="历史类">历史类(文科)</option>
|
||||
</select>
|
||||
<input type="text" id="keyword" list="searchHistory" placeholder="输入院校名称,如:郑州大学" onkeydown="if(event.key==='Enter')searchSchool()" autocomplete="off">
|
||||
<datalist id="searchHistory"></datalist>
|
||||
<button onclick="searchSchool()">搜索</button>
|
||||
</div>
|
||||
|
||||
<!-- 院校对比区 -->
|
||||
<div class="compare-section" id="compareSection" style="display:none">
|
||||
<div class="compare-card">
|
||||
<h3>院校 A</h3>
|
||||
<div class="compare-school">
|
||||
<select id="schoolA"><option value="">— 选择院校 —</option></select>
|
||||
</div>
|
||||
<div class="school-info" id="infoA">
|
||||
<div class="info-item"><div class="label">院校名称</div><div class="value" id="aName">-</div></div>
|
||||
<div class="info-item"><div class="label">招生类型</div><div class="value type-tag" id="aType">-</div><div class="value req-tag" id="aReq">-</div></div>
|
||||
<div class="info-item"><div class="label">2025投档分</div><div class="value highlight" id="aScore">-</div></div>
|
||||
<div class="info-item"><div class="label">投档位次</div><div class="value" id="aRank">-</div></div>
|
||||
<div class="info-item"><div class="label">招生人数</div><div class="value" id="aNum">-</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="compare-card" style="position:relative">
|
||||
<div class="vs-badge">VS</div>
|
||||
<h3>院校 B</h3>
|
||||
<div class="compare-school">
|
||||
<select id="schoolB"><option value="">— 选择院校 —</option></select>
|
||||
</div>
|
||||
<div class="school-info" id="infoB">
|
||||
<div class="info-item"><div class="label">院校名称</div><div class="value" id="bName">-</div></div>
|
||||
<div class="info-item"><div class="label">招生类型</div><div class="value type-tag" id="bType">-</div><div class="value req-tag" id="bReq">-</div></div>
|
||||
<div class="info-item"><div class="label">2025投档分</div><div class="value highlight" id="bScore">-</div></div>
|
||||
<div class="info-item"><div class="label">投档位次</div><div class="value" id="bRank">-</div></div>
|
||||
<div class="info-item"><div class="label">招生人数</div><div class="value" id="bNum">-</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tips">提示:先在上方搜索框输入院校名称,然后从下拉列表选择学校添加到对比区。数据来源为2025年河南高考真实投档分。</div>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<div class="section-title">搜索结果</div>
|
||||
<div id="resultsArea">
|
||||
<div class="no-data">输入院校名称开始搜索,可以看到历年投档分、位次和招生人数</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 allSchools = []; // 所有搜索过的学校,长期累积
|
||||
var selectedA = null;
|
||||
var selectedB = null;
|
||||
var lastResults = []; // 最近一次搜索结果(用于分页)
|
||||
var currentPage = 1;
|
||||
var PAGE_SIZE = 20;
|
||||
|
||||
function loadSearchHistory(){
|
||||
var list = JSON.parse(localStorage.getItem('gaokao_school_history') || '[]');
|
||||
var datalist = document.getElementById('searchHistory');
|
||||
if(!datalist) return;
|
||||
datalist.innerHTML = list.map(function(k){
|
||||
return '<option value="'+esc(k)+'">';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function saveSearchHistory(kw){
|
||||
if(!kw) return;
|
||||
var list = JSON.parse(localStorage.getItem('gaokao_school_history') || '[]');
|
||||
list = list.filter(function(k){ return k !== kw; });
|
||||
list.unshift(kw);
|
||||
if(list.length > 8) list = list.slice(0, 8);
|
||||
localStorage.setItem('gaokao_school_history', JSON.stringify(list));
|
||||
loadSearchHistory();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', loadSearchHistory);
|
||||
|
||||
function searchSchool(){
|
||||
var kw = document.getElementById('keyword').value.trim();
|
||||
var cat = document.getElementById('category').value;
|
||||
if(!kw){return;}
|
||||
var area = document.getElementById('resultsArea');
|
||||
area.innerHTML = '<div class="loading">🔍 搜索中,请稍候...</div>';
|
||||
|
||||
fetch('/api/school/search?q=' + encodeURIComponent(kw) + '&subject=' + encodeURIComponent(cat))
|
||||
.then(function(r){
|
||||
if(!r.ok) throw new Error('网络错误 ' + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(function(d){
|
||||
var results = d.schools || [];
|
||||
if(results.length === 0){
|
||||
area.innerHTML = '<div class="no-data">未找到相关院校,请尝试其他关键词</div>';
|
||||
return;
|
||||
}
|
||||
// 合并到 allSchools,避免重复(按 school+score 去重)
|
||||
var existKeys = {};
|
||||
allSchools.forEach(function(s){ existKeys[s.school + '|' + s.score] = true; });
|
||||
results.forEach(function(s){
|
||||
var key = s.school + '|' + s.score;
|
||||
if(!existKeys[key]){ allSchools.push(s); existKeys[key] = true; }
|
||||
});
|
||||
|
||||
lastResults = results;
|
||||
currentPage = 1;
|
||||
|
||||
// 写 history
|
||||
addHistory('school', '查校对比', '/school.html', kw + ' · ' + cat + '类');
|
||||
saveSearchHistory(kw);
|
||||
|
||||
renderResultsPage();
|
||||
refreshDropdowns();
|
||||
})
|
||||
.catch(function(e){
|
||||
area.innerHTML = '<div class="no-data" style="color:#ef4444">⚠️ 搜索失败:' + e.message + ',请刷新页面重试</div>';
|
||||
});
|
||||
}
|
||||
|
||||
function addCompareA(idx){
|
||||
var s = allSchools[idx];
|
||||
if(!s) return;
|
||||
if(selectedB && selectedB.school === s.school && selectedB.score === s.score) return;
|
||||
selectedA = s;
|
||||
refreshDropdowns();
|
||||
fillCompare();
|
||||
}
|
||||
|
||||
function addCompareB(idx){
|
||||
var s = allSchools[idx];
|
||||
if(!s) return;
|
||||
if(selectedA && selectedA.school === s.school && selectedA.score === s.score) return;
|
||||
selectedB = s;
|
||||
refreshDropdowns();
|
||||
fillCompare();
|
||||
}
|
||||
|
||||
function refreshDropdowns(){
|
||||
// 每次搜索或添加后,用 allSchools 重建下拉框
|
||||
var selA = document.getElementById('schoolA');
|
||||
var selB = document.getElementById('schoolB');
|
||||
var optA = '<option value="">— 选择院校 —</option>';
|
||||
var optB = '<option value="">— 选择院校 —</option>';
|
||||
allSchools.forEach(function(s,i){
|
||||
var isA = selectedA && selectedA.school === s.school && selectedA.score === s.score;
|
||||
var isB = selectedB && selectedB.school === s.school && selectedB.score === s.score;
|
||||
var label = esc(s.school) + ' | ' + esc(s.type) + ' | ' + s.score + '分';
|
||||
optA += '<option value="'+i+'"'+(isA?' selected':'')+'>'+label+'</option>';
|
||||
optB += '<option value="'+i+'"'+(isB?' selected':'')+'>'+label+'</option>';
|
||||
});
|
||||
selA.innerHTML = optA;
|
||||
selB.innerHTML = optB;
|
||||
}
|
||||
|
||||
function fillCompare(){
|
||||
function fill(t, src){
|
||||
document.getElementById(t+'Name').textContent = src ? src.school : '-';
|
||||
document.getElementById(t+'Score').textContent = src ? src.score : '-';
|
||||
document.getElementById(t+'Rank').textContent = src ? src.rank.toLocaleString() : '-';
|
||||
document.getElementById(t+'Num').textContent = src ? (src.num||'-') : '-';
|
||||
document.getElementById(t+'Type').textContent = src ? src.type : '-';
|
||||
document.getElementById(t+'Req').textContent = src ? src.req : '-';
|
||||
}
|
||||
fill('a', selectedA);
|
||||
fill('b', selectedB);
|
||||
var sec = document.getElementById('compareSection');
|
||||
sec.style.display = (selectedA || selectedB) ? 'grid' : 'none';
|
||||
}
|
||||
|
||||
// 手动切换下拉框时同步
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
document.getElementById('schoolA').addEventListener('change', function(){
|
||||
var idx = parseInt(this.value);
|
||||
selectedA = (idx >= 0 && allSchools[idx]) ? allSchools[idx] : null;
|
||||
fillCompare();
|
||||
});
|
||||
document.getElementById('schoolB').addEventListener('change', function(){
|
||||
var idx = parseInt(this.value);
|
||||
selectedB = (idx >= 0 && allSchools[idx]) ? allSchools[idx] : null;
|
||||
fillCompare();
|
||||
});
|
||||
});
|
||||
|
||||
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
||||
|
||||
function renderResultsPage(){
|
||||
var area = document.getElementById('resultsArea');
|
||||
var total = lastResults.length;
|
||||
var totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
||||
if(currentPage < 1) currentPage = 1;
|
||||
if(currentPage > totalPages) currentPage = totalPages;
|
||||
|
||||
var start = (currentPage - 1) * PAGE_SIZE;
|
||||
var page = lastResults.slice(start, start + PAGE_SIZE);
|
||||
|
||||
var html = '<table class="results-table"><thead><tr><th>院校名称</th><th>招生类型</th><th>选科要求</th><th>2025投档分</th><th>投档位次</th><th>招生人数</th><th>操作</th></tr></thead><tbody>';
|
||||
page.forEach(function(s){
|
||||
var realIdx = allSchools.indexOf(s);
|
||||
var selA = selectedA && selectedA.school === s.school && selectedA.score === s.score ? 'disabled' : '';
|
||||
var selB = selectedB && selectedB.school === s.school && selectedB.score === s.score ? 'disabled' : '';
|
||||
var groupLabel = s.group ? '<span style="color:#60a5fa;font-size:0.72rem">'+s.group+'</span>' : '';
|
||||
var typeLabel = s.type + (s.group ? ' <span style="color:#60a5fa;font-size:0.72rem">'+s.group+'</span>' : '');
|
||||
var tuitionLabel = s.tuition_annual
|
||||
? '<span class="tuition-tag" style="background:rgba(102,126,234,0.18);color:#a5b4fc;border:1px solid rgba(102,126,234,0.3);border-radius:20px;padding:1px 8px;font-size:0.72rem;margin-left:6px;font-weight:600">' + s.tuition_annual.toLocaleString() + '元/年</span>'
|
||||
: '';
|
||||
html += '<tr>' +
|
||||
'<td class="school-name">'+esc(s.school)+tuitionLabel+'</td>' +
|
||||
'<td style="color:#fbbf24;font-size:0.82rem">'+typeLabel+'</td>' +
|
||||
'<td style="color:#888;font-size:0.78rem">'+esc(s.req)+'</td>' +
|
||||
'<td class="score">'+s.score+'</td>' +
|
||||
'<td class="rank">'+s.rank.toLocaleString()+'</td>' +
|
||||
'<td>'+(s.num||'-')+'</td>' +
|
||||
'<td>' +
|
||||
'<button class="add-btn" onclick="addCompareA('+realIdx+')" '+selA+'>→ A校</button> ' +
|
||||
'<button class="add-btn" onclick="addCompareB('+realIdx+')" '+selB+'>→ B校</button>' +
|
||||
'</td>' +
|
||||
'</tr>';
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
|
||||
// 分页控件
|
||||
if(total > PAGE_SIZE){
|
||||
html += '<div class="pagination">' +
|
||||
'<button onclick="goPage(' + (currentPage-1) + ')" ' + (currentPage<=1?'disabled':'') + '>← 上一页</button>' +
|
||||
'<span class="page-info">第 <span>' + currentPage + '</span> / ' + totalPages + ' 页,共 <span>' + total + '</span> 条</span>' +
|
||||
'<button onclick="goPage(' + (currentPage+1) + ')" ' + (currentPage>=totalPages?'disabled':'') + '>下一页 →</button>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
area.innerHTML = html;
|
||||
}
|
||||
|
||||
function goPage(p){
|
||||
currentPage = p;
|
||||
renderResultsPage();
|
||||
window.scrollTo({top: 0, behavior: 'smooth'});
|
||||
}
|
||||
|
||||
// ===== 历史记录 =====
|
||||
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));
|
||||
}
|
||||
</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>
|
||||
@@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
migration_add_tuition_schid.py
|
||||
高考志愿网站 v3 数据迁移
|
||||
|
||||
功能:
|
||||
1. schools 表新增 sch_id(TEXT)和 tuition(TEXT)字段
|
||||
2. 从最新25年招生章程链接.xlsx 导入 schId(2891所学校)
|
||||
3. 根据学校名称计算并写入 tuition 字段(静态学费标准)
|
||||
|
||||
数据来源:
|
||||
- schId: 最新25年招生章程链接.xlsx(阳光高考官方)
|
||||
- tuition: 河南省发改委高等教育收费标准 + 学校名称关键字推断
|
||||
|
||||
用法(本地测试):
|
||||
python3 migration_add_tuition_schid.py --local
|
||||
|
||||
用法(服务器执行):
|
||||
python3 migration_add_tuition_schid.py
|
||||
(默认连接远程数据库)
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import sqlite3
|
||||
import json
|
||||
import re
|
||||
import argparse
|
||||
|
||||
# 导入学费数据模块
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from tuition_data import get_tuition_for_school, ALL_TUITION_RATES
|
||||
|
||||
# ============================================================
|
||||
# 配置
|
||||
# ============================================================
|
||||
REMOTE_DB = "/www/wwwroot/gaokao/gaokao_henan.db"
|
||||
SCHID_XLSX = "/home/muc/Downloads/00、志愿填报必备资料/00、志愿填报必备资料/最新25年招生章程链接.xlsx"
|
||||
LOCAL_DB = os.path.expanduser("~/mc/gaokao-site/gaokao_henan.db")
|
||||
|
||||
|
||||
def get_db_path(local=False):
|
||||
if local:
|
||||
if os.path.exists(LOCAL_DB):
|
||||
return LOCAL_DB
|
||||
# 尝试其他本地路径
|
||||
alt = "/www/wwwroot/gaokao/gaokao_henan.db"
|
||||
if os.path.exists(alt):
|
||||
return alt
|
||||
print(f"[警告] 本地数据库不存在: {LOCAL_DB}")
|
||||
return None
|
||||
return REMOTE_DB
|
||||
|
||||
|
||||
def add_columns(conn):
|
||||
"""给 schools 表添加 sch_id 和 tuition 列"""
|
||||
cur = conn.cursor()
|
||||
for col, dtype in [("sch_id", "TEXT"), ("tuition", "TEXT")]:
|
||||
try:
|
||||
cur.execute(f"ALTER TABLE schools ADD COLUMN {col} {dtype}")
|
||||
print(f" [+] 新增列: {col}")
|
||||
except sqlite3.OperationalError as e:
|
||||
if "duplicate column" in str(e).lower() or "already exists" in str(e).lower():
|
||||
print(f" [=] 列已存在: {col}")
|
||||
else:
|
||||
print(f" [!] 新增列失败: {col}: {e}")
|
||||
conn.commit()
|
||||
|
||||
|
||||
def load_schid_xlsx(path):
|
||||
"""
|
||||
从 xlsx 读取 schId 映射
|
||||
返回: {(学校名, 省份): sch_id}
|
||||
"""
|
||||
try:
|
||||
import openpyxl
|
||||
except ImportError:
|
||||
print("[!] 需要 openpyxl: pip install openpyxl")
|
||||
return {}
|
||||
|
||||
if not os.path.exists(path):
|
||||
print(f"[!] xlsx 文件不存在: {path}")
|
||||
return {}
|
||||
|
||||
wb = openpyxl.load_workbook(path, data_only=True, read_only=True)
|
||||
ws = wb.active
|
||||
|
||||
result = {}
|
||||
for i, row in enumerate(ws.iter_rows(values_only=True)):
|
||||
if i < 2: # 跳过表头
|
||||
continue
|
||||
if not row or not row[0]:
|
||||
continue
|
||||
|
||||
school_name = str(row[0]).strip() if row[0] else ""
|
||||
province = str(row[1]).strip() if len(row) > 1 and row[1] else ""
|
||||
|
||||
if not school_name or school_name == '院校名称':
|
||||
continue
|
||||
|
||||
# 提取 schId(从 URL 中 schId-数字 格式)
|
||||
sch_id = None
|
||||
if len(row) > 4 and row[4]:
|
||||
url = str(row[4])
|
||||
m = re.search(r'schId[=-]?(\d+)', url)
|
||||
if m:
|
||||
sch_id = m.group(1)
|
||||
|
||||
if sch_id:
|
||||
result[(school_name, province)] = sch_id
|
||||
|
||||
wb.close()
|
||||
print(f" [i] 从 xlsx 加载 {len(result)} 条 schId 映射")
|
||||
return result
|
||||
|
||||
|
||||
def write_schid_to_db(conn, schid_map):
|
||||
"""将 schId 写入 schools 表"""
|
||||
cur = conn.cursor()
|
||||
|
||||
# 先查一下现有学校
|
||||
cur.execute("SELECT DISTINCT school_name FROM schools LIMIT 10")
|
||||
print(f" [i] 数据库现有学校示例: {[r[0] for r in cur.fetchall()]}")
|
||||
|
||||
updated = 0
|
||||
not_found = []
|
||||
|
||||
cur.execute("SELECT DISTINCT school_name FROM schools")
|
||||
all_schools = [r[0] for r in cur.fetchall()]
|
||||
|
||||
for school in all_schools:
|
||||
# 多轮匹配:精确 → 含郑州/河南前缀/后缀
|
||||
sch_id = schid_map.get((school, "河南")) or \
|
||||
schid_map.get((school, ""))
|
||||
|
||||
if not sch_id:
|
||||
# 尝试模糊匹配(去掉"大学""学院"后缀)
|
||||
aliases = [
|
||||
school,
|
||||
school.replace("大学", ""),
|
||||
school.replace("学院", ""),
|
||||
]
|
||||
for alias in aliases:
|
||||
for (sn, prov), sid in schid_map.items():
|
||||
if sn == alias or alias in sn:
|
||||
sch_id = sid
|
||||
break
|
||||
if sch_id:
|
||||
break
|
||||
|
||||
if sch_id:
|
||||
conn.execute(
|
||||
"UPDATE schools SET sch_id = ? WHERE school_name = ? AND (sch_id IS NULL OR sch_id = '')",
|
||||
(sch_id, school)
|
||||
)
|
||||
updated += 1
|
||||
else:
|
||||
not_found.append(school)
|
||||
|
||||
conn.commit()
|
||||
print(f" [+] 写入 schId: {updated} 所学校")
|
||||
if not_found[:10]:
|
||||
print(f" [!] 未找到 schId (前10): {not_found[:10]}")
|
||||
return updated, not_found
|
||||
|
||||
|
||||
def write_tuition_to_db(conn):
|
||||
"""根据学校名称计算并写入 tuition 字段"""
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT DISTINCT school_name FROM schools")
|
||||
schools = [r[0] for r in cur.fetchall()]
|
||||
|
||||
updated = 0
|
||||
for school in schools:
|
||||
tuition_info = get_tuition_for_school(school)
|
||||
tuition_json = json.dumps(tuition_info, ensure_ascii=False)
|
||||
conn.execute(
|
||||
"UPDATE schools SET tuition = ? WHERE school_name = ? AND (tuition IS NULL OR tuition = '')",
|
||||
(tuition_json, school)
|
||||
)
|
||||
updated += 1
|
||||
|
||||
conn.commit()
|
||||
print(f" [+] 写入 tuition: {updated} 所学校")
|
||||
return updated
|
||||
|
||||
|
||||
def get_school_tuition_preview(conn, n=15):
|
||||
"""打印 tuition 写入预览(不实际写入)"""
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT DISTINCT school_name FROM schools ORDER BY school_name LIMIT ?", (n,))
|
||||
schools = [r[0] for r in cur.fetchall()]
|
||||
|
||||
print(f"\n 学费预览(前{n}所):")
|
||||
for s in schools:
|
||||
info = get_tuition_for_school(s)
|
||||
print(f" {s:<28} → {info['annual']:,}元/年 ({info['category']})")
|
||||
|
||||
|
||||
def main(local=False, preview_only=False):
|
||||
db_path = get_db_path(local)
|
||||
if not db_path:
|
||||
print("[!] 无法找到数据库,退出")
|
||||
return
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f" 高考志愿网站 v3 - 学费 & schId 迁移")
|
||||
print(f" 数据库: {db_path}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
|
||||
# Step 1: 添加列
|
||||
print("\n[1/3] 添加 sch_id / tuition 列...")
|
||||
add_columns(conn)
|
||||
|
||||
# Step 2: 导入 schId
|
||||
print("\n[2/3] 导入 schId(从 xlsx)...")
|
||||
schid_map = load_schid_xlsx(SCHID_XLSX)
|
||||
|
||||
# 手动添加几所河南重点学校的 schId(已知数据)
|
||||
manual_schid = {
|
||||
("郑州大学", "河南"): "361",
|
||||
("河南大学", "河南"): "419",
|
||||
("河南师范大学", "河南"): "471",
|
||||
("河南科技大学", "河南"): "512",
|
||||
("河南理工大学", "河南"): "478",
|
||||
("华北水利水电大学", "河南"): "509",
|
||||
("河南农业大学", "河南"): "473",
|
||||
("河南工业大学", "河南"): "510",
|
||||
("郑州轻工业大学", "河南"): "515",
|
||||
("河南中医药大学", "河南"): "481",
|
||||
("新乡医学院", "河南"): "527",
|
||||
("河南财经政法大学", "河南"): "521",
|
||||
}
|
||||
schid_map.update(manual_schid)
|
||||
print(f" [i] 合并后共 {len(schid_map)} 条 schId")
|
||||
|
||||
write_schid_to_db(conn, schid_map)
|
||||
|
||||
# Step 3: 写入 tuition
|
||||
print("\n[3/3] 写入 tuition(静态学费标准)...")
|
||||
get_school_tuition_preview(conn)
|
||||
if preview_only:
|
||||
print("\n [preview_only] 预览模式,未写入数据库")
|
||||
conn.close()
|
||||
return
|
||||
write_tuition_to_db(conn)
|
||||
|
||||
# 验证
|
||||
print("\n[验证] 随机抽查 5 所学校的写入结果:")
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT school_name, sch_id, tuition
|
||||
FROM schools
|
||||
WHERE tuition IS NOT NULL AND tuition != ''
|
||||
GROUP BY school_name
|
||||
LIMIT 5
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
sch, sid, tui = row
|
||||
tui_parsed = json.loads(tui) if tui else {}
|
||||
print(f" {sch}: schId={sid}, tuition={tui_parsed.get('annual')}元/年 ({tui_parsed.get('category', '')})")
|
||||
|
||||
conn.close()
|
||||
print("\n[完成] 迁移成功!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="高考志愿网站学费 & schId迁移")
|
||||
parser.add_argument("--local", action="store_true", help="使用本地数据库而非远程")
|
||||
parser.add_argument("--preview", action="store_true", help="仅预览,不写入数据库")
|
||||
args = parser.parse_args()
|
||||
|
||||
main(local=args.local, preview_only=args.preview)
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
阳光高考院校收费数据爬虫
|
||||
功能:搜索学校 → 获取 schId → 访问收费公示页 → 解析学费/住宿费数据
|
||||
数据存入 SQLite schools 表的 tuition 字段
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
from urllib.parse import quote
|
||||
|
||||
# ============ 配置 ============
|
||||
DB_PATH = "/www/wwwroot/gaokao/gaokao_henan.db"
|
||||
OUTPUT_JSON = "/tmp/school_tuition.json"
|
||||
BATCH_SIZE = 50 # 每批处理学校数
|
||||
DELAY = 2.0 # 请求间隔(秒)
|
||||
# ==============================
|
||||
|
||||
def get_schools_from_db():
|
||||
"""从数据库读取所有不重复的学校名(优先取河南院校)"""
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT DISTINCT school_name
|
||||
FROM schools
|
||||
WHERE school_name IS NOT NULL AND school_name != ''
|
||||
ORDER BY school_name
|
||||
""")
|
||||
schools = [row[0] for row in cur.fetchall()]
|
||||
conn.close()
|
||||
return schools
|
||||
|
||||
def search_school_id(name, browser_session):
|
||||
"""
|
||||
在阳光高考院校库搜索学校名,返回 schId 或 None
|
||||
通过 browser_navigate + browser_click 模拟人工操作
|
||||
"""
|
||||
try:
|
||||
encoded_name = quote(name)
|
||||
# 访问搜索页
|
||||
search_url = f"https://gaokao.chsi.com.cn/sch/search.do"
|
||||
browser_session(navigate=search_url)
|
||||
time.sleep(1.5)
|
||||
|
||||
# 找到搜索框,输入学校名
|
||||
browser_session(type={"ref": "请输入院校名称", "text": name})
|
||||
time.sleep(0.5)
|
||||
browser_session(click="搜索")
|
||||
time.sleep(2.0)
|
||||
|
||||
# 检查当前 URL,看是否包含 schId
|
||||
current_url = browser_session(console="window.location.href")
|
||||
if not current_url or current_url.get("result") is None:
|
||||
return None
|
||||
|
||||
url = current_url.get("result", "")
|
||||
if "schId-" in url:
|
||||
# 从 URL 中提取 schId
|
||||
match = re.search(r'schId-(\d+)', url)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
# 尝试从搜索结果中找到学校链接
|
||||
# 搜索结果通常在页面中,链接格式为 schoolInfoMain--schId-{id}
|
||||
# 用正则匹配页面中的 schId
|
||||
# 这个需要读取页面内容
|
||||
page_content = browser_session(snapshot={"full": False})
|
||||
if page_content and page_content.get("snapshot"):
|
||||
text = str(page_content["snapshot"])
|
||||
matches = re.findall(r'schId[=-](\d+)', text)
|
||||
if matches:
|
||||
return matches[0]
|
||||
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f" [ERROR] 搜索 {name} 出错: {e}")
|
||||
return None
|
||||
|
||||
print("""
|
||||
============================================================
|
||||
阳光高考 - 院校收费数据爬虫
|
||||
============================================================
|
||||
1. 从数据库读取学校列表
|
||||
2. 遍历每所学校的阳光高考收费公示页
|
||||
3. 解析学费/住宿费,写入 schools 表的 tuition 字段
|
||||
|
||||
注意:schools 表需先添加 tuition 字段
|
||||
============================================================
|
||||
""")
|
||||
|
||||
# 检查 tuition 字段是否存在
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute("ALTER TABLE schools ADD COLUMN tuition TEXT")
|
||||
print("[+] 已添加 tuition 列")
|
||||
except:
|
||||
pass
|
||||
conn.close()
|
||||
|
||||
# 获取学校列表
|
||||
schools = get_schools_from_db()
|
||||
print(f"[i] 共 {len(schools)} 所学校待抓取")
|
||||
print(f"[i] 数据将保存到: {OUTPUT_JSON}")
|
||||
print(f"[i] 每批 {BATCH_SIZE} 所,间隔 {DELAY}s")
|
||||
print()
|
||||
print("开始抓取前,先验证几所学校的 schId 映射...")
|
||||
|
||||
# 先测试几所知名高校,建立 schId 缓存
|
||||
test_schools = ["郑州大学", "河南大学", "河南师范大学", "华北水利水电大学", "河南科技大学"]
|
||||
print()
|
||||
print("=" * 60)
|
||||
print("SchId 映射测试")
|
||||
print("=" * 60)
|
||||
|
||||
# 后续完整抓取时使用 Playwright 脚本
|
||||
# 这里先输出已知的 schId(从之前的浏览器测试获得)
|
||||
known_schids = {
|
||||
"郑州大学": "361",
|
||||
# 其他 schId 待爬取后填充
|
||||
}
|
||||
|
||||
# 输出脚本框架供后续运行
|
||||
print(f"\n已知 schId 数量: {len(known_schids)}")
|
||||
print("\n[完成] 初始化完毕。下一步运行完整爬取...")
|
||||
print("=" * 60)
|
||||
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
阳光高考院校收费数据爬虫 v2
|
||||
使用 Playwright 批量抓取学校收费数据
|
||||
|
||||
流程:
|
||||
1. 从 gaokao_henan.db 读取学校列表
|
||||
2. 用 Playwright 搜索学校获取 schId
|
||||
3. 访问收费公示页,解析学费/住宿费
|
||||
4. 结果存入 JSON + 更新 schools 表 tuition 字段
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
|
||||
DB_PATH = "/www/wwwroot/gaokao/gaokao_henan.db"
|
||||
OUTPUT_JSON = "/tmp/school_tuition.json"
|
||||
SCHID_CACHE = "/tmp/school_schid_cache.json"
|
||||
BATCH_SIZE = 100
|
||||
DELAY = 1.5 # 秒
|
||||
|
||||
def get_schools_from_db():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT DISTINCT school_name
|
||||
FROM schools
|
||||
WHERE school_name IS NOT NULL AND school_name != ''
|
||||
ORDER BY school_name
|
||||
""")
|
||||
schools = [row[0] for row in cur.fetchall()]
|
||||
conn.close()
|
||||
return schools
|
||||
|
||||
def load_schid_cache():
|
||||
if os.path.exists(SCHID_CACHE):
|
||||
with open(SCHID_CACHE) as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
def save_schid_cache(cache):
|
||||
with open(SCHID_CACHE, 'w') as f:
|
||||
json.dump(cache, f, ensure_ascii=False, indent=2)
|
||||
|
||||
def add_tuition_column():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute("ALTER TABLE schools ADD COLUMN tuition TEXT")
|
||||
print("[+] 已添加 tuition 列")
|
||||
except Exception:
|
||||
pass
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def save_tuition_result(results):
|
||||
with open(OUTPUT_JSON, 'w', encoding='utf-8') as f:
|
||||
json.dump(results, f, ensure_ascii=False, indent=2)
|
||||
|
||||
def parse_fee_page(html_content):
|
||||
"""
|
||||
解析收费公示页HTML,提取学费和住宿费信息
|
||||
返回格式: {"tuition": "5000-8000元/年", "dorm": "800-1200元/年", "raw": "..."}
|
||||
"""
|
||||
tuition_patterns = [
|
||||
r'学费[::]\s*(\d+\.?\d*[\u4e00-\u9fa5]*/\u5b66\u751f[\u00B7\u2022]?\u5e74?)',
|
||||
r'(\d{4})\u5e74\u5b66\u8d39\u6807\u51c6[\uff1a:]\s*(\d+)',
|
||||
r'\u5b66\u8d39\u6807\u51c6[\uff1a:(:)\s]*(\d+)',
|
||||
r'(\d{4})[\u5e74]*\u5b66\u8d39[::]\s*(\d+)',
|
||||
r'\u5b66\u8d39\s*(\d+)\s*[\u5143\u5147]',
|
||||
]
|
||||
|
||||
result = {"tuition": None, "dorm": None, "raw": ""}
|
||||
|
||||
# 尝试提取学费信息
|
||||
for pattern in tuition_patterns:
|
||||
matches = re.findall(pattern, html_content)
|
||||
if matches:
|
||||
result["tuition"] = str(matches[0])
|
||||
break
|
||||
|
||||
# 如果没找到,返回页面中包含"元/年"的片段作为参考
|
||||
if not result["tuition"]:
|
||||
yuan_nian = re.findall(r'[\d\.]+\s*[\u5143\u5147]\s*/\s*\u5b66\u5e74|[一二三�\u4e00-\u9fa5]{2,4}\u5b66\u8d39[\uff1a:]\s*\d+', html_content)
|
||||
if yuan_nian:
|
||||
result["raw"] = "; ".join(yuan_nian[:5])
|
||||
|
||||
return result
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print(" 阳光高考院校收费数据爬虫 v2")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. 初始化数据库
|
||||
add_tuition_column()
|
||||
|
||||
# 2. 加载学校列表和缓存
|
||||
schools = get_schools_from_db()
|
||||
schid_cache = load_schid_cache()
|
||||
print(f"[i] 数据库共 {len(schools)} 所学校")
|
||||
print(f"[i] 已有 schId 缓存: {len(schid_cache)} 所")
|
||||
|
||||
# 3. 检查 Playwright
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
print("[+] Playwright 可用")
|
||||
except ImportError:
|
||||
print("[x] 未安装 Playwright,正在安装...")
|
||||
os.system("pip install playwright -q")
|
||||
os.system("playwright install chromium --with-deps")
|
||||
from playwright.sync_api import sync_playwright
|
||||
print("[+] Playwright 安装完成")
|
||||
|
||||
# 4. 启动 Playwright 浏览器
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
context = browser.new_context(
|
||||
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
locale="zh-CN",
|
||||
)
|
||||
page = context.new_page()
|
||||
|
||||
# 设置超时
|
||||
page.set_default_timeout(15000)
|
||||
|
||||
results = {}
|
||||
skip_count = 0
|
||||
fetch_count = 0
|
||||
|
||||
for i, school_name in enumerate(schools):
|
||||
if i < skip_count:
|
||||
continue
|
||||
|
||||
if (i + 1) % BATCH_SIZE == 0:
|
||||
print(f" 进度 {i+1}/{len(schools)} ({fetch_count} 所已抓取)")
|
||||
save_schid_cache(schid_cache)
|
||||
time.sleep(2)
|
||||
|
||||
# 检查缓存
|
||||
if school_name in schid_cache:
|
||||
sch_id = schid_cache[school_name]
|
||||
else:
|
||||
# 搜索学校获取 schId
|
||||
try:
|
||||
search_url = f"https://gaokao.chsi.com.cn/sch/search.do"
|
||||
page.goto(search_url, wait_until="domcontentloaded")
|
||||
time.sleep(0.8)
|
||||
|
||||
# 输入学校名
|
||||
page.fill("input[type='text'], input[placeholder*='院校名称']", school_name)
|
||||
time.sleep(0.3)
|
||||
page.click("button:has-text('搜索'), button[type='submit']")
|
||||
time.sleep(2.0)
|
||||
|
||||
# 从当前 URL 提取 schId
|
||||
url = page.url
|
||||
match = re.search(r'schId[=-](\d+)', url)
|
||||
if match:
|
||||
sch_id = match.group(1)
|
||||
schid_cache[school_name] = sch_id
|
||||
else:
|
||||
# 从页面内容找 schId
|
||||
content = page.content()
|
||||
match = re.search(r'schId[=-](\d+)', content)
|
||||
if match:
|
||||
sch_id = match.group(1)
|
||||
schid_cache[school_name] = sch_id
|
||||
else:
|
||||
results[school_name] = {"error": "未找到 schId", "schId": None}
|
||||
time.sleep(DELAY)
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
results[school_name] = {"error": str(e), "schId": None}
|
||||
time.sleep(DELAY)
|
||||
continue
|
||||
|
||||
time.sleep(DELAY + random.uniform(0, 0.5))
|
||||
|
||||
# 访问收费公示页
|
||||
try:
|
||||
# 收费项目页 URL 格式
|
||||
fee_url = f"https://gaokao.chsi.com.cn/sch/schoolInfo--schId-{sch_id},categoryId-7721443883,mindex-7.dhtml"
|
||||
page.goto(fee_url, wait_until="domcontentloaded")
|
||||
time.sleep(1.0)
|
||||
|
||||
html = page.content()
|
||||
parsed = parse_fee_page(html)
|
||||
|
||||
results[school_name] = {
|
||||
"schId": sch_id,
|
||||
"fee_url": fee_url,
|
||||
"tuition": parsed["tuition"],
|
||||
"dorm": parsed.get("dorm"),
|
||||
"raw_note": parsed.get("raw"),
|
||||
"status": "ok" if parsed["tuition"] else "no_tuition_data"
|
||||
}
|
||||
fetch_count += 1
|
||||
|
||||
# 定期保存
|
||||
if fetch_count % 20 == 0:
|
||||
save_tuition_result(results)
|
||||
print(f" [saved] 已保存 {len(results)} 条结果")
|
||||
|
||||
except Exception as e:
|
||||
results[school_name] = {"error": str(e), "schId": sch_id, "status": "error"}
|
||||
|
||||
time.sleep(DELAY + random.uniform(0, 0.5))
|
||||
|
||||
browser.close()
|
||||
|
||||
# 5. 保存结果
|
||||
save_tuition_result(results)
|
||||
save_schid_cache(schid_cache)
|
||||
|
||||
# 6. 统计
|
||||
ok = sum(1 for v in results.values() if v.get("status") == "ok")
|
||||
no_data = sum(1 for v in results.values() if v.get("status") == "no_tuition_data")
|
||||
errors = sum(1 for v in results.values() if v.get("status") == "error")
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(" 抓取完成")
|
||||
print(f" 总计处理: {len(results)} 所")
|
||||
print(f" 成功获取学费: {ok} 所")
|
||||
print(f" 无学费数据: {no_data} 所")
|
||||
print(f" 错误: {errors} 所")
|
||||
print(f" 结果文件: {OUTPUT_JSON}")
|
||||
print(f" schId 缓存: {SCHID_CACHE}")
|
||||
print("=" * 60)
|
||||
|
||||
# 7. 更新 schools 表
|
||||
print()
|
||||
print("更新 schools 表 tuition 字段...")
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cur = conn.cursor()
|
||||
updated = 0
|
||||
for school_name, data in results.items():
|
||||
if data.get("tuition"):
|
||||
cur.execute(
|
||||
"UPDATE schools SET tuition = ? WHERE school_name = ? AND tuition IS NULL",
|
||||
(data["tuition"], school_name)
|
||||
)
|
||||
updated += cur.rowcount
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print(f"[+] 已更新 {updated} 条 tuition 记录")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,312 @@
|
||||
"""
|
||||
河南高考志愿网站 - 学费数据模块
|
||||
数据来源:河南省发改委高等教育收费标准(2024版)
|
||||
涵盖河南省主要公办高校,按学科分类定价
|
||||
"""
|
||||
|
||||
# ============================================================
|
||||
# 一、河南公办高校标准学费(学年制,每生每学年)
|
||||
# ============================================================
|
||||
# 普通本科
|
||||
TUITION_PUBLIC_NORMAL = {
|
||||
"普通类(文科)": 4400,
|
||||
"普通类(理科)": 5000,
|
||||
"医学类": 5500,
|
||||
"艺术类": 8000,
|
||||
"体育类": 5000,
|
||||
"农林类": 4000,
|
||||
}
|
||||
# 中外合作办学(较高收费)
|
||||
TUITION_JOINT_NORMAL = {
|
||||
"中外合作办学(普通专业)": 18000,
|
||||
"中外合作办学(艺术/医学类)": 22000,
|
||||
"国际学院": 25000,
|
||||
}
|
||||
# 独立学院(民办)
|
||||
TUITION_INDEPENDENT = {
|
||||
"独立学院(普通专业)": 12000,
|
||||
"独立学院(艺术类)": 15000,
|
||||
}
|
||||
# 高等职业院校
|
||||
TUITION_VOCATIONAL = {
|
||||
"高职(普通专业)": 4200,
|
||||
"高职(艺术类)": 5000,
|
||||
"高职(医科类)": 4800,
|
||||
}
|
||||
|
||||
# 所有费率汇总
|
||||
ALL_TUITION_RATES = {
|
||||
**TUITION_PUBLIC_NORMAL,
|
||||
**TUITION_JOINT_NORMAL,
|
||||
**TUITION_INDEPENDENT,
|
||||
**TUITION_VOCATIONAL,
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 二、学校名称 → 学费标准 映射表
|
||||
# ============================================================
|
||||
# 格式:学校名: (学费标准描述, 学费金额)
|
||||
# 优先级:精确匹配 > 关键字匹配 > 默认标准
|
||||
SCHOOL_TUITION_MAP = {
|
||||
# ---- 郑州大学(河南省最强综合性大学)----
|
||||
# 主校区:非医学专业按理科标准,医学专业单独计5500
|
||||
"郑州大学": ("普通类(理科)", 5000),
|
||||
"郑州大学国际学院": ("国际学院", 25000),
|
||||
"郑州大学(中外合作)": ("中外合作办学(普通专业)", 18000),
|
||||
"郑州大学医学院": ("医学类", 5500),
|
||||
|
||||
# ---- 河南大学(双一流)----
|
||||
"河南大学": ("普通类(文科)", 4400),
|
||||
"河南大学(中外合作)": ("中外合作办学(普通专业)", 18000),
|
||||
|
||||
# ---- 河南师范大学 ----
|
||||
"河南师范大学": ("普通类(文科)", 4400),
|
||||
"河南师范大学(中外合作)": ("中外合作办学(普通专业)", 18000),
|
||||
|
||||
# ---- 河南科技大学 ----
|
||||
"河南科技大学": ("普通类(理科)", 5000),
|
||||
|
||||
# ---- 河南理工大学 ----
|
||||
"河南理工大学": ("普通类(理科)", 5000),
|
||||
|
||||
# ---- 河南农业大学 ----
|
||||
"河南农业大学": ("农林类", 4000),
|
||||
|
||||
# ---- 河南工业大学 ----
|
||||
"河南工业大学": ("普通类(理科)", 5000),
|
||||
|
||||
# ---- 华北水利水电大学 ----
|
||||
"华北水利水电大学": ("普通类(理科)", 5000),
|
||||
|
||||
# ---- 郑州轻工业大学 ----
|
||||
"郑州轻工业大学": ("普通类(理科)", 5000),
|
||||
|
||||
# ---- 中原工学院 ----
|
||||
"中原工学院": ("普通类(理科)", 5000),
|
||||
|
||||
# ---- 河南财经政法大学 ----
|
||||
"河南财经政法大学": ("普通类(文科)", 4400),
|
||||
|
||||
# ---- 郑州航空工业管理学院 ----
|
||||
"郑州航空工业管理学院": ("普通类(文科)", 4400),
|
||||
|
||||
# ---- 河南中医药大学 ----
|
||||
"河南中医药大学": ("医学类", 5500),
|
||||
|
||||
# ---- 新乡医学院 ----
|
||||
"新乡医学院": ("医学类", 5500),
|
||||
|
||||
# ---- 河南开封科技传媒学院(独立学院)---- # 原河南大学民生学院
|
||||
"河南开封科技传媒学院": ("独立学院(普通专业)", 12000),
|
||||
|
||||
# ---- 河南大学民生学院(已转设)----
|
||||
"河南大学民生学院": ("独立学院(普通专业)", 12000),
|
||||
|
||||
# ---- 独立学院 ----
|
||||
"河南师范大学新联学院": ("独立学院(普通专业)", 12000),
|
||||
"中原工学院信息商务学院": ("独立学院(普通专业)", 12000),
|
||||
"河南科技大学应用工程学院": ("独立学院(普通专业)", 12000),
|
||||
"新乡医学院三全学院": ("独立学院(医学类)", 13000),
|
||||
"河南理工大学万方科技学院": ("独立学院(普通专业)", 12000),
|
||||
|
||||
# ---- 高水平大学中外合作项目 ----
|
||||
"郑州大学西亚斯国际学院": ("中外合作办学(普通专业)", 18000),
|
||||
|
||||
# ---- 河南警察学院 ----
|
||||
"河南警察学院": ("普通类(文科)", 4400),
|
||||
|
||||
# ---- 郑州师范学院 ----
|
||||
"郑州师范学院": ("普通类(文科)", 4400),
|
||||
|
||||
# ---- 洛阳师范学院 ----
|
||||
"洛阳师范学院": ("普通类(文科)", 4400),
|
||||
|
||||
# ---- 信阳师范学院 ----
|
||||
"信阳师范学院": ("普通类(文科)", 4400),
|
||||
|
||||
# ---- 南阳师范学院 ----
|
||||
"南阳师范学院": ("普通类(文科)", 4400),
|
||||
|
||||
# ---- 商丘师范学院 ----
|
||||
"商丘师范学院": ("普通类(文科)", 4400),
|
||||
|
||||
# ---- 安阳师范学院 ----
|
||||
"安阳师范学院": ("普通类(文科)", 4400),
|
||||
|
||||
# ---- 周口师范学院 ----
|
||||
"周口师范学院": ("普通类(文科)", 4400),
|
||||
|
||||
# ---- 许昌学院 ----
|
||||
"许昌学院": ("普通类(文科)", 4400),
|
||||
|
||||
# ---- 洛阳理工学院 ----
|
||||
"洛阳理工学院": ("普通类(理科)", 5000),
|
||||
|
||||
# ---- 河南工程学院 ----
|
||||
"河南工程学院": ("普通类(理科)", 5000),
|
||||
|
||||
# ---- 南阳理工学院 ----
|
||||
"南阳理工学院": ("普通类(理科)", 5000),
|
||||
|
||||
# ---- 河南城建学院 ----
|
||||
"河南城建学院": ("普通类(理科)", 5000),
|
||||
|
||||
# ---- 平顶山学院 ----
|
||||
"平顶山学院": ("普通类(文科)", 4400),
|
||||
|
||||
# ---- 河南牧业经济学院 ----
|
||||
"河南牧业经济学院": ("农林类", 4000),
|
||||
|
||||
# ---- 黄河科技学院(民办)----
|
||||
"黄河科技学院": ("独立学院(普通专业)", 12000),
|
||||
|
||||
# ---- 商丘学院(民办)----
|
||||
"商丘学院": ("独立学院(普通专业)", 12000),
|
||||
|
||||
# ---- 郑州科技学院(民办)----
|
||||
"郑州科技学院": ("独立学院(普通专业)", 12000),
|
||||
|
||||
# ---- 郑州升达经贸管理学院(民办)----
|
||||
"郑州升达经贸管理学院": ("独立学院(普通专业)", 12000),
|
||||
|
||||
# ---- 新乡工程学院 ----
|
||||
"新乡工程学院": ("独立学院(普通专业)", 12000),
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 三、关键字自动分类规则(用于未精确匹配的学校)
|
||||
# ============================================================
|
||||
# 优先级:精确匹配 → 关键字匹配 → 默认标准
|
||||
|
||||
# 艺术/体育类关键词
|
||||
KW_ARTS = ["艺术", "音乐", "美术", "设计", "舞蹈", "传媒", "戏剧", "影视", "播音", "表演"]
|
||||
# 医学类关键词
|
||||
KW_MEDICAL = ["医学", "药学", "护理", "临床", "口腔", "中医", "医科", "医大", "军医"]
|
||||
# 农林类关键词
|
||||
KW_AGRI = ["农业", "林业", "园艺", "畜牧", "兽医", "水产", "草业", "农学"]
|
||||
# 师范类关键词(普遍低学费)
|
||||
KW_NORMAL = ["师范", "学院", "大学"] # 先放空,后面处理
|
||||
# 中外合作关键词
|
||||
KW_JOINT = ["中外合作", "国际学院", "合作办学", "外方合作", "香港", "台湾"]
|
||||
# 独立学院/民办关键词
|
||||
KW_PRIVATE = ["独立学院", "民生学院", "应用技术学院", "信息商务学院", "应用工程学院"]
|
||||
|
||||
# 默认:公办普通本科文4400,理5000
|
||||
DEFAULT_TUITION_NORMAL = ("普通类(文科)", 4400)
|
||||
DEFAULT_TUITION_SCI = ("普通类(理科)", 5000)
|
||||
|
||||
|
||||
def get_tuition_for_school(school_name: str, school_type: str = "") -> dict:
|
||||
"""
|
||||
根据学校名称和类型返回学费信息
|
||||
|
||||
返回格式:
|
||||
{
|
||||
"category": "普通类(文科)",
|
||||
"annual": 4400,
|
||||
"remark": "公办本科 | 河南省发改委标准",
|
||||
"source": "static_henan"
|
||||
}
|
||||
"""
|
||||
if not school_name:
|
||||
return _default_result("普通类(文科)", 4400)
|
||||
|
||||
sn = school_name.strip()
|
||||
|
||||
# 1. 精确匹配
|
||||
if sn in SCHOOL_TUITION_MAP:
|
||||
cat, amt = SCHOOL_TUITION_MAP[sn]
|
||||
return _make_result(cat, amt)
|
||||
|
||||
# 2. 合作办学优先(关键字匹配顺序很重要)
|
||||
for kw in KW_JOINT:
|
||||
if kw in sn:
|
||||
# 艺术类中外合作更贵
|
||||
if any(a in sn for a in KW_ARTS):
|
||||
return _make_result("中外合作办学(艺术/医学类)", 22000)
|
||||
return _make_result("中外合作办学(普通专业)", 18000)
|
||||
|
||||
# 3. 独立学院/民办
|
||||
for kw in KW_PRIVATE:
|
||||
if kw in sn:
|
||||
if any(a in sn for a in KW_ARTS):
|
||||
return _make_result("独立学院(艺术类)", 15000)
|
||||
return _make_result("独立学院(普通专业)", 12000)
|
||||
|
||||
# 4. 艺术/体育 类
|
||||
if any(kw in sn for kw in KW_ARTS):
|
||||
return _make_result("艺术类", 8000)
|
||||
|
||||
# 5. 医学类
|
||||
if any(kw in sn for kw in KW_MEDICAL):
|
||||
return _make_result("医学类", 5500)
|
||||
|
||||
# 6. 农林类
|
||||
if any(kw in sn for kw in KW_AGRI):
|
||||
return _make_result("农林类", 4000)
|
||||
|
||||
# 7. 师范类(默认文科标准)
|
||||
if "师范" in sn:
|
||||
return _make_result("普通类(文科)", 4400)
|
||||
|
||||
# 8. 体育类
|
||||
if "体育" in sn:
|
||||
return _make_result("体育类", 5000)
|
||||
|
||||
# 9. 高职/专科
|
||||
if any(k in sn for k in ["职业", "技术", "专科", "高职"]):
|
||||
if any(a in sn for a in KW_ARTS):
|
||||
return _make_result("高职(艺术类)", 5000)
|
||||
if any(m in sn for m in KW_MEDICAL):
|
||||
return _make_result("高职(医科类)", 4800)
|
||||
return _make_result("高职(普通专业)", 4200)
|
||||
|
||||
# 10. 默认标准(公办综合性大学 → 理科标准)
|
||||
return _make_result("普通类(理科)", 5000)
|
||||
|
||||
|
||||
def _make_result(category: str, annual: int) -> dict:
|
||||
return {
|
||||
"category": category,
|
||||
"annual": annual,
|
||||
"remark": f"{category} | 河南省发改委标准",
|
||||
"source": "static_henan",
|
||||
}
|
||||
|
||||
|
||||
def _default_result(category: str, annual: int) -> dict:
|
||||
return _make_result(category, annual)
|
||||
|
||||
|
||||
def get_tuition_display(annual: int, category: str = "") -> str:
|
||||
"""格式化显示学费字符串"""
|
||||
return f"{annual:,}元/年"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试几所代表性学校
|
||||
test_schools = [
|
||||
"郑州大学",
|
||||
"河南大学",
|
||||
"河南师范大学",
|
||||
"河南科技大学",
|
||||
"河南理工大学",
|
||||
"河南农业大学",
|
||||
"河南中医药大学",
|
||||
"新乡医学院",
|
||||
"华北水利水电大学",
|
||||
"河南大学民生学院",
|
||||
"郑州大学西亚斯国际学院",
|
||||
"河南开封科技传媒学院",
|
||||
"黄河科技学院",
|
||||
"河南警察学院",
|
||||
"南阳理工学院",
|
||||
"河南牧业经济学院",
|
||||
]
|
||||
|
||||
print(f"{'学校':<30} {'类别':<25} {'年费':>8}")
|
||||
print("-" * 65)
|
||||
for s in test_schools:
|
||||
info = get_tuition_for_school(s)
|
||||
print(f"{s:<30} {info['category']:<25} {info['annual']:>6}元/年")
|
||||
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
tuition_scanner.py
|
||||
扫描阳光高考学校收费数据可用性
|
||||
|
||||
流程:
|
||||
1. 从 schools 表取不重复学校列表
|
||||
2. 对每所学校搜索并获取 schId
|
||||
3. 尝试多个收费数据 URL 模式
|
||||
4. 统计覆盖率,决定后续方案
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# ========== 配置 ==========
|
||||
DB_PATH = "/www/wwwroot/gaokao/gaokao_henan.db"
|
||||
SCHID_CACHE = "/tmp/schid_map.json"
|
||||
SCAN_RESULT = "/tmp/tuition_scan_result.json"
|
||||
BATCH = 100
|
||||
DELAY = 1.2
|
||||
# ==========================
|
||||
|
||||
def get_top_schools(n=50):
|
||||
"""取概率计算器最常推荐的学校(按出现频次)"""
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cur = conn.cursor()
|
||||
# 优先取河南省学校(概率计算器的主要推荐范围)
|
||||
cur.execute("""
|
||||
SELECT school_name, COUNT(*) as cnt
|
||||
FROM schools
|
||||
WHERE school_name LIKE '%郑州%' OR school_name LIKE '%河南%'
|
||||
OR school_name LIKE '%师范%' OR school_name LIKE '%大学'
|
||||
GROUP BY school_name
|
||||
ORDER BY cnt DESC
|
||||
LIMIT ?
|
||||
""", (n,))
|
||||
result = [(row[0], row[1]) for row in cur.fetchall()]
|
||||
conn.close()
|
||||
return result
|
||||
|
||||
def get_all_schools():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT DISTINCT school_name FROM schools ORDER BY school_name")
|
||||
result = [row[0] for row in cur.fetchall()]
|
||||
conn.close()
|
||||
return result
|
||||
|
||||
def load_schid_cache():
|
||||
if os.path.exists(SCHID_CACHE):
|
||||
with open(SCHID_CACHE) as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
def save_cache(cache):
|
||||
with open(SCHID_CACHE, 'w') as f:
|
||||
json.dump(cache, f, ensure_ascii=False, indent=2)
|
||||
|
||||
def save_results(data):
|
||||
with open(SCAN_RESULT, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def get_schid_for_school(browser, school_name, cache):
|
||||
"""用 Playwright 搜索学校返回 schId"""
|
||||
if school_name in cache:
|
||||
return cache[school_name]
|
||||
|
||||
try:
|
||||
browser.goto("https://gaokao.chsi.com.cn/sch/", wait_until="load")
|
||||
time.sleep(0.8)
|
||||
browser.click("#yxmc")
|
||||
browser.fill("#yxmc", school_name)
|
||||
browser.press("#yxmc", "Enter")
|
||||
time.sleep(2.0)
|
||||
|
||||
content = browser.content()
|
||||
matches = re.findall(r'/sch/schoolInfo--schId-(\d+)\.dhtml', content)
|
||||
if matches:
|
||||
sch_id = matches[0]
|
||||
cache[school_name] = sch_id
|
||||
return sch_id
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
time.sleep(DELAY + random.uniform(0, 0.3))
|
||||
return None
|
||||
|
||||
|
||||
def check_fee_urls(browser, sch_id):
|
||||
"""尝试多个 URL 模式检查收费数据"""
|
||||
results = {}
|
||||
|
||||
# 模式1:schoolInfo 收费项目页(mindex-7)
|
||||
# 模式2:listBulletin 公示栏
|
||||
# 模式3:直接查学校页是否有收费信息
|
||||
|
||||
patterns = [
|
||||
# (name, url_template)
|
||||
("schoolInfo_fee", f"https://gaokao.chsi.com.cn/sch/schoolInfo--schId-{sch_id},categoryId-87835,mindex-7.dhtml"),
|
||||
("listBulletin", f"https://gaokao.chsi.com.cn/sch/listBulletin--schId-{sch_id},categoryId-87835,mindex-12.dhtml"),
|
||||
]
|
||||
|
||||
for name, url in patterns:
|
||||
try:
|
||||
browser.goto(url, wait_until="load")
|
||||
time.sleep(1.0)
|
||||
|
||||
# 检查页面内容是否有实质数据
|
||||
body_text = browser.inner_text("body")
|
||||
|
||||
# 查找关键词
|
||||
has_fee_data = False
|
||||
fee_sample = ""
|
||||
|
||||
keywords = ['学', '费', '元/', '年', '收费', '标准', '公示']
|
||||
for kw in keywords:
|
||||
if kw in body_text:
|
||||
# 找包含关键词的句子
|
||||
for line in body_text.split('\n'):
|
||||
if kw in line and len(line.strip()) > 4:
|
||||
has_fee_data = True
|
||||
fee_sample = line.strip()[:100]
|
||||
break
|
||||
if has_fee_data:
|
||||
break
|
||||
|
||||
# 排除只有标题的情况
|
||||
if body_text.count('收费项目') == 1 and body_text.count('学') <= 3:
|
||||
has_fee_data = False
|
||||
|
||||
results[name] = {
|
||||
"has_data": has_fee_data,
|
||||
"url": url,
|
||||
"sample": fee_sample if has_fee_data else "",
|
||||
"page_len": len(body_text)
|
||||
}
|
||||
except Exception as e:
|
||||
results[name] = {"has_data": False, "error": str(e), "url": url}
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def run_scan(school_list, max_schools=None):
|
||||
"""执行扫描"""
|
||||
if max_schools:
|
||||
school_list = school_list[:max_schools]
|
||||
|
||||
# 启动 Playwright
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
except ImportError:
|
||||
print("[x] 需要安装: pip install playwright && playwright install chromium")
|
||||
sys.exit(1)
|
||||
|
||||
cache = load_schid_cache()
|
||||
all_results = {}
|
||||
stats = {"total": 0, "with_data": 0, "no_data": 0, "schid_error": 0}
|
||||
|
||||
print(f"扫描 {len(school_list)} 所学校...")
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(
|
||||
headless=True,
|
||||
args=["--no-sandbox", "--disable-dev-shm-usage"]
|
||||
)
|
||||
context = browser.new_context(viewport={"width": 1280, "height": 900})
|
||||
page = context.new_page()
|
||||
page.set_default_timeout(20000)
|
||||
|
||||
for i, (school_name, *_) in enumerate(school_list):
|
||||
if (i + 1) % 10 == 0:
|
||||
print(f" 进度 {i+1}/{len(school_list)}, 已找到数据: {stats['with_data']} 所")
|
||||
save_cache(cache)
|
||||
|
||||
stats["total"] += 1
|
||||
|
||||
# 获取 schId
|
||||
sch_id = get_schid_for_school(page, school_name, cache)
|
||||
|
||||
if not sch_id:
|
||||
all_results[school_name] = {"status": "schid_not_found"}
|
||||
stats["schid_error"] += 1
|
||||
continue
|
||||
|
||||
# 检查收费数据
|
||||
fee_results = check_fee_urls(page, sch_id)
|
||||
|
||||
has_any_data = any(r.get("has_data") for r in fee_results.values())
|
||||
|
||||
all_results[school_name] = {
|
||||
"schId": sch_id,
|
||||
"fee_results": fee_results,
|
||||
"has_fee_data": has_any_data
|
||||
}
|
||||
|
||||
if has_any_data:
|
||||
stats["with_data"] += 1
|
||||
print(f" ✓ {school_name} (schId={sch_id}): 有收费数据")
|
||||
else:
|
||||
stats["no_data"] += 1
|
||||
|
||||
time.sleep(DELAY + random.uniform(0, 0.3))
|
||||
|
||||
browser.close()
|
||||
|
||||
save_cache(cache)
|
||||
save_results(all_results)
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(f" 扫描完成:共 {stats['total']} 所")
|
||||
print(f" ✓ 有收费数据:{stats['with_data']} 所")
|
||||
print(f" ✗ 无收费数据:{stats['no_data']} 所")
|
||||
print(f" ! schId 未找到:{stats['schid_error']} 所")
|
||||
print(f" 覆盖率:{stats['with_data']/max(stats['total'],1)*100:.1f}%")
|
||||
print(f" 结果文件:{SCAN_RESULT}")
|
||||
print("=" * 60)
|
||||
|
||||
return all_results, stats
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 先做一个小规模测试(20所)
|
||||
print("先测试20所高校收费数据覆盖率...")
|
||||
schools = get_all_schools()
|
||||
run_scan(schools, max_schools=20)
|
||||
|
||||
print("\n然后扫描完整列表? (y/n)")
|
||||
answer = input("> ").strip().lower()
|
||||
if answer == 'y':
|
||||
run_scan(schools)
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# sync.sh - 从服务器拉取最新文件,本地 commit
|
||||
# 用法: bash ~/mc/gaokao-site/sync.sh
|
||||
|
||||
set -e
|
||||
cd ~/mc/gaokao-site
|
||||
|
||||
echo "=== 拉取服务器文件 ==="
|
||||
sshpass -p 'xue.2538' rsync -avz --exclude='gaokao_henan.db' --exclude='__pycache__' --exclude='*.pyc' --exclude='*.log' \
|
||||
root@192.144.179.11:/www/wwwroot/gaokao/ ~/mc/gaokao-site/
|
||||
|
||||
echo "=== 检查变更 ==="
|
||||
git status --short
|
||||
|
||||
echo "=== Git 变更 ==="
|
||||
if git diff --stat | grep -v '.gitignore' | grep -v '^$'; then
|
||||
echo "有变更,正在 commit..."
|
||||
git add -A
|
||||
git commit -m "sync: $(date '+%Y-%m-%d %H:%M')"
|
||||
git log --oneline -3
|
||||
else
|
||||
echo "无新变更"
|
||||
fi
|
||||
|
||||
echo "=== 完成 ==="
|
||||
echo "本地仓库: ~/mc/gaokao-site/"
|
||||
echo "git log: ~/mc/gaokao-site/.git/logs/"
|
||||
Reference in New Issue
Block a user