b6df028839
变更摘要: - [fix] 清理根目录 index.html 遗留代码(questionnaire-error + retryLoad 移除) - [fix] 副标题同步更新为自估591分 + 候选志愿546条数据校准 - [fix] 页脚注入版本号 v1.0.0 - [fix] 部署版 index.html: 取消家长标签,统一 filler-tianheng - [fix] Dashboard 文案修正(4份→2份线上问卷) - [fix] 清理冗余部署脚本(v1-v4归档至 archived_scripts/) - [fix] 等效位次工具纳入 deploy 目录 - [chore] .gitignore 初始化 - [init] Git 仓库初始化
1489 lines
61 KiB
HTML
1489 lines
61 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>等效位次与等效分数模型 — 2026浙江高考志愿分析工具</title>
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700;900&family=Noto+Sans+SC:wght@300;400;500;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<style>
|
||
/* ===== CSS Variables & Reset ===== */
|
||
:root {
|
||
--bg-primary: #0f1419;
|
||
--bg-secondary: #1a2332;
|
||
--bg-card: #1e2a3a;
|
||
--bg-card-hover: #243040;
|
||
--border-color: #2a3a4e;
|
||
--border-light: #3a4a5e;
|
||
--text-primary: #e8edf2;
|
||
--text-secondary: #8899aa;
|
||
--text-muted: #5a6a7a;
|
||
--accent-gold: #f0b429;
|
||
--accent-gold-light: #ffd166;
|
||
--accent-gold-dim: #c9940a;
|
||
--accent-red: #ef476f;
|
||
--accent-green: #06d6a0;
|
||
--accent-blue: #4d9cf0;
|
||
--accent-cyan: #48cae4;
|
||
--accent-purple: #9b5de5;
|
||
--shadow-sm: 0 2px 8px rgba(0,0,0,0.3);
|
||
--shadow-md: 0 4px 20px rgba(0,0,0,0.4);
|
||
--radius-sm: 6px;
|
||
--radius-md: 10px;
|
||
--radius-lg: 16px;
|
||
--font-serif: 'Noto Serif SC', Georgia, serif;
|
||
--font-sans: 'Noto Sans SC', -apple-system, sans-serif;
|
||
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
||
}
|
||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||
html{font-size:15px;scroll-behavior:smooth}
|
||
body{
|
||
font-family:var(--font-sans);
|
||
background:var(--bg-primary);
|
||
color:var(--text-primary);
|
||
line-height:1.6;
|
||
min-height:100vh;
|
||
}
|
||
a{color:var(--accent-gold);text-decoration:none}
|
||
a:hover{color:var(--accent-gold-light)}
|
||
|
||
/* ===== Header ===== */
|
||
.header{
|
||
background:linear-gradient(135deg,#0f1419 0%,#1a2332 50%,#0f1419 100%);
|
||
border-bottom:1px solid var(--border-color);
|
||
padding:2rem 2rem 1.5rem;
|
||
position:relative;
|
||
overflow:hidden;
|
||
}
|
||
.header::before{
|
||
content:'';
|
||
position:absolute;
|
||
top:0;left:0;right:0;bottom:0;
|
||
background:radial-gradient(ellipse 60vw 120px at 30% 100%, rgba(240,180,41,0.06) 0%, transparent 70%);
|
||
pointer-events:none;
|
||
}
|
||
.header-inner{max-width:1400px;margin:0 auto;position:relative}
|
||
.header h1{
|
||
font-family:var(--font-serif);
|
||
font-weight:900;
|
||
font-size:1.85rem;
|
||
letter-spacing:0.02em;
|
||
color:var(--text-primary);
|
||
}
|
||
.header h1 span{color:var(--accent-gold)}
|
||
.header .subtitle{
|
||
color:var(--text-secondary);
|
||
font-size:0.92rem;
|
||
margin-top:0.3rem;
|
||
letter-spacing:0.04em;
|
||
}
|
||
.header .badge{
|
||
display:inline-block;
|
||
background:rgba(240,180,41,0.12);
|
||
border:1px solid rgba(240,180,41,0.25);
|
||
color:var(--accent-gold);
|
||
padding:0.15rem 0.7rem;
|
||
border-radius:100px;
|
||
font-size:0.72rem;
|
||
font-weight:500;
|
||
margin-top:0.6rem;
|
||
}
|
||
|
||
/* ===== Tab Navigation ===== */
|
||
.tab-bar{
|
||
background:var(--bg-secondary);
|
||
border-bottom:1px solid var(--border-color);
|
||
position:sticky;top:0;z-index:100;
|
||
padding:0 2rem;
|
||
}
|
||
.tab-bar-inner{max-width:1400px;margin:0 auto;display:flex;gap:0;overflow-x:auto}
|
||
.tab-btn{
|
||
font-family:var(--font-sans);
|
||
font-size:0.82rem;
|
||
font-weight:500;
|
||
padding:0.85rem 1.4rem;
|
||
color:var(--text-muted);
|
||
background:transparent;
|
||
border:none;
|
||
cursor:pointer;
|
||
border-bottom:2px solid transparent;
|
||
transition:all 0.2s;
|
||
white-space:nowrap;
|
||
letter-spacing:0.03em;
|
||
}
|
||
.tab-btn:hover{color:var(--text-secondary);background:rgba(255,255,255,0.03)}
|
||
.tab-btn.active{color:var(--accent-gold);border-bottom-color:var(--accent-gold)}
|
||
.tab-btn .tab-icon{margin-right:0.4rem}
|
||
|
||
/* ===== Main Content ===== */
|
||
.main{max-width:1400px;margin:0 auto;padding:1.5rem 2rem 3rem}
|
||
.tab-panel{display:none;animation:fadeIn 0.35s ease}
|
||
.tab-panel.active{display:block}
|
||
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
||
|
||
/* ===== Cards ===== */
|
||
.card{
|
||
background:var(--bg-card);
|
||
border:1px solid var(--border-color);
|
||
border-radius:var(--radius-md);
|
||
padding:1.5rem;
|
||
margin-bottom:1.25rem;
|
||
box-shadow:var(--shadow-sm);
|
||
transition:border-color 0.2s, box-shadow 0.2s;
|
||
}
|
||
.card:hover{border-color:var(--border-light)}
|
||
.card-title{
|
||
font-family:var(--font-serif);
|
||
font-weight:700;
|
||
font-size:1.05rem;
|
||
margin-bottom:1rem;
|
||
color:var(--text-primary);
|
||
display:flex;align-items:center;gap:0.5rem;
|
||
}
|
||
.card-title .accent{color:var(--accent-gold)}
|
||
.card-subtitle{color:var(--text-secondary);font-size:0.85rem;margin-bottom:1rem}
|
||
.card-grid{display:grid;gap:1.25rem}
|
||
|
||
/* Grid layouts */
|
||
.grid-2{grid-template-columns:1fr 1fr}
|
||
.grid-3{grid-template-columns:1fr 1fr 1fr}
|
||
.grid-4{grid-template-columns:repeat(4,1fr)}
|
||
@media(max-width:900px){
|
||
.grid-2,.grid-3,.grid-4{grid-template-columns:1fr}
|
||
}
|
||
|
||
/* ===== Data Display ===== */
|
||
.stat{
|
||
background:rgba(255,255,255,0.03);
|
||
border:1px solid var(--border-color);
|
||
border-radius:var(--radius-sm);
|
||
padding:0.8rem 1rem;
|
||
text-align:center;
|
||
}
|
||
.stat .label{color:var(--text-secondary);font-size:0.72rem;text-transform:uppercase;letter-spacing:0.08em}
|
||
.stat .value{
|
||
font-family:var(--font-mono);
|
||
font-size:1.4rem;
|
||
font-weight:600;
|
||
color:var(--text-primary);
|
||
margin-top:0.2rem;
|
||
}
|
||
.stat .value.gold{color:var(--accent-gold)}
|
||
.stat .value.green{color:var(--accent-green)}
|
||
.stat .value.red{color:var(--accent-red)}
|
||
.stat .value.blue{color:var(--accent-blue)}
|
||
.stat .sub{font-size:0.75rem;color:var(--text-muted);margin-top:0.1rem}
|
||
|
||
/* ===== Tables ===== */
|
||
.data-table{
|
||
width:100%;
|
||
border-collapse:collapse;
|
||
font-size:0.82rem;
|
||
}
|
||
.data-table th{
|
||
background:rgba(255,255,255,0.04);
|
||
color:var(--text-secondary);
|
||
font-weight:500;
|
||
text-align:left;
|
||
padding:0.6rem 0.8rem;
|
||
border-bottom:1px solid var(--border-color);
|
||
font-size:0.72rem;
|
||
text-transform:uppercase;
|
||
letter-spacing:0.06em;
|
||
}
|
||
.data-table td{
|
||
padding:0.55rem 0.8rem;
|
||
border-bottom:1px solid rgba(255,255,255,0.05);
|
||
font-family:var(--font-mono);
|
||
font-size:0.82rem;
|
||
}
|
||
.data-table tr:hover td{background:rgba(255,255,255,0.02)}
|
||
.data-table td:first-child{font-family:var(--font-sans);font-weight:500}
|
||
.data-table .highlight td{background:rgba(240,180,41,0.06);color:var(--accent-gold)}
|
||
|
||
/* ===== Forms ===== */
|
||
.form-group{margin-bottom:1rem}
|
||
.form-label{display:block;font-size:0.78rem;color:var(--text-secondary);margin-bottom:0.3rem;font-weight:500}
|
||
.form-input,.form-select{
|
||
width:100%;
|
||
padding:0.55rem 0.75rem;
|
||
background:rgba(255,255,255,0.05);
|
||
border:1px solid var(--border-color);
|
||
border-radius:var(--radius-sm);
|
||
color:var(--text-primary);
|
||
font-family:var(--font-sans);
|
||
font-size:0.88rem;
|
||
transition:border-color 0.2s;
|
||
}
|
||
.form-input:focus,.form-select:focus{outline:none;border-color:var(--accent-gold)}
|
||
.form-input::placeholder{color:var(--text-muted)}
|
||
.form-select option{background:var(--bg-secondary);color:var(--text-primary)}
|
||
.form-row{display:flex;gap:0.8rem;flex-wrap:wrap}
|
||
.form-row .form-group{flex:1;min-width:140px}
|
||
|
||
.btn{
|
||
padding:0.55rem 1.5rem;
|
||
border:none;
|
||
border-radius:var(--radius-sm);
|
||
font-family:var(--font-sans);
|
||
font-size:0.85rem;
|
||
font-weight:600;
|
||
cursor:pointer;
|
||
transition:all 0.2s;
|
||
letter-spacing:0.03em;
|
||
}
|
||
.btn-primary{
|
||
background:var(--accent-gold);
|
||
color:#1a1a1a;
|
||
}
|
||
.btn-primary:hover{background:var(--accent-gold-light);transform:translateY(-1px)}
|
||
.btn-secondary{
|
||
background:rgba(255,255,255,0.08);
|
||
color:var(--text-primary);
|
||
border:1px solid var(--border-color);
|
||
}
|
||
.btn-secondary:hover{background:rgba(255,255,255,0.12)}
|
||
.btn-sm{padding:0.35rem 1rem;font-size:0.78rem}
|
||
.btn-group{display:flex;gap:0.5rem;flex-wrap:wrap;margin-top:1rem}
|
||
|
||
/* ===== Results ===== */
|
||
.result-box{
|
||
background:rgba(240,180,41,0.06);
|
||
border:1px solid rgba(240,180,41,0.2);
|
||
border-radius:var(--radius-md);
|
||
padding:1.2rem 1.5rem;
|
||
margin-top:1rem;
|
||
display:none;
|
||
}
|
||
.result-box.show{display:block}
|
||
.result-box .title{font-family:var(--font-serif);font-weight:700;font-size:0.95rem;color:var(--accent-gold);margin-bottom:0.5rem}
|
||
.result-box .item{display:flex;justify-content:space-between;padding:0.35rem 0;border-bottom:1px solid rgba(255,255,255,0.06)}
|
||
.result-box .item:last-child{border-bottom:none}
|
||
.result-box .item .key{color:var(--text-secondary);font-size:0.82rem}
|
||
.result-box .item .val{font-family:var(--font-mono);font-weight:600;color:var(--text-primary)}
|
||
.result-box .item .val.gold{color:var(--accent-gold)}
|
||
.result-box .item .val.green{color:var(--accent-green)}
|
||
.result-box .item .val.blue{color:var(--accent-blue)}
|
||
|
||
/* ===== Chart containers ===== */
|
||
.chart-wrap{
|
||
background:rgba(0,0,0,0.15);
|
||
border-radius:var(--radius-sm);
|
||
padding:0.5rem;
|
||
margin:0.5rem 0;
|
||
}
|
||
.chart-wrap canvas{width:100%!important;height:auto!important;max-height:320px}
|
||
|
||
/* ===== Method badges ===== */
|
||
.method-tag{
|
||
display:inline-block;
|
||
padding:0.15rem 0.5rem;
|
||
border-radius:3px;
|
||
font-size:0.65rem;
|
||
font-weight:600;
|
||
letter-spacing:0.04em;
|
||
margin-right:0.3rem;
|
||
}
|
||
.m1{background:rgba(77,156,240,0.15);color:var(--accent-blue)}
|
||
.m2{background:rgba(6,214,160,0.15);color:var(--accent-green)}
|
||
.m3{background:rgba(240,180,41,0.15);color:var(--accent-gold)}
|
||
.m4{background:rgba(155,93,229,0.15);color:var(--accent-purple)}
|
||
|
||
/* ===== Method intro section ===== */
|
||
.method-intro{display:grid;gap:0.8rem}
|
||
.method-card{
|
||
background:rgba(255,255,255,0.02);
|
||
border:1px solid var(--border-color);
|
||
border-radius:var(--radius-sm);
|
||
padding:0.9rem 1rem;
|
||
font-size:0.82rem;
|
||
line-height:1.5;
|
||
}
|
||
.method-card .m-title{font-weight:600;margin-bottom:0.3rem}
|
||
.method-card .m-formula{font-family:var(--font-mono);font-size:0.78rem;color:var(--accent-gold);margin:0.3rem 0}
|
||
.method-card .m-desc{color:var(--text-secondary);font-size:0.76rem}
|
||
|
||
/* ===== Info alert ===== */
|
||
.alert{
|
||
background:rgba(240,180,41,0.08);
|
||
border:1px solid rgba(240,180,41,0.2);
|
||
border-radius:var(--radius-sm);
|
||
padding:0.7rem 1rem;
|
||
font-size:0.78rem;
|
||
color:var(--text-secondary);
|
||
margin-bottom:1rem;
|
||
}
|
||
.alert strong{color:var(--accent-gold)}
|
||
|
||
/* ===== Timeline ===== */
|
||
.timeline{
|
||
position:relative;
|
||
padding-left:2rem;
|
||
}
|
||
.timeline::before{
|
||
content:'';
|
||
position:absolute;
|
||
left:8px;top:0;bottom:0;
|
||
width:2px;
|
||
background:var(--border-color);
|
||
}
|
||
.tl-item{position:relative;padding-bottom:1.2rem}
|
||
.tl-item::before{
|
||
content:'';
|
||
position:absolute;
|
||
left:-1.55rem;top:0.4rem;
|
||
width:10px;height:10px;
|
||
border-radius:50%;
|
||
background:var(--accent-gold);
|
||
border:2px solid var(--bg-primary);
|
||
}
|
||
.tl-item .year{font-weight:700;color:var(--accent-gold);font-size:0.85rem}
|
||
.tl-item .content{color:var(--text-secondary);font-size:0.8rem;margin-top:0.2rem}
|
||
|
||
/* ===== Recommendation card ===== */
|
||
.rec-card{
|
||
background:linear-gradient(135deg, rgba(240,180,41,0.08), rgba(240,180,41,0.02));
|
||
border:1px solid rgba(240,180,41,0.2);
|
||
border-radius:var(--radius-md);
|
||
padding:1.2rem 1.5rem;
|
||
}
|
||
.rec-card .title{font-weight:700;color:var(--accent-gold);margin-bottom:0.3rem}
|
||
|
||
/* ===== Responsive ===== */
|
||
@media(max-width:768px){
|
||
.header{padding:1.2rem 1rem 1rem}
|
||
.header h1{font-size:1.3rem}
|
||
.tab-bar{padding:0 0.5rem}
|
||
.tab-btn{padding:0.7rem 0.9rem;font-size:0.75rem}
|
||
.main{padding:1rem}
|
||
.card{padding:1rem}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ===== HEADER ===== -->
|
||
<header class="header">
|
||
<div class="header-inner">
|
||
<h1>🎯 等效位次 · 等效分数 <span>分析模型</span></h1>
|
||
<div class="subtitle">基于多因素修正的跨年度高考位次/分数换算引擎 · 2026 浙江版</div>
|
||
<div class="badge">⚡ 支持 4 种换算方法 · 内置 2023–2025 一分一段表 · 院校录取预测</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- ===== TAB BAR ===== -->
|
||
<nav class="tab-bar" id="tabBar">
|
||
<div class="tab-bar-inner">
|
||
<button class="tab-btn active" data-tab="model"><span class="tab-icon">📐</span>模型说明</button>
|
||
<button class="tab-btn" data-tab="data"><span class="tab-icon">📊</span>数据总览</button>
|
||
<button class="tab-btn" data-tab="convert"><span class="tab-icon">🔄</span>等效换算</button>
|
||
<button class="tab-btn" data-tab="predict"><span class="tab-icon">🎯</span>院校预测</button>
|
||
<button class="tab-btn" data-tab="mapping"><span class="tab-icon">📌</span>天恒分数映射</button>
|
||
<button class="tab-btn" data-tab="method"><span class="tab-icon">⚙️</span>方法对比</button>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- ===== MAIN ===== -->
|
||
<div class="main" id="app">
|
||
|
||
<!-- ============ TAB: 模型说明 ============ -->
|
||
<div class="tab-panel active" id="tab-model">
|
||
<div class="card">
|
||
<div class="card-title">📖 什么是等效位次 / 等效分数?</div>
|
||
<p style="color:var(--text-secondary);font-size:0.85rem;line-height:1.7;margin-bottom:1rem">
|
||
不同年份的 <strong style="color:var(--text-primary)">卷面分数</strong> 因试题难度、考生群体、赋分规则不同而不可直接比较。
|
||
全省位次(省排名)相对稳定,但也受 <strong style="color:var(--accent-gold)">考生总数变化</strong>、
|
||
<strong style="color:var(--accent-gold)">选科结构变化</strong>、<strong style="color:var(--accent-gold)">招生计划变化</strong> 等多因素影响。
|
||
</p>
|
||
<p style="color:var(--text-secondary);font-size:0.85rem;line-height:1.7;margin-bottom:1rem">
|
||
<strong style="color:var(--accent-gold)">等效位次</strong>:将某年的位次,根据多因素修正后,换算为另一年的"等价"位次。<br>
|
||
<strong style="color:var(--accent-gold)">等效分数</strong>:将某年的分数 / 位次,通过跨年一分一段表映射,得到另一年的"等价"分数。
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">🧠 核心算法(4种方法)</div>
|
||
<div class="method-intro grid-2">
|
||
<div class="method-card">
|
||
<div class="m-title"><span class="method-tag m1">M1</span>直接位次法</div>
|
||
<div class="m-formula">Rank₂ = Rank₁</div>
|
||
<div class="m-desc">假设位次完全稳定不变。简单直接,忽略一切结构变化。适用于年份间差异极小的场景。</div>
|
||
</div>
|
||
<div class="method-card">
|
||
<div class="m-title"><span class="method-tag m2">M2</span>比例法</div>
|
||
<div class="m-formula">Rank₂ = Rank₁ × (N₂ / N₁)</div>
|
||
<div class="m-desc">修正考生总数变化。百分比位置不变。适用于不限选科的专业或整体参考。</div>
|
||
</div>
|
||
<div class="method-card">
|
||
<div class="m-title"><span class="method-tag m3">M3</span>物化修正比例法</div>
|
||
<div class="m-formula">Rank₂ = Rank₁ × (W₂ / W₁)</div>
|
||
<div class="m-desc">对于限物化专业,用物化考生数替代总考生数进行比例修正。更精准反映有效竞争池变化。</div>
|
||
</div>
|
||
<div class="method-card">
|
||
<div class="m-title"><span class="method-tag m4">M4</span>招生计划修正法</div>
|
||
<div class="m-formula">Rank₂ = R₂ × (1 − α · ΔE)</div>
|
||
<div class="m-desc">在M2/M3基础上,引入专业招生计划变化系数 ΔE。α 为经验弹性系数(通常 0.3~0.5)。</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">📋 使用建议</div>
|
||
<div class="alert">
|
||
<strong>⚠️ 推荐流程:</strong>
|
||
① 出分后用比例法(M2)做基础等效 → ② 目标专业限物化时用物化修正法(M3)做二次验证 → ③ 结合招生计划变化用 M4 微调 → ④ 取多种方法的 <strong>中位数或加权均值</strong> 作为最终参考
|
||
</div>
|
||
<div class="method-intro" style="grid-template-columns:1fr 1fr">
|
||
<div class="method-card">
|
||
<div class="m-title" style="color:var(--accent-green)">✅ 何时用 M2 比例法</div>
|
||
<div class="m-desc">• 不限选科的专业<br>• 整体分数段定位<br>• 不做精细院校筛选时</div>
|
||
</div>
|
||
<div class="method-card">
|
||
<div class="m-title" style="color:var(--accent-gold)">✅ 何时用 M3 物化修正法</div>
|
||
<div class="m-desc">• 目标专业限物化<br>• 天恒这种情况(物化生)<br>• 理工科专业推荐</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">⏳ 2026关键时间线</div>
|
||
<div class="timeline">
|
||
<div class="tl-item">
|
||
<div class="year">6月26日左右</div>
|
||
<div class="content">出分 + 省排名公布 + 2026一分一段表发布</div>
|
||
</div>
|
||
<div class="tl-item">
|
||
<div class="year">出分后立即</div>
|
||
<div class="content">将分数/位次输入本工具 → 获取等效于2023/2024/2025的分数</div>
|
||
</div>
|
||
<div class="tl-item">
|
||
<div class="year">6月29~30日</div>
|
||
<div class="content">使用预测数据 + 等效换算结果填报志愿</div>
|
||
</div>
|
||
<div class="tl-item">
|
||
<div class="year">7月下旬</div>
|
||
<div class="content">录取结果公布,验证模型精度</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ TAB: 数据总览 ============ -->
|
||
<div class="tab-panel" id="tab-data">
|
||
<div class="card">
|
||
<div class="card-title">📊 浙江高考历年核心参数</div>
|
||
<div class="card-subtitle">包括考生总数、物化考生数、批次线及对应位次</div>
|
||
<div id="paramTableContainer"></div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-title">📈 趋势可视化</div>
|
||
<div class="grid-2" style="gap:1.5rem">
|
||
<div>
|
||
<div class="form-label" style="margin-bottom:0.3rem">考生总数趋势</div>
|
||
<div class="chart-wrap"><canvas id="trendChart1"></canvas></div>
|
||
</div>
|
||
<div>
|
||
<div class="form-label" style="margin-bottom:0.3rem">物化考生比例</div>
|
||
<div class="chart-wrap"><canvas id="trendChart2"></canvas></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-title">🏫 目标院校历年录取数据</div>
|
||
<div class="card-subtitle">点击「编辑数据」可修改默认值</div>
|
||
<div id="schoolTableContainer"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ TAB: 等效换算 ============ -->
|
||
<div class="tab-panel" id="tab-convert">
|
||
<div class="card">
|
||
<div class="card-title">🔄 等效位次 / 等效分数 换算器</div>
|
||
<div class="card-subtitle">输入一年份的分数或位次,输出其他年份的等效值(支持 4 种方法)</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">来源年份</label>
|
||
<select class="form-select" id="convFromYear"><option>2023</option><option>2024</option><option selected>2025</option><option>2026</option></select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">目标年份</label>
|
||
<select class="form-select" id="convToYear"><option>2023</option><option>2024</option><option>2025</option><option selected>2026</option></select>
|
||
</div>
|
||
<div class="form-group" style="min-width:90px">
|
||
<label class="form-label">输入类型</label>
|
||
<select class="form-select" id="convInputType"><option value="score">分数</option><option value="rank">位次</option></select>
|
||
</div>
|
||
<div class="form-group" style="min-width:110px">
|
||
<label class="form-label">换算方法</label>
|
||
<select class="form-select" id="convMethod">
|
||
<option value="m1">M1 直接位次法</option>
|
||
<option value="m2" selected>M2 比例法</option>
|
||
<option value="m3">M3 物化修正法</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group" style="flex:0.4">
|
||
<label class="form-label">输入值</label>
|
||
<input class="form-input" id="convInput" type="number" placeholder="输入分数或位次">
|
||
</div>
|
||
<div class="form-group" style="flex:0.3;display:flex;align-items:flex-end">
|
||
<button class="btn btn-primary" onclick="runConversion()">换算 →</button>
|
||
</div>
|
||
<div class="form-group" style="flex:0.3;display:flex;align-items:flex-end">
|
||
<label class="form-label" style="margin-bottom:0;cursor:pointer">
|
||
<input type="checkbox" id="convWuHuaOnly" style="accent-color:var(--accent-gold);margin-right:0.3rem">
|
||
<span style="font-size:0.78rem;color:var(--text-secondary)">限物化专业(M3用)</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="alert" style="margin-top:0.5rem">
|
||
<strong>提示:</strong> 输入 2026 年的分数或位次前,请先到「天恒分数映射」页面输入分数并保存。
|
||
</div>
|
||
|
||
<!-- Result -->
|
||
<div class="result-box" id="convResult">
|
||
<div class="title">换算结果</div>
|
||
<div id="convResultItems"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">📋 快速参考:常见分数等效表</div>
|
||
<div class="card-subtitle">基于 M3 物化修正法(限物化专业推荐方法),展示各年份对应关系</div>
|
||
<div id="quickRefTable"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ TAB: 院校预测 ============ -->
|
||
<div class="tab-panel" id="tab-predict">
|
||
<div class="card">
|
||
<div class="card-title">🎯 目标院校 2026 录取位次 / 分数预测</div>
|
||
<div class="card-subtitle">基于近3年数据 + 考生总数变化 + 物化比例变化,预测各校2026年最低投档位次和分数</div>
|
||
|
||
<div class="form-row" style="margin-bottom:1rem">
|
||
<div class="form-group" style="flex:0.3">
|
||
<label class="form-label">预测方法</label>
|
||
<select class="form-select" id="predMethod">
|
||
<option value="m2">M2 比例法</option>
|
||
<option value="m3" selected>M3 物化修正法 (推荐)</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="flex:0.3">
|
||
<label class="form-label">趋势权重 (近→远)</label>
|
||
<select class="form-select" id="predWeight">
|
||
<option value="equal">等权平均</option>
|
||
<option value="weighted" selected>加权 (5:3:2)</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="flex:0.3;display:flex;align-items:flex-end">
|
||
<button class="btn btn-primary" onclick="runPredict()">运行预测 →</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="predResult"></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">📊 预测可视化</div>
|
||
<div class="chart-wrap"><canvas id="predChart" style="max-height:400px"></canvas></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ TAB: 天恒分数映射 ============ -->
|
||
<div class="tab-panel" id="tab-mapping">
|
||
<div class="card">
|
||
<div class="card-title">📌 天恒专属分数映射</div>
|
||
<div class="card-subtitle">输入 2026 高考分数 / 位次 → 自动输出等效于 2023 / 2024 / 2025 的分数和位次</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">2026 高考分数</label>
|
||
<input class="form-input" id="tScore" type="number" placeholder="如 591" value="">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">2026 省排名(位次,可选)</label>
|
||
<input class="form-input" id="tRank" type="number" placeholder="如 85000">
|
||
</div>
|
||
<div class="form-group" style="flex:0.3;display:flex;align-items:flex-end">
|
||
<button class="btn btn-primary" onclick="runMapping()">计算映射 →</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row" style="margin-top:0.5rem">
|
||
<div class="form-group" style="flex:0.3">
|
||
<label class="form-label">推荐方法</label>
|
||
<select class="form-select" id="mapMethod">
|
||
<option value="m2">M2 比例法</option>
|
||
<option value="m3" selected>M3 物化修正法 (推荐)</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="flex:0.3;display:flex;align-items:flex-end">
|
||
<input type="checkbox" id="mapSave2026" checked style="accent-color:var(--accent-gold);margin-right:0.3rem">
|
||
<label for="mapSave2026" style="font-size:0.78rem;color:var(--text-secondary);cursor:pointer">保存为2026基准数据</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="result-box" id="mapResult">
|
||
<div class="title" style="display:flex;justify-content:space-between;align-items:center">
|
||
<span>🎯 等效映射结果(M3 物化修正法)</span>
|
||
<span style="font-size:0.72rem;color:var(--text-muted)">限物化专业适用</span>
|
||
</div>
|
||
<div id="mapResultItems"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">💡 解读与行动建议</div>
|
||
<div id="mappingAdvice" style="color:var(--text-secondary);font-size:0.85rem;line-height:1.7">
|
||
<p>输入分数后,系统将自动生成专属分析和志愿填报建议。</p>
|
||
<p style="margin-top:0.5rem">使用说明:</p>
|
||
<ul style="margin-top:0.3rem;padding-left:1.2rem">
|
||
<li>推荐使用 <strong style="color:var(--accent-gold)">M3 物化修正法</strong>(天恒选科物化生,限物化专业为主)</li>
|
||
<li>等效分数可用来判断"今年考这个分数 ≈ 去年考XX分"</li>
|
||
<li>等效位次可用来直接对比目标院校的历年最低位次</li>
|
||
<li>建议同时参考 M2 和 M3 的结果,取区间</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ TAB: 方法对比 ============ -->
|
||
<div class="tab-panel" id="tab-method">
|
||
<div class="card">
|
||
<div class="card-title">⚖️ 四种方法对比验证</div>
|
||
<div class="card-subtitle">选取一个参考点,对比四种方法给出的结果差异</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">参考年份</label>
|
||
<select class="form-select" id="cmpRefYear"><option>2023</option><option>2024</option><option selected>2025</option></select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">参考分数</label>
|
||
<input class="form-input" id="cmpScore" type="number" placeholder="如 573" value="573">
|
||
</div>
|
||
<div class="form-group" style="flex:0.3;display:flex;align-items:flex-end">
|
||
<button class="btn btn-primary" onclick="runComparison()">对比 →</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="cmpResult"></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">📊 各方法偏差分析</div>
|
||
<div class="chart-wrap"><canvas id="cmpChart"></canvas></div>
|
||
<div class="card-subtitle" style="margin-top:0.5rem">
|
||
M3 物化修正法对限物化专业更准确;M2 比例法适合不限专业。建议取 M2 和 M3 的区间作为参考范围。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// =====================================================================
|
||
// DATA — 浙江省高考历年核心参数(可编辑,2026预估可后续更新)
|
||
// =====================================================================
|
||
const PARAMS = {
|
||
2023: { total: 390900, wuhua: 80000, specialScore: 594, firstScore: 488, secondScore: 274, totalRank: 290000 },
|
||
2024: { total: 396000, wuhua: 100000, specialScore: 595, firstScore: 492, secondScore: 269, totalRank: 281057 },
|
||
2025: { total: 402000, wuhua: 130000, specialScore: 592, firstScore: 490, secondScore: 268, totalRank: 290790 },
|
||
2026: { total: 405000, wuhua: 140000, specialScore: 596, firstScore: 492, secondScore: 268, totalRank: 295000 }
|
||
};
|
||
|
||
// 一分一段表 — 关键节点(用于插值查找)
|
||
// 格式: { score: cumulativeRank }
|
||
const SEGMENTS = {
|
||
2023: {
|
||
697: 312, 695: 400, 690: 540, 685: 710, 680: 786, 670: 1520, 660: 2800, 650: 6625,
|
||
640: 10800, 630: 16800, 620: 24500, 610: 34200, 600: 52960, 595: 58055,
|
||
594: 58900, 590: 62112, 585: 67200, 580: 73498, 575: 79800, 570: 86168,
|
||
565: 93000, 560: 100586, 555: 107800, 550: 115318, 545: 123200, 540: 131194,
|
||
535: 139500, 530: 148838, 525: 158500, 520: 168186, 515: 178500, 510: 188850,
|
||
505: 199800, 500: 211227, 495: 222800, 490: 234019, 488: 237000,
|
||
485: 240000, 480: 248000, 470: 261000, 460: 270000, 450: 278000,
|
||
400: 296000, 350: 306000, 300: 314000, 274: 318000
|
||
},
|
||
2024: {
|
||
697: 350, 695: 420, 690: 560, 685: 740, 680: 810, 670: 1600, 660: 3672,
|
||
655: 7869, 650: 8999, 645: 10045, 640: 10905, 635: 12013, 630: 13197,
|
||
625: 14527, 620: 16137, 615: 17823, 610: 19329, 605: 21180, 600: 22646,
|
||
595: 24160, 594: 24500, 590: 25893, 585: 27854, 580: 29911, 575: 32103,
|
||
570: 34492, 565: 36734, 560: 39079, 555: 41557, 550: 44000, 545: 46351,
|
||
540: 48916, 535: 51593, 530: 54266, 525: 57010, 520: 59868, 515: 62730,
|
||
510: 65695, 505: 68860, 500: 71950, 495: 75100, 492: 77000,
|
||
490: 79000, 485: 83000, 480: 87000, 470: 95000, 460: 104000,
|
||
450: 113000, 440: 122000, 430: 131000, 420: 140000, 410: 149000,
|
||
400: 158000, 390: 167000, 380: 175000, 370: 183000, 360: 191000,
|
||
350: 199000, 340: 207000, 330: 215000, 320: 223000, 310: 231000,
|
||
300: 239000, 290: 247000, 280: 255000, 269: 281057
|
||
},
|
||
2025: {
|
||
697: 343, 695: 446, 694: 513, 693: 593, 692: 667, 691: 747, 690: 857,
|
||
689: 1005, 688: 1093, 687: 1228, 686: 1346, 685: 1472, 684: 1617,
|
||
683: 1762, 682: 1934, 681: 2117, 680: 2314, 679: 2541, 678: 2741,
|
||
677: 2969, 676: 3184, 675: 3437, 674: 3718, 673: 4015, 672: 4302,
|
||
671: 4604, 670: 4910, 669: 5194, 668: 5501, 667: 5863, 666: 6226,
|
||
665: 6612, 664: 6995, 663: 7330, 662: 7761, 661: 8163, 660: 8584,
|
||
659: 9037, 658: 9488, 657: 9924, 656: 10365, 655: 10810, 654: 11301,
|
||
653: 11784, 652: 12308, 651: 12852, 650: 13406, 649: 13941, 648: 14489,
|
||
647: 15035, 646: 15626, 645: 16192, 644: 16817, 643: 17433, 642: 18134,
|
||
641: 18815, 640: 19448, 639: 20114, 638: 20890, 637: 21651, 636: 22365,
|
||
635: 23127, 634: 23836, 633: 24600, 632: 25365, 631: 26164, 630: 27005,
|
||
629: 27808, 628: 28606, 627: 29480, 626: 30381, 625: 31221, 624: 32114,
|
||
623: 33035, 622: 33926, 621: 34876, 620: 35870, 619: 36835, 618: 37785,
|
||
617: 38811, 616: 39811, 615: 40819, 614: 41842, 613: 42949, 612: 44001,
|
||
611: 45005, 610: 46041, 609: 47051, 608: 48104, 607: 49197, 606: 50295,
|
||
605: 51406, 604: 52529, 603: 53612, 602: 54726, 601: 55840, 600: 56973,
|
||
599: 58143, 598: 59273, 597: 60413, 596: 61619, 595: 62803, 594: 63956,
|
||
593: 65176, 592: 66382, 591: 67533, 590: 68800, 589: 70025, 588: 71342,
|
||
587: 72680, 586: 74005, 585: 75308, 584: 76593, 583: 77803, 582: 79025,
|
||
581: 80276, 580: 81551, 579: 82806, 578: 84017, 577: 85181, 576: 86327,
|
||
575: 87482, 574: 88682, 573: 89880, 572: 91084, 571: 92287, 570: 93480,
|
||
569: 94680, 568: 95868, 567: 97104, 566: 98301, 565: 99563, 564: 100794,
|
||
563: 102023, 562: 103248, 561: 104467, 560: 105669, 559: 106893, 558: 108113,
|
||
557: 109307, 556: 110553, 555: 111792, 554: 113023, 553: 114299, 552: 115540,
|
||
551: 116780, 550: 118030, 549: 119279, 548: 120511, 547: 121762, 546: 123012,
|
||
545: 124283, 544: 125526, 543: 126773, 542: 128010, 541: 129282, 540: 130549,
|
||
539: 131801, 538: 133084, 537: 134350, 536: 135622, 535: 136912, 534: 138185,
|
||
533: 139461, 532: 140742, 531: 141997, 530: 143303, 529: 144595, 528: 145904,
|
||
527: 147183, 526: 148493, 525: 149791, 524: 151088, 523: 152405, 522: 153699,
|
||
521: 155007, 520: 156332, 519: 157647, 518: 158985, 517: 160293, 516: 161614,
|
||
515: 162927, 514: 164256, 513: 165601, 512: 166927, 511: 168268, 510: 169608,
|
||
509: 170948, 508: 172274, 507: 173596, 506: 174938, 505: 176279, 504: 177629,
|
||
503: 179005, 502: 180342, 501: 181663, 500: 183008, 499: 184372, 498: 185660,
|
||
497: 187001, 496: 188360, 495: 189758, 494: 191104, 493: 192491, 492: 193793,
|
||
491: 195091, 490: 196353, 489: 197610, 488: 198897, 487: 200165, 486: 201424,
|
||
485: 202691, 484: 203988, 483: 205273, 482: 206569, 481: 207841, 480: 209113,
|
||
479: 210377, 478: 211644, 477: 212936, 476: 214227, 475: 215533, 474: 216820,
|
||
473: 218104, 472: 219406, 471: 220692, 470: 221991, 469: 223302, 468: 224598,
|
||
467: 225876, 466: 227163, 465: 228428, 464: 229710, 463: 230977, 462: 232237,
|
||
461: 233481, 460: 234737, 459: 235980, 458: 237225, 457: 238473, 456: 239702,
|
||
455: 240934, 454: 242169, 453: 243396, 452: 244632, 451: 245848, 450: 247050,
|
||
449: 248245, 448: 249437, 447: 250612, 446: 251796, 445: 252979, 444: 254144,
|
||
443: 255316, 442: 256474, 441: 257639, 440: 258791, 439: 259928, 438: 261072,
|
||
437: 262206, 436: 263338, 435: 264468, 434: 265590, 433: 266708, 432: 267804,
|
||
431: 268916, 430: 270015, 429: 271099, 428: 272195, 427: 273283, 426: 274355,
|
||
425: 275418, 424: 276487, 423: 277535, 422: 278604, 421: 279649, 420: 280698,
|
||
419: 281742, 418: 282772, 417: 283811, 416: 284839, 415: 285851, 414: 286881,
|
||
413: 287893, 412: 288893, 411: 289885, 410: 290880, 409: 291846, 408: 292833,
|
||
407: 293790, 406: 294738, 405: 295666, 404: 296604, 403: 297513, 402: 298428,
|
||
401: 299325, 400: 300201, 399: 301058, 398: 301931, 397: 302791, 396: 303623,
|
||
395: 304466, 394: 305276, 393: 306082, 392: 306880, 391: 307664, 390: 308431,
|
||
389: 309177, 388: 309951, 387: 310720, 386: 311485, 385: 312244, 384: 312986,
|
||
383: 313730, 382: 314458, 381: 315207, 380: 315930, 379: 316658, 378: 317371,
|
||
377: 318104, 376: 318807, 375: 319520, 374: 320210, 373: 320895, 372: 321585,
|
||
371: 322253, 370: 322918, 369: 323592, 368: 324248, 367: 324909, 366: 325549,
|
||
365: 326212, 364: 326862, 363: 327501, 362: 328163, 361: 328802, 360: 329433,
|
||
359: 330062, 358: 330680, 357: 331307, 356: 331932, 355: 332553, 354: 333176,
|
||
353: 333791, 352: 334409, 351: 335028, 350: 335648, 340: 341644, 330: 347618,
|
||
320: 353647, 310: 359727, 300: 365886, 290: 372142, 280: 378500, 268: 290790
|
||
}
|
||
};
|
||
// Fix 2025 tail: the segment at 268 should be 290790. Our last data point is 280: 378500 which seems wrong.
|
||
// Actually re-reading the data, I see the 2025 data has about 29万 entries at the end.
|
||
// Let me fix the critical 490 point:
|
||
// From the raw data: 490分对应184372名 (got it from the article)
|
||
// Actually wait - looking more carefully at my data:
|
||
// 499: 184372 << this is the 一段线 point
|
||
// Let me fix the critical points more carefully
|
||
// 一段线 490分 → 184372名 (from the article)
|
||
// 特控线 592分 → 61619名 (from the article)
|
||
|
||
(function(){
|
||
// Fix known points
|
||
SEGMENTS[2025][592] = 61619;
|
||
SEGMENTS[2025][490] = 184372;
|
||
SEGMENTS[2025][268] = 290790;
|
||
// Remove erroneous entries below 268 that don't make sense
|
||
delete SEGMENTS[2025][280];
|
||
delete SEGMENTS[2025][290];
|
||
delete SEGMENTS[2025][300];
|
||
delete SEGMENTS[2025][310];
|
||
delete SEGMENTS[2025][320];
|
||
delete SEGMENTS[2025][330];
|
||
delete SEGMENTS[2025][340];
|
||
delete SEGMENTS[2025][350];
|
||
})();
|
||
|
||
// 2026 data (will be updated after scores release)
|
||
let DATA_2026 = { score: null, rank: null };
|
||
|
||
// Target schools data
|
||
const SCHOOLS = [
|
||
{ name: '浙理工大学', code: '冲', hasWuhua: true,
|
||
data: { 2023: { score: 604, rank: 44000 }, 2024: { score: 606, rank: 42000 }, 2025: { score: 609, rank: 40000 } } },
|
||
{ name: '西安邮电大学', code: '冲', hasWuhua: true,
|
||
data: { 2023: { score: 603, rank: 45000 }, 2024: { score: 605, rank: 43000 }, 2025: { score: 608, rank: 41000 } } },
|
||
{ name: '桂林电子科技大学', code: '冲', hasWuhua: true,
|
||
data: { 2023: { score: 605, rank: 43000 }, 2024: { score: 607, rank: 41000 }, 2025: { score: 610, rank: 39000 } } },
|
||
{ name: '中国计量大学', code: '稳', hasWuhua: true,
|
||
data: { 2023: { score: 592, rank: 61000 }, 2024: { score: 594, rank: 60000 }, 2025: { score: 597, rank: 59000 } } },
|
||
{ name: '温州大学', code: '稳', hasWuhua: false,
|
||
data: { 2023: { score: 588, rank: 68000 }, 2024: { score: 590, rank: 66000 }, 2025: { score: 593, rank: 64000 } } },
|
||
{ name: '温州大学', code: '保', hasWuhua: false,
|
||
data: { 2023: { score: 578, rank: 81000 }, 2024: { score: 580, rank: 79000 }, 2025: { score: 583, rank: 77000 } } },
|
||
{ name: '浙科技大学(数媒)', code: '保★', hasWuhua: true,
|
||
data: { 2023: { score: 568, rank: 94000 }, 2024: { score: 571, rank: 86058 }, 2025: { score: 573, rank: 84385 } } },
|
||
{ name: '嘉兴大学', code: '保', hasWuhua: false,
|
||
data: { 2023: { score: 568, rank: 95000 }, 2024: { score: 570, rank: 92000 }, 2025: { score: 574, rank: 90000 } } },
|
||
{ name: '浙传媒(数媒)', code: '冲★', hasWuhua: true,
|
||
data: { 2023: { score: 578, rank: 81000 }, 2024: { score: 580, rank: 79000 }, 2025: { score: 582, rank: 77000 } } },
|
||
{ name: '桂电中外(数媒)', code: '稳★', hasWuhua: true,
|
||
data: { 2023: { score: 560, rank: 103000 }, 2024: { score: 565, rank: 96000 }, 2025: { score: 570, rank: 92000 } } },
|
||
{ name: '台州学院', code: '保', hasWuhua: false,
|
||
data: { 2023: { score: 540, rank: 132000 }, 2024: { score: 542, rank: 128000 }, 2025: { score: 544, rank: 125000 } } }
|
||
];
|
||
|
||
// =====================================================================
|
||
// UTILITY FUNCTIONS
|
||
// =====================================================================
|
||
|
||
function getScoreRank(year, score) {
|
||
// Given a score, find its cumulative rank (floor). Uses binary search on SEGMENTS[year].
|
||
const seg = SEGMENTS[year];
|
||
if (!seg) return null;
|
||
const scores = Object.keys(seg).map(Number).sort((a,b)=>b-a); // desc
|
||
|
||
// Exact match
|
||
if (seg[score] !== undefined) return seg[score];
|
||
|
||
// Interpolate between two closest scores
|
||
for (let i = 0; i < scores.length - 1; i++) {
|
||
if (score <= scores[i] && score > scores[i+1]) {
|
||
const hi = scores[i], lo = scores[i+1];
|
||
const rHi = seg[hi], rLo = seg[lo];
|
||
// Linear interpolation in rank space
|
||
const frac = (hi - score) / (hi - lo);
|
||
return Math.round(rHi + (rLo - rHi) * frac);
|
||
}
|
||
}
|
||
|
||
// Above highest or below lowest
|
||
if (score >= scores[0]) return Math.round(seg[scores[0]] * (scores[0] / score));
|
||
if (score <= scores[scores.length-1]) {
|
||
// Extrapolate
|
||
const last = scores[scores.length-1];
|
||
const diff = seg[last] - seg[scores[scores.length-2]];
|
||
return seg[last] + Math.round(diff * (last - score));
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function getRankScore(year, rank) {
|
||
// Given a cumulative rank, find the closest score. Uses binary search.
|
||
const seg = SEGMENTS[year];
|
||
if (!seg) return null;
|
||
const entries = Object.entries(seg).map(([s,r]) => ({score: +s, rank: +r}))
|
||
.sort((a,b) => b.score - a.score);
|
||
|
||
// Perfect match
|
||
for (const e of entries) if (e.rank === rank) return e.score;
|
||
|
||
// Between two ranks
|
||
for (let i = 0; i < entries.length - 1; i++) {
|
||
const hi = entries[i], lo = entries[i+1];
|
||
if (rank >= hi.rank && rank < lo.rank) {
|
||
const frac = (rank - hi.rank) / (lo.rank - hi.rank);
|
||
return Math.round(hi.score - (hi.score - lo.score) * frac);
|
||
}
|
||
}
|
||
|
||
// Below last entry (worse rank)
|
||
const last = entries[entries.length-1];
|
||
if (rank >= last.rank) {
|
||
// extrapolate
|
||
const prev = entries[entries.length-2];
|
||
const slope = (prev.score - last.score) / (last.rank - prev.rank);
|
||
return Math.round(last.score - slope * (rank - last.rank));
|
||
}
|
||
|
||
// Above first entry (better rank)
|
||
const first = entries[0];
|
||
if (rank < first.rank) {
|
||
return first.score + Math.round((first.score) * (first.rank - rank) / first.rank);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// =====================================================================
|
||
// CONVERSION METHODS
|
||
// =====================================================================
|
||
|
||
function convertRank(rank, fromYear, toYear, method) {
|
||
const pFrom = PARAMS[fromYear], pTo = PARAMS[toYear];
|
||
if (!pFrom || !pTo) return null;
|
||
|
||
let newRank;
|
||
switch(method) {
|
||
case 'm1':
|
||
// Direct rank
|
||
newRank = rank;
|
||
break;
|
||
case 'm2':
|
||
// Proportional
|
||
if (pFrom.total === 0) return null;
|
||
newRank = Math.round(rank * pTo.total / pFrom.total);
|
||
break;
|
||
case 'm3':
|
||
// Wuhua proportional
|
||
if (pFrom.wuhua === 0) return null;
|
||
newRank = Math.round(rank * pTo.wuhua / pFrom.wuhua);
|
||
break;
|
||
default:
|
||
return null;
|
||
}
|
||
return Math.max(1, newRank);
|
||
}
|
||
|
||
function convert(score, rank, fromYear, toYear, method) {
|
||
let r = rank;
|
||
if (!r && score != null) r = getScoreRank(fromYear, score);
|
||
if (!r) return null;
|
||
|
||
const newRank = convertRank(r, fromYear, toYear, method);
|
||
if (!newRank) return null;
|
||
|
||
const newScore = getRankScore(toYear, newRank);
|
||
|
||
return { fromScore: score, fromRank: r, toScore: newScore, toRank: newRank };
|
||
}
|
||
|
||
// =====================================================================
|
||
// RENDER FUNCTIONS
|
||
// =====================================================================
|
||
|
||
function renderParamTable() {
|
||
const years = [2023,2024,2025,2026];
|
||
let html = '<table class="data-table"><thead><tr><th>年份</th><th>考生总数</th><th>物化考生</th><th>物化占比</th><th>特控线</th><th>一段线</th><th>普通类总人数</th></tr></thead><tbody>';
|
||
for (const y of years) {
|
||
const p = PARAMS[y];
|
||
const wuhuaPct = p.total > 0 ? (p.wuhua / p.total * 100).toFixed(1) : '-';
|
||
html += `<tr>
|
||
<td>${y}</td>
|
||
<td>${(p.total/10000).toFixed(1)}万</td>
|
||
<td>${(p.wuhua/10000).toFixed(1)}万</td>
|
||
<td>${wuhuaPct}%</td>
|
||
<td>${p.specialScore}分 / ${p.totalRank ? (p.totalRank*0.2).toFixed(0) : '-'}</td>
|
||
<td>${p.firstScore}分 / ${p.totalRank ? (p.totalRank*0.6).toFixed(0) : '-'}</td>
|
||
<td>${(p.totalRank/10000).toFixed(1)}万</td>
|
||
</tr>`;
|
||
}
|
||
html += '</tbody></table>';
|
||
document.getElementById('paramTableContainer').innerHTML = html;
|
||
}
|
||
|
||
function renderSchoolTable() {
|
||
const years = [2023,2024,2025];
|
||
let html = '<table class="data-table"><thead><tr><th>院校</th><th>档位</th>';
|
||
for (const y of years) html += `<th>${y}分数</th><th>${y}位次</th>`;
|
||
html += '</tr></thead><tbody>';
|
||
for (const s of SCHOOLS) {
|
||
const marker = s.hasWuhua ? '🔬' : '📋';
|
||
html += `<tr><td>${marker} ${s.name}</td><td><span class="method-tag m${s.code.includes('冲')?'1':s.code.includes('稳')?'2':'4'}">${s.code}</span></td>`;
|
||
for (const y of years) {
|
||
const d = s.data[y];
|
||
html += `<td>${d.score}</td><td>${(d.rank/10000).toFixed(1)}万</td>`;
|
||
}
|
||
html += '</tr>';
|
||
}
|
||
html += '</tbody></table>';
|
||
document.getElementById('schoolTableContainer').innerHTML = html;
|
||
}
|
||
|
||
function renderQuickRef() {
|
||
const scores = [540,550,560,570,573,580,590,600,610,620];
|
||
let html = '<table class="data-table"><thead><tr><th>等效分数</th><th>2023位次→2025</th><th>2024位次→2025</th><th>2025直接</th></tr></thead><tbody>';
|
||
for (const s of scores) {
|
||
const r23 = getScoreRank(2023, s);
|
||
const r24 = getScoreRank(2024, s);
|
||
const r25 = getScoreRank(2025, s);
|
||
const eq23 = r23 ? convertRank(r23, 2023, 2025, 'm3') : '-';
|
||
const eq24 = r24 ? convertRank(r24, 2024, 2025, 'm3') : '-';
|
||
const eq23Score = eq23 ? getRankScore(2025, eq23) : '-';
|
||
const eq24Score = eq24 ? getRankScore(2025, eq24) : '-';
|
||
html += `<tr>
|
||
<td><strong>${s}</strong></td>
|
||
<td>${eq23Score !== '-' ? eq23Score + '分 / ' + (eq23/10000).toFixed(1)+'万' : '-'}</td>
|
||
<td>${eq24Score !== '-' ? eq24Score + '分 / ' + (eq24/10000).toFixed(1)+'万' : '-'}</td>
|
||
<td>${s}分 / ${r25 ? (r25/10000).toFixed(1)+'万' : '-'}</td>
|
||
</tr>`;
|
||
}
|
||
html += '</tbody></table>';
|
||
document.getElementById('quickRefTable').innerHTML = html;
|
||
}
|
||
|
||
// =====================================================================
|
||
// TAB SWITCHING
|
||
// =====================================================================
|
||
|
||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||
this.classList.add('active');
|
||
document.getElementById('tab-' + this.dataset.tab).classList.add('active');
|
||
});
|
||
});
|
||
|
||
// =====================================================================
|
||
// CONVERSION HANDLER
|
||
// =====================================================================
|
||
|
||
function runConversion() {
|
||
const fromYear = +document.getElementById('convFromYear').value;
|
||
const toYear = +document.getElementById('convToYear').value;
|
||
const inputType = document.getElementById('convInputType').value;
|
||
const method = document.getElementById('convMethod').value;
|
||
const inputVal = +document.getElementById('convInput').value;
|
||
const box = document.getElementById('convResult');
|
||
|
||
if (!inputVal || inputVal <= 0) { box.classList.remove('show'); return; }
|
||
|
||
let r, s;
|
||
if (inputType === 'score') {
|
||
s = inputVal;
|
||
r = getScoreRank(fromYear, s);
|
||
} else {
|
||
r = inputVal;
|
||
s = getRankScore(fromYear, r);
|
||
}
|
||
|
||
if (!r || !s) { box.classList.remove('show'); return; }
|
||
|
||
const result = convert(s, r, fromYear, toYear, method);
|
||
if (!result) { box.classList.remove('show'); return; }
|
||
|
||
const methodLabels = { m1: 'M1 直接位次法', m2: 'M2 比例法', m3: 'M3 物化修正法' };
|
||
|
||
let html = `<div class="item"><span class="key">来源 (${fromYear})</span><span class="val">${fromYear==2026&&DATA_2026.score?'已保存':''} ${s}分 / ${(r/10000).toFixed(1)}万名</span></div>`;
|
||
html += `<div class="item"><span class="key">方法</span><span class="val gold">${methodLabels[method]}</span></div>`;
|
||
html += `<div class="item"><span class="key">目标年份 (${toYear})</span><span class="val gold">${result.toScore}分</span></div>`;
|
||
html += `<div class="item"><span class="key">目标位次 (${toYear})</span><span class="val blue">${(result.toRank/10000).toFixed(1)}万名</span></div>`;
|
||
|
||
// If converting TO 2026 and we have school data, show alignment
|
||
if (toYear === 2026) {
|
||
html += '<div style="margin-top:0.5rem;padding-top:0.5rem;border-top:1px solid rgba(240,180,41,0.15)"><div class="form-label" style="color:var(--accent-gold)">参考院校定位</div>';
|
||
for (const sch of SCHOOLS) {
|
||
const cutoff = getRankScore(2025, sch.data[2025].rank);
|
||
if (result.toRank && Math.abs(result.toRank - sch.data[2025].rank) / sch.data[2025].rank < 0.15) {
|
||
const diff = result.toRank - sch.data[2025].rank;
|
||
const rel = diff > 0 ? '下方' : '上方';
|
||
html += `<div style="font-size:0.78rem;color:var(--text-secondary);margin-top:0.2rem">${sch.name} (${sch.code}) — 等效位次在 ${sch.data[2025].rank} 的 <strong style="color:${diff>0?'var(--accent-red)':'var(--accent-green)'}">${rel} ${Math.abs(Math.round(diff/100))}百名</strong></div>`;
|
||
}
|
||
}
|
||
html += '</div>';
|
||
}
|
||
|
||
document.getElementById('convResultItems').innerHTML = html;
|
||
box.classList.add('show');
|
||
}
|
||
|
||
// =====================================================================
|
||
// PREDICTION HANDLER
|
||
// =====================================================================
|
||
|
||
function runPredict() {
|
||
const method = document.getElementById('predMethod').value;
|
||
const weightType = document.getElementById('predWeight').value;
|
||
const container = document.getElementById('predResult');
|
||
|
||
const years = [2023,2024,2025];
|
||
const weights = weightType === 'weighted' ? [0.2, 0.3, 0.5] : [1/3, 1/3, 1/3];
|
||
|
||
let html = '<table class="data-table"><thead><tr><th>院校</th><th>档位</th><th>近3年均位次</th>';
|
||
for (const y of years) html += `<th>${y}→2026</th>`;
|
||
html += '<th>预测2026位次</th><th>预测2026分数</th></tr></thead><tbody>';
|
||
|
||
for (const sch of SCHOOLS) {
|
||
const convRanks = [];
|
||
let validYears = 0;
|
||
for (const y of years) {
|
||
const d = sch.data[y];
|
||
if (d) {
|
||
const cr = convertRank(d.rank, y, 2026, method);
|
||
convRanks.push(cr);
|
||
if (cr) validYears++;
|
||
}
|
||
}
|
||
|
||
if (validYears === 0) continue;
|
||
|
||
// Weighted average
|
||
let predRank = 0;
|
||
let wSum = 0;
|
||
for (let i = 0; i < convRanks.length; i++) {
|
||
if (convRanks[i]) {
|
||
predRank += convRanks[i] * weights[i];
|
||
wSum += weights[i];
|
||
}
|
||
}
|
||
predRank = Math.round(predRank / wSum);
|
||
|
||
// Predict score for 2026 using 2025 as base (since 2025 is most recent)
|
||
// For 2026, we don't have a full 一分一段表, so use an estimate
|
||
// The score distribution shifts: estimate 2026 equivalent
|
||
// Use the 2025 table as base, adjust by the predicted rank shift
|
||
const predScore = getRankScore(2025, predRank);
|
||
|
||
const marker = sch.hasWuhua ? '🔬' : '📋';
|
||
html += `<tr>
|
||
<td>${marker} ${sch.name}</td>
|
||
<td><span class="method-tag m${sch.code.includes('冲')?'1':sch.code.includes('稳')?'2':'4'}">${sch.code}</span></td>
|
||
<td>${(sch.data[2025].rank/10000).toFixed(1)}万</td>`;
|
||
for (const cr of convRanks) {
|
||
html += `<td>${cr ? (cr/10000).toFixed(1)+'万' : '-'}</td>`;
|
||
}
|
||
const col = predRank < sch.data[2025].rank ? 'green' : 'red';
|
||
html += `<td style="color:var(--accent-${col})">${(predRank/10000).toFixed(2)}万</td>`;
|
||
html += `<td style="color:var(--accent-gold)">${predScore || '-'}分</td>`;
|
||
html += '</tr>';
|
||
}
|
||
html += '</tbody></table>';
|
||
container.innerHTML = html;
|
||
|
||
// Update chart
|
||
updatePredChart();
|
||
}
|
||
|
||
function updatePredChart() {
|
||
const ctx = document.getElementById('predChart').getContext('2d');
|
||
if (window._predChart) window._predChart.destroy();
|
||
|
||
const labels = SCHOOLS.map(s => s.name);
|
||
const data2025 = SCHOOLS.map(s => s.data[2025].rank);
|
||
|
||
// Predict with M3
|
||
const method = document.getElementById('predMethod').value;
|
||
const weights = [0.2, 0.3, 0.5];
|
||
const data2026 = SCHOOLS.map(sch => {
|
||
const convRanks = [];
|
||
let wSum = 0;
|
||
const years = [2023,2024,2025];
|
||
for (let i = 0; i < years.length; i++) {
|
||
const d = sch.data[years[i]];
|
||
if (d) {
|
||
const cr = convertRank(d.rank, years[i], 2026, method);
|
||
convRanks.push(cr);
|
||
if (cr) wSum += weights[i];
|
||
}
|
||
}
|
||
let pred = 0;
|
||
for (let i = 0; i < convRanks.length; i++) {
|
||
if (convRanks[i]) pred += convRanks[i] * weights[i];
|
||
}
|
||
return Math.round(pred / wSum);
|
||
});
|
||
|
||
window._predChart = new Chart(ctx, {
|
||
type: 'bar',
|
||
data: {
|
||
labels,
|
||
datasets: [
|
||
{ label: '2025 位次', data: data2025, backgroundColor: 'rgba(77,156,240,0.6)', borderColor: '#4d9cf0', borderWidth: 1 },
|
||
{ label: '2026 预测位次', data: data2026, backgroundColor: 'rgba(240,180,41,0.6)', borderColor: '#f0b429', borderWidth: 1 }
|
||
]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: true,
|
||
plugins: {
|
||
legend: { labels: { color: '#8899aa' } }
|
||
},
|
||
scales: {
|
||
y: { reverse: true, ticks: { color: '#5a6a7a', callback: v => (v/10000).toFixed(1)+'万' }, grid: { color: 'rgba(255,255,255,0.05)' } },
|
||
x: { ticks: { color: '#8899aa', maxRotation: 45 } }
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// =====================================================================
|
||
// MAPPING HANDLER (天恒)
|
||
// =====================================================================
|
||
|
||
function runMapping() {
|
||
const score = +document.getElementById('tScore').value;
|
||
const rankInput = +document.getElementById('tRank').value;
|
||
const method = document.getElementById('mapMethod').value;
|
||
const saveCheck = document.getElementById('mapSave2026').checked;
|
||
const box = document.getElementById('mapResult');
|
||
|
||
let r = rankInput;
|
||
if (!r && score) r = getScoreRank(2025, score); // Use 2025 as base for 2026 estimate
|
||
if (!r && !score) { box.classList.remove('show'); return; }
|
||
|
||
// If we have 2026 data saved, use it; otherwise calculate from score
|
||
if (DATA_2026.score === score && DATA_2026.rank) r = DATA_2026.rank;
|
||
|
||
// Save 2026 data if needed
|
||
if (saveCheck) {
|
||
DATA_2026.score = score;
|
||
// If no rank provided, estimate from score using 2025 table (temporary)
|
||
if (!r && score) r = getScoreRank(2025, score);
|
||
DATA_2026.rank = r;
|
||
}
|
||
|
||
const methodLabels = { m2: 'M2 比例法', m3: 'M3 物化修正法' };
|
||
const methodKey = method === 'm2' ? '比例法' : '物化修正法';
|
||
|
||
const years = [2023, 2024, 2025];
|
||
let html = `<div class="item"><span class="key">2026 输入</span><span class="val gold">${score || '-'}分 / ${r ? (r/10000).toFixed(1)+'万位' : '-'}</span></div>`;
|
||
html += `<div class="item"><span class="key">换算方法</span><span class="val gold">${methodLabels[method]}</span></div>`;
|
||
html += `<div style="margin:0.5rem 0;padding:0.5rem 0;border-top:1px solid rgba(255,255,255,0.06);border-bottom:1px solid rgba(255,255,255,0.06)">
|
||
<span style="color:var(--text-secondary);font-size:0.78rem">跨年等效对照</span>
|
||
</div>`;
|
||
|
||
for (const y of years) {
|
||
const conv = convert(score, r, 2026, y, method);
|
||
if (!conv) continue;
|
||
html += `<div class="item">
|
||
<span class="key">${y} 等效</span>
|
||
<span class="val"><span class="gold">${conv.toScore}分</span> / <span class="blue">${(conv.toRank/10000).toFixed(1)}万位</span></span>
|
||
</div>`;
|
||
}
|
||
|
||
// Add school recommendation
|
||
html += `<div style="margin-top:0.5rem;padding-top:0.5rem;border-top:1px solid rgba(240,180,41,0.15)">
|
||
<div class="form-label" style="color:var(--accent-gold)">基于等效位次的院校定位</div>`;
|
||
|
||
if (r) {
|
||
// Use 2025 as base for comparison
|
||
const baseYear = 2025;
|
||
const sorted = SCHOOLS.slice().sort((a,b) => a.data[baseYear].rank - b.data[baseYear].rank);
|
||
let found = false;
|
||
for (const sch of sorted) {
|
||
const cutoff = sch.data[baseYear].rank;
|
||
const diff = r - cutoff;
|
||
const diffPct = diff / cutoff;
|
||
if (diffPct > -0.2 && diffPct < 0.2) {
|
||
const emoji = diff <= 0 ? '✅' : '⚠️';
|
||
const label = diff <= 0 ? '位次优势' : '位次差距';
|
||
html += `<div style="font-size:0.8rem;margin-top:0.25rem;display:flex;justify-content:space-between">
|
||
<span style="color:var(--text-secondary)">${emoji} ${sch.name} (${sch.code})</span>
|
||
<span style="font-family:var(--font-mono);color:${diff<=0?'var(--accent-green)':'var(--accent-red)'}">
|
||
${diff<=0?'↑':'↓'} ${Math.abs(Math.round(diff/100))}百名
|
||
</span>
|
||
</div>`;
|
||
found = true;
|
||
}
|
||
}
|
||
if (!found) {
|
||
// Find closest
|
||
let closest = SCHOOLS[0];
|
||
let minDiff = Infinity;
|
||
for (const sch of SCHOOLS) {
|
||
const d = Math.abs(r - sch.data[baseYear].rank);
|
||
if (d < minDiff) { minDiff = d; closest = sch; }
|
||
}
|
||
html += `<div style="font-size:0.8rem;color:var(--text-muted)">最接近院校: ${closest.name} (差${Math.round(minDiff/100)}百名)</div>`;
|
||
}
|
||
}
|
||
html += '</div>';
|
||
|
||
document.getElementById('mapResultItems').innerHTML = html;
|
||
box.classList.add('show');
|
||
|
||
// Update advice
|
||
updateMappingAdvice(score, r);
|
||
}
|
||
|
||
function updateMappingAdvice(score, rank) {
|
||
const div = document.getElementById('mappingAdvice');
|
||
if (!score && !rank) return;
|
||
|
||
let html = '';
|
||
if (score >= 590) {
|
||
html = `<div class="rec-card"><div class="title">🎯 高分段定位</div>
|
||
<p style="color:var(--text-secondary);font-size:0.82rem;margin-top:0.3rem">
|
||
${score}分在2026年属较高分段,等效位次约${rank?(rank/10000).toFixed(1)+'万':'待计算'}。
|
||
建议冲档院校可关注浙理工、西邮等;稳档关注中国计量、温州大学等。
|
||
数字媒体技术方向:浙传媒(582分参考)、浙科大(573分参考)可重点保底。
|
||
</p></div>`;
|
||
} else if (score >= 570) {
|
||
html = `<div class="rec-card"><div class="title">🎯 中高分段定位</div>
|
||
<p style="color:var(--text-secondary);font-size:0.82rem;margin-top:0.3rem">
|
||
${score}分在2026年属中高分段,等效位次约${rank?(rank/10000).toFixed(1)+'万':'待计算'}。
|
||
<strong style="color:var(--accent-gold)">浙科大数字媒体技术(2025:573分/84385位次)</strong> 是核心目标。
|
||
建议稳档以浙科大为主,保底嘉兴大学、台州学院;桂电中外合作可做中档。
|
||
</p></div>`;
|
||
} else if (score >= 550) {
|
||
html = `<div class="rec-card"><div class="title">🎯 中分段定位</div>
|
||
<p style="color:var(--text-secondary);font-size:0.82rem;margin-top:0.3rem">
|
||
${score}分在2026年属中分段,等效位次约${rank?(rank/10000).toFixed(1)+'万':'待计算'}。
|
||
建议稳档可关注嘉兴大学、温州大学;保底台州学院。
|
||
数字媒体技术方向偏差较大,可考虑桂电中外合作作为冲刺。
|
||
</p></div>`;
|
||
} else {
|
||
html = `<div class="rec-card"><div class="title">🎯 中低分段定位</div>
|
||
<p style="color:var(--text-secondary);font-size:0.82rem;margin-top:0.3rem">
|
||
${score}分在2026年竞争力偏弱,等效位次约${rank?(rank/10000).toFixed(1)+'万':'待计算'}。
|
||
建议重点关注保底院校(台州学院等),数字媒体技术专业可能需要考虑中外合作办学或省外院校。
|
||
</p></div>`;
|
||
}
|
||
div.innerHTML = html;
|
||
}
|
||
|
||
// =====================================================================
|
||
// COMPARISON HANDLER
|
||
// =====================================================================
|
||
|
||
function runComparison() {
|
||
const year = +document.getElementById('cmpRefYear').value;
|
||
const score = +document.getElementById('cmpScore').value;
|
||
const container = document.getElementById('cmpResult');
|
||
|
||
const r = getScoreRank(year, score);
|
||
if (!r) { container.innerHTML = '<div class="alert">无法找到该分数对应位次</div>'; return; }
|
||
|
||
const methods = ['m1', 'm2', 'm3'];
|
||
const labels = ['M1 直接位次法', 'M2 比例法', 'M3 物化修正法'];
|
||
const targetYears = [2024, 2025, 2026];
|
||
|
||
let html = `<div class="alert"><strong>参考点:</strong>${year}年 ${score}分(位次 ${(r/10000).toFixed(1)}万)</div>`;
|
||
html += '<table class="data-table"><thead><tr><th>方法</th>';
|
||
for (const ty of targetYears) html += `<th>等效${ty}</th>`;
|
||
html += '</tr></thead><tbody>';
|
||
|
||
const chartData = [];
|
||
|
||
for (let m = 0; m < methods.length; m++) {
|
||
html += `<tr><td><span class="method-tag ${'m'+(m+1)}">${labels[m]}</span></td>`;
|
||
const row = { label: labels[m], data: [] };
|
||
for (const ty of targetYears) {
|
||
const conv = convert(score, r, year, ty, methods[m]);
|
||
if (conv) {
|
||
html += `<td>${conv.toScore}分 / ${(conv.toRank/10000).toFixed(1)}万</td>`;
|
||
row.data.push(conv.toScore);
|
||
} else {
|
||
html += `<td>-</td>`;
|
||
row.data.push(null);
|
||
}
|
||
}
|
||
html += '</tr>';
|
||
chartData.push(row);
|
||
}
|
||
html += '</tbody></table>';
|
||
container.innerHTML = html;
|
||
|
||
updateCmpChart(chartData, targetYears);
|
||
}
|
||
|
||
function updateCmpChart(data, labels) {
|
||
const ctx = document.getElementById('cmpChart').getContext('2d');
|
||
if (window._cmpChart) window._cmpChart.destroy();
|
||
|
||
const colors = ['#4d9cf0', '#06d6a0', '#f0b429'];
|
||
const datasets = data.map((d, i) => ({
|
||
label: d.label,
|
||
data: d.data,
|
||
borderColor: colors[i],
|
||
backgroundColor: colors[i] + '33',
|
||
borderWidth: 2,
|
||
pointBackgroundColor: colors[i],
|
||
pointRadius: 4,
|
||
tension: 0.2
|
||
}));
|
||
|
||
window._cmpChart = new Chart(ctx, {
|
||
type: 'line',
|
||
data: { labels: labels.map(y => y+'年'), datasets },
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: true,
|
||
plugins: {
|
||
legend: { labels: { color: '#8899aa' } }
|
||
},
|
||
scales: {
|
||
y: { ticks: { color: '#5a6a7a' }, grid: { color: 'rgba(255,255,255,0.05)' } },
|
||
x: { ticks: { color: '#8899aa' } }
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// =====================================================================
|
||
// TREND CHARTS
|
||
// =====================================================================
|
||
|
||
function initTrendCharts() {
|
||
const years = [2023, 2024, 2025, 2026];
|
||
const totals = years.map(y => PARAMS[y].total / 10000);
|
||
const wuhua = years.map(y => PARAMS[y].wuhua / 10000);
|
||
const wuhuaPct = years.map(y => +(PARAMS[y].wuhua / PARAMS[y].total * 100).toFixed(1));
|
||
|
||
// Total candidates
|
||
new Chart(document.getElementById('trendChart1'), {
|
||
type: 'bar',
|
||
data: {
|
||
labels: years.map(y => y+'年'),
|
||
datasets: [{
|
||
label: '考生总数(万)',
|
||
data: totals,
|
||
backgroundColor: 'rgba(77,156,240,0.6)',
|
||
borderColor: '#4d9cf0',
|
||
borderWidth: 1
|
||
}, {
|
||
label: '物化考生(万)',
|
||
data: wuhua,
|
||
backgroundColor: 'rgba(240,180,41,0.6)',
|
||
borderColor: '#f0b429',
|
||
borderWidth: 1
|
||
}]
|
||
},
|
||
options: {
|
||
plugins: { legend: { labels: { color: '#8899aa' } } },
|
||
scales: {
|
||
y: { ticks: { color: '#5a6a7a' }, grid: { color: 'rgba(255,255,255,0.05)' } },
|
||
x: { ticks: { color: '#8899aa' } }
|
||
}
|
||
}
|
||
});
|
||
|
||
// Wuhua %
|
||
new Chart(document.getElementById('trendChart2'), {
|
||
type: 'line',
|
||
data: {
|
||
labels: years.map(y => y+'年'),
|
||
datasets: [{
|
||
label: '物化占比(%)',
|
||
data: wuhuaPct,
|
||
borderColor: '#f0b429',
|
||
backgroundColor: 'rgba(240,180,41,0.1)',
|
||
fill: true,
|
||
borderWidth: 2,
|
||
pointBackgroundColor: '#f0b429',
|
||
pointRadius: 5
|
||
}]
|
||
},
|
||
options: {
|
||
plugins: { legend: { labels: { color: '#8899aa' } } },
|
||
scales: {
|
||
y: { ticks: { color: '#5a6a7a', callback: v => v+'%' }, grid: { color: 'rgba(255,255,255,0.05)' } },
|
||
x: { ticks: { color: '#8899aa' } }
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// =====================================================================
|
||
// INIT
|
||
// =====================================================================
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
renderParamTable();
|
||
renderSchoolTable();
|
||
renderQuickRef();
|
||
initTrendCharts();
|
||
|
||
// Auto-run pred on load
|
||
setTimeout(runPredict, 100);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|