十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微信小程序题库自动切题:setData驱动下一题实现

微信小程序题库自动切题:setData驱动下一题实现 简介面向微信小程序初学者和需要搭建答题模块的开发者这份题库案例源码完整实现了“选择选项自动切换至下一题”的核心交互并将页面结构、数据管理、事件处理、自定义组件、网络请求与路由跳转等关键知识点融入实际项目适合边看边学。压缩包共33个文件大小约1.9MB内含8个js逻辑文件、4个wxml页面结构、4个wxss样式文件、4个json配置以及11张png截图和1张gif动图可直观对照运行效果便于逐段理解代码。已有297人学习/下载对于刚接触小程序或想快速实现题库功能的开发者有较高参考价值。通过这个案例可以学会利用setData实时刷新题目状态、使用wx:if与wx:for渲染题目选项、通过事件处理完成答案校验并跳转下一题同时还能了解组件化拆分与基础调试方法为自主开发同类功能打下扎实基础。1. 题库小程序里“选完就跳下一题”不是页面跳转是数据流切换微信小程序里的“题库类”页面最容易被写成“每道题一个页面”答题页、对错页、下一题页……实际项目里我一般不用页面跳转。标题里“选择选项切换至下一题”这句话拆开看就是两件事一个点击事件拿到所选索引一个 setData 把当前题号加一。下面就用一个能打包成“案例源码.zip”的最小项目作主线把题库数据模型、选项渲染、判题与自动切换的完整写法讲透——适合正在做答题、问卷、刷题类微信小程序又不想引入完整框架的开发者。所有代码都能在开发者工具里直接跑zip 打包时有哪些文件、哪些别漏也会一并说明。2. 先用 data 把题库和答题状态设计好再写渲染层2.1 题目数据结构不要把选项写死在页面里先明确一点题库页面的核心不是“页面长什么样”而是“题目数据从哪来、选完去哪”。如果直接在 wxml 里写死四个选项标签后面换题、加题、调顺序都要改页面文件基本没法维护。我一般会把一道题抽象成一条对象// data/questions.js 里的结构 const questions [ { id: 1, title: 在微信小程序中声明页面逻辑的文件是, options: [app.js, page.js, component.js, plugin.js], answerIndex: 1, explain: 每个页面由 .js、.wxml、.wxss、.json 四个文件组成。 } ];这里answerIndex存正确选项在options里的下标而不是存字母B。原因是刷题类需求常做“选项乱序”顺序一变字母就失效下标只需要跟着数组走渲染层比较chosenIndex answerIndex一次命中不需要额外映射表。explain字段留给答完后的解析面板虽然不是切题必需但题库场景十有八九要补这步提前预留比后期改结构省事。提示题目量大时把questions单独放data/questions.js用module.exports导出页面里require进来。题库数据与答题逻辑分离后将来接后端接口只需替换数据源。2.2 页面 data 拆成“静态题库”和“动态状态”两组很多初学者会把“当前第几题”“已选答案”“得分”全部塞到题目对象里。问题在于setData每次都会走一次 diff静态数据混在动态状态里既难排查也容易出现“更新一题的答案把整题内容刷掉”的误操作。我习惯把页面 data 拆成两组// pages/quiz/index.js 初始结构 Page({ data: { questionList: [], // 静态题库渲染用 currentIndex: 0, // 当前题号从 0 开始 selectedIndex: -1, // 当前题已选项-1 表示未选 score: 0, // 答对题数 showResult: false, // 是否显示对错反馈 finished: false // 整套题是否结束 } });字段拆分看语义就够了currentIndex是“切换至下一题”时唯一要改的游标selectedIndex记录当前题用户所选score在判题后累加finished在最后一题处理后置为 true用于隐藏选项区、展示结束面板。为什么currentIndex和selectedIndex不合并成一个对象因为返回上一题时需要拿selectedIndex恢复现场它必须独立存在。2.3 选项渲染用 wx:for单选控件选 view 而非 radio-group渲染题目本身很直接view classquestion-card view classquestion-title{{currentIndex 1}}. {{questionList[currentIndex].title}}/view view classoption-list view wx:for{{questionList[currentIndex].options}} wx:keyindex classoption-item {{selectedIndex index ? option-selected : }} >onOptionTap(event) { const idx Number(event.currentTarget.dataset.index); const { selectedIndex, finished } this.data; if (finished || selectedIndex ! -1) return; this.setData({ selectedIndex: idx }); this.judgeAndNext(idx); }finished为 true 时答题已结束不能再响应点击selectedIndex ! -1说明本题已选过直接 return。这套守卫同时解决了自动切题场景里的重复点击问题延迟期间用户再点别的选项在入口处就被拦下。3.2 判题时先更新得分和选中态再决定怎么切拿到idx之后先判题不要先切题。切题动画播放期间 setData 的批量更新顺序不可控得分展示会出现闪烁。更稳的顺序先判题、更新score与showResult让用户看到约 200-300ms 的选中和对错反馈然后再推进currentIndex。judgeAndNext(chosenIndex) { const { questionList, currentIndex, score } this.data; const question questionList[currentIndex]; const isCorrect chosenIndex question.answerIndex; this.setData({ score: isCorrect ? score 1 : score, showResult: true }); if (currentIndex questionList.length - 1) { setTimeout(() { this.setData({ currentIndex: currentIndex 1, selectedIndex: -1, showResult: false }); }, 250); } else { this.setData({ finished: true }); } }这里的 250ms 是我常用的延迟太短50ms用户看不清自己点的是哪个太长600ms 以上刷题节奏被拖慢。若题目带答案解析可选方案是把延迟拉到 800ms 并展示解析再把自动切题改成手动按钮。3.3 “切换下一题”的三种节奏按场景选做题节奏直接决定产品形态三种常见写法的取舍列在下面节奏实现方式适用场景点击立即切题判题和currentIndex 1在同一次 setData 完成极速刷题、背单词延时自动切题setTimeout 200~300ms 后切题考试模拟、单选练习手动点击下一题选项点击只记录selectedIndex按钮触发切题可修改答案、带解析的练习模式“手动点击下一题”数据上最干净selectedIndex只记录选择currentIndex只响应按钮事件。自动切题则必须处理竞态第一题延迟还没结束用户快速点了第二题的选项。3.1 里的selectedIndex ! -1守卫就是为它准备的。题库量大的场景还涉及分批渲染几百道题一次setData传给视图层会掉帧。常见做法是每 10 道一组currentIndex越界时替换questionList为下一组数据切题逻辑不需要改写。4. 直接能跑的题库页面代码与 zip 源码包的文件组织前面代码片段是零散的这里给出一套最小可运行项目结构。整个案例只有 9 个文件适合对照敲完直接导入开发者工具看效果这也是“案例源码.zip”最常见的组成方式。4.1 项目文件结构一个页面加一个数据文件就能跑quiz-demo/ ├── app.js ├── app.json ├── app.wxss ├── project.config.json ├── sitemap.json └── pages/ └── quiz/ ├── index.js ├── index.json ├── index.wxml └── index.wxssapp.json里注册页面路径pages/quiz/index作为第一项是启动页project.config.json是开发者工具识别项目的入口zip 解压后能不能正常导入由它决定。这个文件在新建项目时会自动生成但如果手动整理分享用的源码包漏掉它会导致导入报错。4.2 页面四件套wxml、wxss、js、json 各司其职4.2.1 index.wxml进度条、题目、选项、反馈四块view classpage view classprogress text第 {{currentIndex 1}} / {{questionList.length}} 题/text view classprogress-bar view classprogress-fill stylewidth: {{(currentIndex 1) / questionList.length * 100}}%/view /view /view view classquestion-title{{questionList[currentIndex].title}}/view view classoption-list view wx:for{{questionList[currentIndex].options}} wx:keyindex classoption {{selectedIndex index ? selected : }} {{showResult index questionList[currentIndex].answerIndex ? correct : }} >const questions require(../../data/questions.js); Page({ data: { questionList: [], currentIndex: 0, selectedIndex: -1, score: 0, showResult: false, finished: false }, onLoad() { this.setData({ questionList: questions }); }, onOptionTap(event) { if (this.data.finished || this.data.selectedIndex ! -1) return; const idx Number(event.currentTarget.dataset.index); this.setData({ selectedIndex: idx }); this.judge(idx); }, judge(chosenIndex) { const { questionList, currentIndex, score } this.data; const question questionList[currentIndex]; const correct chosenIndex question.answerIndex; this.setData({ score: correct ? score 1 : score, showResult: true }); if (currentIndex questionList.length - 1) { setTimeout(() { this.setData({ currentIndex: currentIndex 1, selectedIndex: -1, showResult: false }); }, 300); } else { this.setData({ finished: true }); } } });require引入题库而不是在 onLoad 里硬编码数组好处是结果页、错题页可以复用同一份数据。切题时的三个重置字段currentIndex、selectedIndex、showResult缺一不可漏掉showResult会让下一题首次点击沿用上一题的反馈文案。4.2.3 index.wxss选中态与正确态统一用类名控制.page { padding: 24rpx; box-sizing: border-box; } .progress { margin-bottom: 32rpx; font-size: 26rpx; color: #666; } .progress-bar { height: 12rpx; background: #eee; border-radius: 6rpx; margin-top: 12rpx; overflow: hidden; } .progress-fill { height: 100%; background: #07c160; border-radius: 6rpx; transition: width 0.2s ease; } .question-title { font-size: 36rpx; font-weight: 600; line-height: 1.5; margin-bottom: 32rpx; } .option { display: flex; align-items: center; padding: 28rpx 24rpx; border: 2rpx solid #e5e5e5; border-radius: 16rpx; margin-bottom: 24rpx; } .option.selected { border-color: #07c160; background: #f0faf3; } .option.correct { border-color: #07c160; background: #f0faf3; } .option-index { width: 48rpx; height: 48rpx; border-radius: 50%; background: #f2f3f5; text-align: center; line-height: 48rpx; margin-right: 16rpx; flex-shrink: 0; } .option.selected .option-index, .option.correct .option-index { background: #07c160; color: #fff; } .option-text { flex: 1; font-size: 30rpx; } .feedback { margin-top: 24rpx; padding: 20rpx; background: #f7f8fa; border-radius: 12rpx; text-align: center; color: #666; }.selected与.correct会同时出现选对时所以背景色必须一致否则答对本该是绿色却闪现白色。若产品要求“答错不显示正确答案”把 wxml 里showResult index questionList[currentIndex].answerIndex这个条件删掉即可.correct样式保留但不再命中。4.2.4 index.json导航栏标题与背景色{ navigationBarTitleText: 题库练习, backgroundColor: #f7f8fa }navigationBarTitleText决定小程序顶部导航栏标题也用于搜索引擎收录里的页面标识backgroundColor对应 window 背景色page 根节点没给背景时用它兜底。4.3 把案例整理成“源码.zip”时哪些文件不能少、怎么导入打包场景很常见一个 zip 里装着整个项目目录下载者解压后拿开发者工具“导入项目”却报找不到 app.json。原因多数是目录层选错——开发者工具要求选择的目录必须直接包含project.config.json而不是 zip 解压后最外层那层带仓库名的套壳目录。分享源码时建议在压缩前把项目根目录命名为quiz-demo解压后直接选它。另一个常见疑问是“小程序能不能下载 zip 文件”。前端本身不直接解压 zip但可以用wx.downloadFile下载、用FileSystemManager把附件保存到用户目录wx.downloadFile({ url: https://example.com/quiz-data.zip, success(res) { const fs wx.getFileSystemManager(); fs.saveFile({ tempFilePath: res.tempFilePath, filePath: ${wx.env.USER_DATA_PATH}/quiz-data.zip, success() { console.log(附件已保存到用户数据目录); } }); } });wx.env.USER_DATA_PATH对应网上的wx.env.user_data_path说法属性名在基础库里是大写带下划线。保存成功后基本的 zip 解压能力基础库未内置需要引入 js 端 zip 解析库、把文件读成 ArrayBuffer 后处理。题目量大的项目常用这个方案下发题库包避免把几百道题硬编码进代码包。5. 进阶答题进度存储、错题回溯与连点防抖的边界处理5.1 用 Storage 在每次切题前落盘意外退出不丢进度自动切题场景里连续做几十题中途退出再进来进度不该清零。做法是在 judge 方法切题 setData 之后同步写一次 Storagewx.setStorageSync(quiz_progress, { currentIndex: this.data.currentIndex 1, score: this.data.score });写入的currentIndex是“下一题下标”而不是当前题下标onLoad 时读出来直接作为恢复点。onLoad 里读取后要单独 setData不能合并进questionList同一次 setData避免大对象每次都做全量 diff。5.2 返回上一题时selectedIndex 不只是重置还要恢复带“上一题”按钮的需求常见错误是只做currentIndex - 1然后把selectedIndex重置成 -1用户回来发现刚才选的答案丢了。正确做法是维护答案数组每题一个记录this.setData({ [answerList[${currentIndex}]]: chosenIndex });setData支持用数组下标作为 key 路径更新数组中的某一项避免整数组重传。回退时selectedIndex从answerList里取而不是 -1。这个数组后续还能复用做错题本时直接遍历answerList与questionList对照错题集就出来了。5.3 连点防抖的三种做法自动切题与手动切题都面对同一个问题用户连续快速点击setTimeout还没触发又点了一次。处理分三个层次selectedIndex ! -1在业务逻辑层拦截this._lock布尔值在方法入口加锁锁在延迟回调里解除wxml 上配合 pointer-events 禁用点击。实际项目里第二种最常用代码量最小onOptionTap(e) { if (this._lock) return; this._lock true; // 判题与切题并延时 300ms setTimeout(() { this._lock false; }, 300); }_lock挂在this而不是data上因为它不需要触发视图更新。把锁的释放放在 setTimeout 回调里而不是切题 setData 之后这 300ms 的间隔就是自动切题与连点防抖之间的那个最小共识如果需求改成允许修改答案把锁的释放挪到手动“下一题”按钮的 tap 里即可。本文还有配套的精品资源点击获取
返回列表