尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

纯前端刷题系统:Vue+JSON+localStorage离线实现四大模式

纯前端刷题系统:Vue+JSON+localStorage离线实现四大模式 简介这是一套轻量级、纯前端实现的H5刷题应用源码面向前端初学者、教育类小程序开发者及自学备考人群解决无服务端环境下的在线练习场景需求特别适合快速部署个人学习站或嵌入现有静态站点。资源共23个文件包含4个核心JSvue.min.js、axios.min.js等支撑交互与数据请求、3个HTMLindex.html、timu.html、type.html构成主页面结构、3个JSON2016–2018年题库数据、4个TXT含【必看】安装说明、README、zgdx课程说明等关键指引以及CSS、字体、图标和Python脚本timuToJson.py用于题库格式转换。包体仅544KB即下即用。已有144人学习下载。读者可直接获得完整可运行的四种刷题模式随机/章节/模拟考/错题重练前端逻辑、本地化题库管理方案、VueAxios技术栈实践范例以及从题库生成到答题渲染的全流程代码结构。1. 一个纯前端刷题网站为什么能跑通四种模式且不连服务器你打开浏览器拖入index.html题目立刻加载——没有后端接口调用没有数据库连接甚至没开本地服务file://协议直跑但「随机刷题」「章节练习」「模拟考试」「错题重做」四个模式全可切换、答案实时判分、错题自动归档、进度本地持久化。这不是 Demo而是真实可交付的 H5 学习工具。它靠的是JSON 静态题库 Vue 响应式驱动 localStorage 状态管理 Python 辅助题库格式转换四层组合拳。整个流程不依赖任何远程 API 或服务端渲染所有逻辑在浏览器内存中闭环完成。适合教育类轻量项目快速落地、培训机构定制化题库封装、或作为前端工程化教学案例——尤其适合想搞懂「无后端场景下如何设计状态流、题型路由、答题生命周期」的初中级开发者。如果你正卡在「Vue 怎么离线存错题」「axios 请求怎么 mock 成本地 JSON」「Python 脚本怎么把 Word 表格转成结构化题库」这些具体问题上这篇就是为你拆解的。2. 题库构建与前端数据流设计从 Word 模板到 Vue 可消费的 JSON2.1 题库来源与格式转换链路项目提供模板.docx和timuToJson.py这是整套方案的数据入口。模板.docx实际是带固定表头的 Word 表格非富文本包含字段如题干、选项A、选项B、选项C、选项D、正确答案、知识点、年份、题型。timuToJson.py的作用不是解析 DOCX 二进制而是将用户手动复制粘贴后的纯文本内容即将模板内容复制到txt中运行python即可.txt所指按行、按制表符或顿号分割生成标准 JSON 数组。这种设计规避了 DOCX 解析库依赖如 python-docx降低环境门槛也强制题库录入标准化。提示timuToJson.py默认读取同目录下的input.txt输出为output.json。若需多套题库建议修改脚本中input_file 2017_input.txt并保留原始文件名对应关系如2017.json来自2017_input.txt便于后续按年份动态加载。2.1.1 Python 转换脚本关键逻辑解析# timuToJson.py 核心片段已补全注释 import json def parse_line(line): # 按中文顿号、英文逗号、制表符统一分割兼容不同录入习惯 parts [p.strip() for p in line.replace(、, \t).replace(,, \t).split(\t) if p.strip()] if len(parts) 6: # 至少需题干4选项答案 return None return { id: len(data), # 自增ID用于localStorage索引 stem: parts[0], # 题干 options: parts[1:5], # A-D选项 answer: parts[5], # 正确答案值为A/B/C/D knowledge: parts[6] if len(parts) 6 else , year: parts[7] if len(parts) 7 else unknown, type: parts[8] if len(parts) 8 else single } data [] with open(input.txt, r, encodingutf-8) as f: for line in f: item parse_line(line) if item: data.append(item) with open(output.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)这段代码的关键在于parse_line的容错处理它不强求字段对齐而是用字符串替换统一分隔符再按实际长度截取。answer字段直接存字母而非索引使 Vue 判分逻辑更直观userAnswer question.answer。id由脚本生成而非 Word 中填写避免人工编号错误导致 localStorage 冲突。2.2 静态题库加载与 Vue 数据初始化index.html通过script srcjson/2017.json/script方式加载题库但注意这不是 JSONP而是利用浏览器将.json文件当作 JavaScript 执行的 hack——2017.json实际内容必须是var questions2017 [...];格式。源码中2017.json等文件正是按此格式导出timuToJson.py输出后需手动加var questions2017 前缀并改扩展名为.js。public.js中通过window.questions2017访问数据再注入 Vue 实例// public.js 片段 const app new Vue({ el: #app, data: { questions: window.questions2017 || [], // 动态挂载题库 currentQuestionIndex: 0, userAnswers: {}, // {questionId: A} wrongQuestions: [] // 错题ID数组 }, computed: { currentQuestion() { return this.questions[this.currentQuestionIndex]; } } });注意questions是响应式数组但userAnswers和wrongQuestions是普通对象/数组。为实现 localStorage 持久化需在mounted()钩子中读取mounted() { const saved localStorage.getItem(aiShuaTi); if (saved) { const state JSON.parse(saved); this.userAnswers state.userAnswers || {}; this.wrongQuestions state.wrongQuestions || []; } }这样刷新页面后答题记录不丢失是「无后端」体验的关键。2.3 四种刷题模式的路由与状态隔离模式切换不靠 URL 路由无 Vue Router而是通过data.mode控制视图分支。type.html是模式选择页点击后设置app.mode random | chapter | exam | wrong。各模式数据源不同randomthis.questions.sort(() Math.random() - 0.5).slice(0, 30)chapter需预处理questions添加chapter字段zgdx.txt可能含章节映射规则再filter(q q.chapter selectedChapter)exam固定 100 题currentQuestionIndex达到 100 后触发交卷逻辑wrongthis.questions.filter(q this.wrongQuestions.includes(q.id))提示exam模式交卷后需计算得分并生成报告但源码中timu.html的提交逻辑未完整实现。实际开发中应在submitExam()方法里遍历userAnswers与questions对比统计正确数并将结果存入localStorage的examHistory数组中支持历史回看。3. Vue 交互实现与 axios 替代方案纯前端请求如何模拟 API 行为3.1 为什么用 axios.min.js 却不发网络请求压缩包中包含axios.min.js但整个项目无任何axios.get()调用。这是因为axios在此处被用作Promise 封装器和统一错误处理壳而非 HTTP 客户端。例如在public.js的nextQuestion()方法中nextQuestion() { // 模拟异步加载如动画过渡 return axios Promise.resolve().then(() { this.currentQuestionIndex; if (this.currentQuestionIndex this.questions.length) { this.currentQuestionIndex 0; } }); }这里axios仅提供.then()链式调用能力避免回调地狱。若未来需接入真实 API如用户登录、题库更新只需将Promise.resolve()替换为axios.get(/api/questions)其他逻辑无需改动——这是典型的「接口抽象」实践。注意iview.min.js是 View UI 组件库基于 Vue 2提供Button、RadioGroup、Modal等组件。timu.html中的单选按钮组i-radio-group v-modeluserAnswers[question.id]即由其渲染v-model双向绑定自动同步userAnswers对象。3.2 答题判分与错题归档的原子操作判分逻辑在submitAnswer()中实现核心是对比用户选择与题干answer字段submitAnswer() { const q this.currentQuestion; const userAns this.userAnswers[q.id]; const isCorrect userAns q.answer; // 更新错题列表正确则移除错误则添加去重 if (isCorrect) { const idx this.wrongQuestions.indexOf(q.id); if (idx -1) this.wrongQuestions.splice(idx, 1); } else { if (!this.wrongQuestions.includes(q.id)) { this.wrongQuestions.push(q.id); } } // 持久化到 localStorage localStorage.setItem(aiShuaTi, JSON.stringify({ userAnswers: this.userAnswers, wrongQuestions: this.wrongQuestions })); }这段代码的关键点在于错题列表wrongQuestions存储的是question.id而非整个题目对象。这样 localStorage 存储量极小ID 是数字每个约 4 字节且与题库 JSON 解耦——即使题库更新只要id不变错题记录仍有效。3.3 模拟考试模式的计时与防作弊机制exam模式需倒计时和禁止切屏。源码中timu.html有div idtimer60:00/div但计时逻辑缺失。补全方案如下// 在 exam 模式启动时执行 startExamTimer() { this.examTimeLeft 60 * 60; // 60分钟秒数 this.examTimer setInterval(() { this.examTimeLeft--; if (this.examTimeLeft 0) { this.submitExam(); // 强制交卷 clearInterval(this.examTimer); } }, 1000); // 监听页面可见性变化防切后台 document.addEventListener(visibilitychange, () { if (document.hidden this.mode exam) { alert(检测到切出考试页面系统将自动交卷); this.submitExam(); } }); }提示document.hidden是标准 API无需额外库。此方案比监听blur事件更可靠因微信内置浏览器等环境blur可能不触发。4. 本地部署与跨平台适配从 file:// 到微信公众号 H5 的平滑迁移4.1 直接双击运行的限制与绕过方案file://协议下Chrome 禁用localStorage安全策略导致答题记录丢失。解决方案分三步启用 Chrome 本地存储启动 Chrome 时加参数--unsafely-treat-insecure-origin-as-securefile:/// --user-data-dir/tmp/chrome-test仅开发用改用http-server本地服务npm install -g http-server http-server -p 8080 # 访问 http://localhost:8080生产环境必须部署到 HTTP(S) 服务器Nginx 配置示例server { listen 80; server_name aishuati.example.com; root /var/www/aishuati; index index.html; # 防止 SPA 路由 404 location / { try_files $uri $uri/ /index.html; } }4.2 微信公众号内嵌 H5 的适配要点当index.html被嵌入微信公众号webview时需解决三个典型问题4.2.1 iOS 微信中 localStorage 丢失问题iOS 微信webview对localStorage有严格限制。必须改用IndexedDB或cookie。推荐轻量方案用js-cookie库替代npm install js-cookie # 或直接引入 cdn script srchttps://cdn.jsdelivr.net/npm/js-cookie3.0.5/dist/js.cookie.min.js/script// 替换 localStorage 操作 Cookies.set(aiShuaTi, JSON.stringify(state), { expires: 365 }); // 一年有效期 const saved Cookies.get(aiShuaTi);4.2.2 微信右上角菜单与返回逻辑微信webview中H5 页面无法控制右上角菜单但可通过WeixinJSBridge监听返回事件// 在页面加载后执行 if (typeof WeixinJSBridge ! undefined) { WeixinJSBridge.on(menu:share:appmessage, function() { // 分享逻辑 }); } // 监听物理返回键Android和左上角返回iOS window.history.pushState(null, , #); window.addEventListener(popstate, function() { if (confirm(确定要退出考试)) { window.history.back(); } else { window.history.pushState(null, , #); } });4.2.3 视口缩放与字体适配微信webview默认禁用双指缩放但需确保viewport设置正确meta nameviewport contentwidthdevice-width, initial-scale1.0, maximum-scale1.0, user-scalableno同时iview.css中的font-size基于px在 iPhone 上可能过小。在css/index.css中追加media screen and (max-width: 414px) { html { font-size: 16px; } .ivu-radio-group { font-size: 18px; } }4.3 四种模式的 URL 参数化改造进阶技巧当前模式切换靠location.href timu.html?moderandom但timu.html未读取参数。补全mounted()中的参数解析mounted() { const urlParams new URLSearchParams(window.location.search); const mode urlParams.get(mode) || random; this.mode mode; // 根据 mode 加载对应题库 if (mode 2017) this.questions window.questions2017; else if (mode 2018) this.questions window.questions2018; }这样即可生成分享链接https://aishuati.example.com/timu.html?modewrongyear2017用户点击直达错题模式并加载 2017 年题库大幅提升传播效率。参数名可选值说明moderandom,chapter,exam,wrong刷题模式year2016,2017,2018指定题库年份需对应questions2017等全局变量chapter第一章,第二章仅chapter模式有效需题库含chapter字段此改造无需后端纯前端解析 URL完全兼容静态托管环境。本文还有配套的精品资源点击获取
返回列表