记录从 v1 到 v5 的全部问题、根因分析、修复方案。 作为后续维护的参考手册。
前端: 原生 HTML/CSS/JS (Vanilla) — 零依赖,无构建工具
路由: Hash-based SPA — 所有页面内嵌 index.html,hash 切换
存储: localStorage (L1) + JSON 导入/导出 (L2)
服务器: Node.js 内建 http 模块 — 零 npm 依赖,零第三方包
题库: JSON 文件 — AI 生成,直接 fetch 加载
- 多页面方案需要每个页面重复加载 CSS/JS
- 需要处理 CORS(file:// 协议下 fetch 不能用)
- GitHub Pages 部署时需要额外配置 404.html 做 fallback
- SPA 一个 index.html 解决所有问题
http-server 的坑:
- 默认 Cache: 3600s(1小时缓存),导致跨浏览器不一致
- -c-1 参数在部分版本失效
- 与 Python http.server 同时监听同一端口时无报错,静默冲突
自写 server.js 的优势:
- 精确控制缓存头:Cache-Control: no-store + Pragma: no-cache + Expires: 0
- 单一进程、唯一启动方式
- 零外部依赖
| # | 问题 | 首次出现 | 根因 | 修复版本 |
|---|---|---|---|---|
| P0-1 | CCNA 模拟考显示 32 题(应为 60 题) | v2 | 浏览器缓存了旧版 JSON 文件 | v4 |
| P0-2 | CCNP 水平测试在 Edge 显示"待推出" | v2 | Edge 缓存了文件不存在时的 404 响应 | v4 |
| P0-3 | 两个服务器同时监听 8080 端口 | v3 | http-server 未彻底关闭,与 server.js 冲突 | v4 |
| P0-4 | Chrome 和 Edge 显示不同版本内容 | v3 | 不同浏览器在不同时间点各自缓存了不同版本 | v4 |
| P0-5 | 模拟考返回后列表空白,刷新才恢复 | v4 | renderMockList() 未重新显示 mock-list 容器 |
v4 |
| # | 问题 | 首次出现 | 根因 | 修复版本 |
|---|---|---|---|---|
| P1-1 | 水平测试选完不能返回重新选择 | v2 | 缺少重置 UI 的机制 | v4 |
| P1-2 | 考试字体太小,页面空白多 | v3 | CSS 字体 14-15px,卡片 padding 过大 | v4 |
| P1-3 | 启动方式混乱(python/node/http-server) | v2 | 文档同时列出多种启动方式 | v4 |
| P1-4 | 首页标题文案不准确 | v3 | "零版权风险"未体现"考纲驱动"核心 | v4 |
| P1-5 | CCNP 模拟考只有 31 题 | v3 | 出题脚本未按官方权重配比 | v4 |
| # | 问题 | 首次出现 | 根因 | 修复版本 |
|---|---|---|---|---|
| P2-1 | 项目根目录堆积构建脚本和中间文件 | v3 | 边开发边打补丁,未清理 | v4 |
| P2-2 | 缓存爆破用 Date.now() |
v3 | 同一页面内多次请求时间戳可能相同 | v4 |
| P2-3 | JS 模块初始化顺序依赖隐式全局 | v2 | script 标签顺序即依赖顺序 | v4 |
| P2-4 | 多个 page-change 事件监听器 |
v3 | app.js 末尾添加了第二个 listener | v4 |
Chrome: CCNA 模拟考 60 题 ✓ CCNP 水平测试 ✓
Edge: CCNA 模拟考 60 题 ✓ CCNP 水平测试 ✗(待推出)
反过来:
Chrome: CCNA 模拟考 32 题 ✗ CCNP 水平测试 ✓
Edge: CCNA 模拟考 60 题 ✓ CCNP 水平测试 ✗
浏览器 A 浏览器 B
│ │
├─ 首次请求 ├─ 首次请求(时间晚一些)
│ GET ccna-mock-1.json │ GET ccna-mock-1.json
│ ← 200 (32题版本) │ ← 200 (60题版本,已更新)
│ 缓存: max-age=3600 │ 缓存: max-age=3600
│ │
├─ 后续请求(1小时内) ├─ 后续请求(1小时内)
│ 读缓存 → 32题 │ 读缓存 → 60题
│ ✗ 显示错误 │ ✓ 显示正确
关键发现:不同浏览器在不同时间打开页面,从 http-server 获取了不同版本的文件,并被各自缓存。服务器端的 Cache-Control: max-age=3600 让浏览器在 1 小时内不重新请求。
同时,旧版 http-server 进程未被杀死,与新版 server.js 同时监听 8080 端口。部分请求打到旧服务器(带缓存),部分打到新服务器(不带缓存)。
第一层 — 服务器(server.js):
Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate
Pragma: no-cache
Expires: 0
第二层 — HTML 页面:
<meta http-equiv="Cache-Control" content="no-store">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
第三层 — fetch 请求:
fetch(url + '?v=' + APP_VER)
APP_VER 硬编码在 app.js 顶部,版本变更时所有 URL 自动刷新
验证方式:
curl -sI http://localhost:8080/any-file | grep -i "cache"
# 输出: Cache-Control: no-store, no-cache, must-revalidateCCNA 模拟考: 文件显示 60 题,浏览器显示 32 题(缓存导致)
CCNP 模拟考: 31 题,未按官方考纲配比
CCNA 200-301 v1.1:
| 知识域 | 权重 | 配题 |
|---|---|---|
| Network Fundamentals | 20% | 12 |
| Network Access | 20% | 12 |
| IP Connectivity | 25% | 15 |
| IP Services | 10% | 6 |
| Security Fundamentals | 15% | 9 |
| Automation | 10% | 6 |
| 合计 | 100% | 60 |
CCNP ENCOR 350-401 v1.2:
| 知识域 | 权重 | 配题 |
|---|---|---|
| Architecture | 15% | 9 |
| Virtualization | 10% | 6 |
| Infrastructure | 30% | 18 |
| Network Assurance | 10% | 6 |
| Security | 20% | 12 |
| Automation & AI | 15% | 9 |
| 合计 | 100% | 60 |
- Cisco 官网 Exam Topics 页面
- Cisco Live BRKCRT-2008 会议资料
- Cisco Press 官方认证指南
1. 进入模拟考试列表 → 看到 CCNA / CCNP 两个选项 ✓
2. 点击 CCNA → 进入考试页面 ✓
3. 点击浏览器返回 → 页面空白 ✗
4. 刷新 → 恢复正常 ✓
loadExam() 将 mock-list 设置为 display: none。导航回模拟考页面时触发 page-change → 调用 renderMockList()。该方法重新渲染列表内容,但没有将 mock-list 重新设置回 display: block。
loadExam():
mock-list.style.display = 'none' ← 隐藏列表
mock-exam.style.display = 'block' ← 显示考试
renderMockList():
mock-list.innerHTML = '...' ← 重新渲染
mock-exam.style.display = 'none' ← 隐藏考试
// 漏了: mock-list.style.display = 'block' ← 没有重新显示!
function renderMockList() {
// ... 渲染代码 ...
document.getElementById('mock-exam').style.display = 'none';
document.getElementById('mock-list').style.display = 'block'; // ← 加这一行
}同时新增「⬅ 返回试卷列表」按钮和 backMock() 函数。
选择 CCNA 或 CCNP 进入答题后,没有按钮可以回到选择界面。
// 页面切换时自动重置
window.addEventListener('page-change', (e) => {
if (e.detail.page === 'page-level-test') resetLT();
});
function resetLT() {
document.getElementById('lt-setup').style.display = 'block';
document.getElementById('lt-exam').style.display = 'none';
engine = null;
}同时在 HTML 中添加「⬅ 重新选择」按钮。
| 元素 | 原始 | 优化后 |
|---|---|---|
| 题干字体 | 15px | 17px |
| 选项字体 | 14px | 16px |
| 选项内边距 | 10px 14px | 14px 18px |
| 选项行高 | 1.6 | 1.5 |
| 题干 margin-bottom | 16px | 20px |
❌ npx http-server → 缓存不可控,多进程冲突
❌ python -m http.server → 非 Windows 默认,显式退出
✅ node server.js → 零依赖,缓存精确控制,唯一进程
❌ 依赖服务器默认缓存行为
❌ Date.now() 做缓存爆破(同页多次请求可能相同)
✅ Cache-Control: no-store(服务器端)
✅ HTML meta 头防缓存
✅ fetch URL 带版本号参数
❌ 不同页面间共享全局变量,没有重置机制
✅ 每次 page-change 事件触发时重置页面状态
✅ 每个页面有独立的 reset 函数
❌ 只在 Chrome 测试,认为 Edge/Firefox 会自动一致
✅ 关键行为需在 Chrome + Edge + Firefox 各验证一次
✅ 服务器端缓存头是所有浏览器统一遵循的标准
项目初期只在 PC 端 Chrome 上测试,未考虑手机端使用场景。
实际上,考生在通勤、午休时的碎片化刷题需求非常强烈。
支持同一 WiFi 下手机访问,具体步骤:
1. 电脑启动 node server.js(监听 0.0.0.0:8080)
2. 手机连接同一 WiFi
3. 获取电脑局域网 IP(ipconfig 查 192.168.x.x)
4. 手机浏览器访问 http://192.168.x.x:8080
服务器绑定 0.0.0.0(默认行为)→ 局域网内所有设备可访问
无需部署到公网服务器
无需 HTTPS(局域网内足够安全)
数据仍存在各自设备的 localStorage 中
新增 使用教程.md:
- PC 端完整使用指南
- 手机端连接教程(含防火墙设置)
- 功能详解(水平测试、模拟考、错题本、学习计划)
- 数据管理说明
- 常见问题(FAQ)
- 命令速查表
❌ 凭印象写题目数量
✅ 从官方文档核实考试时长、题量、域名、权重
✅ 数据来源明确记录在日志中
# 唯一方式
node server.js
# 浏览器打开
http://localhost:8080# 1. 用 AI 生成 JSON 题库
# 2. 放入 questions/generated/
# 3. 更新 questions/index.json 元数据
# 4. 验证
node scripts/build-all-questions.js
# 5. 重新加载页面即可(无缓存,立刻生效)修改 app.js 顶部的 APP_VER 常量,所有 ?v= 参数自动刷新。
每次修改后在以下浏览器测试:
□ Chrome 最新版
□ Edge 最新版
□ Firefox 最新版
检查项:
□ 水平测试可选 CCNA / CCNP 并答题
□ 模拟考 60 题完整显示
□ 错题本收录 / 筛选 / 重练
□ 导入 / 导出数据
□ 深色 / 浅色主题切换
□ 移动端 375px 宽度布局
NetLearner/
├── server.js # 服务器(唯一入口)
├── index.html # SPA 主页面
├── assets/
│ ├── css/main.css # 设计系统
│ └── js/
│ ├── app.js # 应用主逻辑(版本号在此定义)
│ ├── router.js # 哈希路由
│ ├── storage.js # 存储层
│ ├── exam-engine.js # 考试状态机
│ ├── wrong-answer-service.js # 错题本
│ └── planner-generator.js # 学习计划
├── questions/
│ ├── index.json # 题库元数据
│ └── generated/ # 4 个题库文件
│ ├── ccna-level-demo.json
│ ├── ccna-mock-1.json
│ ├── ccnp-encor-level-demo.json
│ └── ccnp-encor-mock-1.json
├── scripts/
│ └── build-all-questions.js # 题库生成脚本
├── plugin-forge/ # 发布用 skill
├── 项目说明.md
├── 项目计划书.md
└── 项目开发日志.md
| 文件 | 目标 | 题量 | 时长 | 大纲版本 |
|---|---|---|---|---|
| ccna-level-demo.json | CCNA | 10 | — | 200301 v1.1 |
| ccna-mock-1.json | CCNA | 60 | 120min | 200301 v1.1 |
| ccnp-encor-level-demo.json | CCNP ENCOR | 10 | — | 350401 v1.2 |
| ccnp-encor-mock-1.json | CCNP ENCOR | 60 | 120min | 350401 v1.2 |
c## v5.0 - 2026-05-26 双厂商架构 / 大池加权 / 多题型引擎
从 v4.0 的 Cisco 单厂商升级为 Cisco+Huawei 双厂商。核心从固定试卷升级为大池加权随机。
1. Huawei 认证全系接入 HCIA-Datacom H12-811 V2.0 (8域大池加权) / HCIP-Datacom H12-821 V1.0 (13域)
2. 多题型引擎 single(单选) / multiple(多选) / fill(填空) / drag(拖拽) 四种评分逻辑
3. 域加权随机抽题系统 ExamEngine.pickFromPool() 按官方配比从各域加权抽取
4. 厂商筛选 + 双品牌主题 模考页: [全部] [Cisco] [Huawei] 三级筛选 / Cisco蓝 #4fc3f7 / 华为红 #e53935
5. 跨厂商共享概念池 questions/shared-concepts.json - 一个概念生成Cisco英文+Huawei双版本。已收录21个共享概念。
| 池 | 概念 | 变体 | 题型 | 域 |
|---|---|---|---|---|
| CCNA | 543 | 543 | single | 6 |
| CCNP ENCOR | 67 | 67 | single+multi+drag+fill | 6 |
| HCIA V2.0 | 119 | 229 | single+variants | 8 |
| HCIP H12-821 | 107 | 207 | single+multi+drag+fill | 13 |
CCNA池: 9套模考去重524题+共享概念19题=543题
tools/question-importer.html / tools/build-pools.js / questions/shared-concepts.json / questions/generated/ccna-pool.json / ccnp-pool.json / hcia-pool.json / hcip-pool.json / hcia-level-demo.json / hcip-level-demo.json
废除所有水平测试的固定 10 题模式,全部改为从对应大池加权随机抽取:
| 考试 | 原题量 | 现题量 | 覆盖域 | 模式 |
|---|---|---|---|---|
| CCNA (200-301) | 10 题 | 15 题 | 6 域加权 | 从 543 池随机 |
| CCNP ENCOR (350-401) | 10 题 | 20 题 | 6 域加权 | 从 67 池随机 |
| HCIA-Datacom (H12-811) | 10 题 | 15 题 | 8 域加权 | 从 119 池随机 |
| HCIP-Datacom (H12-821) | 10 题 | 20 题 | 13 域加权 | 从 107 池随机 |
考量: 10 题对 HCIP 的 13 个域完全不够,每个域分不到 1 题。CCNP/HCIP 提至 20 题,每域至少 1-2 题,测出来才有参考价值。CCNA/HCIA 入门级提至 15 题,适中。
所有界面使用官方考试代码,与 Cisco/Huawei 官方命名一致:
旧: CCNA 模拟考试 → 新: CCNA (200-301) 模拟考试
旧: CCNP ENCOR 水平测试 → 新: CCNP ENCOR (350-401) 水平测试
旧: HCIA 水平测试 → 新: HCIA-Datacom (H12-811) 水平测试
旧: HCIP 水平测试 → 新: HCIP-Datacom (H12-821) 水平测试
涉及文件:questions/index.json、index.html(标题/描述/关于)、app.js(fallback 元数据)
- CCNP池扩充(当前67概念偏小)
- HCIP选考H12-831支持
- 答题卡支持回顾