微信小程序诗词答题源码详解:题库改造与性能优化
发布时间:2026/9/15 1:53:01 作者:尧图编辑部 阅读量:1,286

简介这是一份面向微信小程序学习者和高校实践项目的诗词答题小程序源码包可直接导入微信开发者工具运行适用于毕业设计、课程设计与期末大作业等场景能帮助读者快速理解小程序页面结构、事件绑定、数据绑定与基础API调用方式。压缩包共20个文件以json、js、wxml、wxss为主要类型json负责全局与页面配置js承载答题逻辑、计分与交互处理wxml和wxss分别构建页面结构与样式另含readme项目说明与项目配置文件整体仅173KB目录规整便于按模块阅读。目前已有303人学习浏览。源码涵盖题库组织、随机出题、答案判分、结果统计与页面跳转等完整功能并保留utils工具模块供扩展既可作为微信小程序开发的入门范例也能在此基础上修改题库和界面适配不同诗词主题的学习场景兼具课程设计与个人练手价值。1. 拿到「微信小程序诗词答题小程序源码.zip」后先别急着找题库入口很多人拿到“微信小程序诗词答题小程序源码.zip”以后第一反应是翻目录找题库文件想着把几百首诗词塞进去就能跑。但这类源码包真正卡人的地方往往不是题目数量而是工程结构答题页在 app.json 里注册了没有题库是本地 JS 还是云数据库选项洗牌后正确答案下标是否同步更新。这份 zip 打开后先理解目录再动手改比反复试错省一个下午。下面按解压、跑通、改题库、排错、上线前优化的顺序走一遍新手可以照做有经验的人直接跳到第 4 章看数据模型。2. 解压后对照工程结构把诗词答题小程序的页面注册关系理清2.1 先验证压缩包完整性zip 损坏比缺文件更难排查不要跳过完整性验证这一关。很多从网盘下载的源码包在传输中丢字节Windows 自带解压有时能解出文件但内部 CRC 校验已经报错小程序编译时才在某个 wxml 上突然失败。命令行进入 zip 所在目录先执行unzip -t 微信小程序诗词答题小程序源码.zip-t参数是 test 模式只验证压缩包内每个文件的 CRC不解压。看到No errors detected in compressed data再解压。如果提示bad CRC说明压缩包本身坏了重新下载通常比手工修复更省事。解压时用unzip 微信小程序诗词答题小程序源码.zip -d poem-quiz-d指定解压目录避免几十个文件直接撒在桌面。如果压缩包带了密码建议回下载页面找说明不要使用网上流传的解锁 exe这类工具经常捆绑木马。解压完成后先看一级目录结构通常会有app.js、app.json、pages和project.config.json。如果看到的是多层嵌套文件夹说明上传者把多个工程一起打包需要再打开一层找到包含app.json的目录之后导入项目时指到那一层。2.2 微信小程序源码的目录约定页面、组件和工具函数分开放一份原生微信小程序源码的顶层结构并不复杂比较典型的诗词答题项目会长这样poem-quiz/ ├─ app.js ├─ app.json ├─ app.wxss ├─ project.config.json ├─ pages/ │ ├─ index/ │ ├─ quiz/ │ └─ result/ ├─ components/ │ └─ poem-card/ ├─ utils/ │ ├─ poems.js │ └─ format.js └─ assets/app.json是全局配置里面的pages数组决定哪些页面会被编译进来数组第一项就是启动页。不要把index当成固定首页很多源码作者会把quiz放在第一位打开就直接进答题。pages/quiz/下会有四个同名文件quiz.js是答题逻辑quiz.wxml是页面结构quiz.wxss是界面样式quiz.json是页面级配置。components存放自定义组件比如诗词卡片、答题进度条这类被多个页面复用的代码抽成组件不要在wxml里复制好几段相同结构。utils/poems.js通常是题库也可能是远程请求的封装需要打开看模块导出方式。如果源码里用了自定义组件页面 json 中必须有usingComponents注册。漏掉这一步时组件标签不会被渲染控制台也不会直接报错只会在模拟器里看到空白区域。这是源码 zip 项目里最容易忽略的一环。2.3 app.json 与页面 json 的配置项决定了答题页启动状态页面 json 不是数据源它只负责窗口表现。一个能跑的答题页至少要有这么一段配置{ navigationBarTitleText: 诗词答题, navigationBarBackgroundColor: #2c3e50, enablePullDownRefresh: false, backgroundTextStyle: light }navigationBarTitleText是顶部标题也是转发分享时默认标题的来源之一。enablePullDownRefresh建议在答题页设为 false诗词题大多不需要下拉刷新一旦用户做到第 10 题误触下拉而源码的onPullDownRefresh没有重置当前题号页面就会回到第 1 题。下表是几个高频配置项的判断标准配置项作用答题项目建议navigationBarTitleText顶部导航标题写成“诗词答题”便于分享识别enablePullDownRefresh是否允许下拉刷新答题页设为 falsebackgroundTextStyle下拉刷新 loading 的颜色深色导航栏配 light如果你还打算改自定义导航栏会遇到“微信小程序顶部导航栏高度”的适配问题。普通项目不用手写导航栏高度微信会根据机型自动算只有用navigationStyle: custom时才需要用wx.getWindowInfo()读取statusBarHeight再给内容区做padding-top否则刘海屏上内容会被摄像头区域挡住。3. 在微信开发者工具里跑通诗词答题小程序的最小可运行步骤3.1 导入项目选目录而不是选 zipuni-app 工程还要先过 HBuilderX微信开发者工具不认 zip 文件必须先把压缩包解压再打开开发者工具点击“导入项目”项目目录指向解压后的poem-quiz文件夹。如果目录下找不到project.config.json工具会提示“不是小程序项目”这时多半是解压出了两层目录入口应选择包含app.json的那一层。AppID 的处理方式是源码里已有的 AppID 如果属于原作者你没法直接用于发布建议在导入弹窗里改成自己的小程序 AppID或者选择“测试号”。测试号能调试和预览但不能用云开发也不能上传为正式版本。想一次性改干净就在导入前编辑project.config.json{ appid: wx1234567890abcdef, projectname: poem-quiz, miniprogramRoot: miniprogram/ }appid填你自己的小程序 AppIDminiprogramRoot是相对路径只有源码目录里还有一层miniprogram/时才需要保留如果app.json就在根目录这一项可以删掉。另外要判断源码是不是 uni-app 生成的解压后如果看到pages.json而没有app.json这多半是 uni-app 工程不能直接导入微信开发者工具需要先用 HBuilderX 打开项目再点“运行 → 运行到小程序模拟器 → 微信开发者工具”等 HBuilderX 编译完成微信开发者工具才会自动打开生成后的小程序工程。3.2 跑通前先做三处检查页面注册、接口域名、npm 构建第一app.json里的pages数组必须包含所有通过wx.navigateTo跳转的页面否则点击“开始答题”时控制台直接报page not found。第二如果源码用了普通后端接口在开发者工具“详情 → 本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”这个选项只在开发期生效上线前必须关闭。第三源码目录中如果存在node_modules或miniprogram_npm导入后要执行“工具 → 构建 npm”否则依赖库的模块找不到。用命令行检索所有网络请求也是个好习惯grep -rn wx.request\|https:// pages/ utils/ app.js结果里出现https://api.example.com这类地址说明源码必须有服务端配合。如果暂时不打算搭后端可以先把题库改成utils/poems.js的本地导出下一章会讲数据结构。还有一种很常见的情况源码在app.js中写死了云开发环境 ID导入后题库空白控制台只报Cloud init error这是云环境不属于你的缘故要么在代码里替换为自己创建的云环境 ID要么放弃云开发、把数据改成本地加载。3.3 用 Network 面板排查加载链路不需要额外抓包工具不少人在小程序里遇到“题目出不来”就急着找抓包工具实际上微信开发者工具“调试器 → Network”足够看到页面请求、静态资源和异步接口。打开 Network 后重新编译重点看三类信息请求 URL 是否正常、状态码是不是 4xx 或 5xx、返回 JSON 里的业务 code 是否符合预期。没有网络请求但页面空白基本是本地数据没有成功require先检查utils/poems.js有没有语法错误。下面是一张处理高频报错的速查表现象原因处理点击开始无反应页面未注册或路径大小写不一致在 app.json 的 pages 数组补齐路径并核对大小写题目列表为空poems.js 路径错误或没有导出console.log(require(../../utils/poems.js)) 看导出对象域名校验失败请求不是 HTTPS 或未加白名单开发期勾选不校验域名上线前在后台配置合法域名资源加载缓慢音频和背景图全部放在 assets拆到 CDN/云存储或改成分包页面跑通最小可运行步骤之后才算真正进入业务逻辑替换题库。4. 自己改题库诗词答题小程序的数据模型与判题逻辑4.1 标准题库字段题干、选项、答案、赏析一个都不能少大多数诗词答题源码会把题库集中在utils目录下常见形式是utils/poems.js文件末尾用module.exports导出。一个能同时支撑选择题和填空题的数据结构如下[ { id: 1, type: choice, question: 《静夜思》中「床前明月光」的下一句是, options: [疑是地上霜, 低头思故乡, 举头望明月, 对影成三人], answer: 0, analysis: 出自李白《静夜思》床前明月光疑是地上霜。 }, { id: 2, type: fill, question: 「海内存知己」的下一句是____。, answer: 天涯若比邻, analysis: 出自王勃《送杜少府之任蜀州》。 } ]type字段决定答题页渲染的是选项按钮还是输入框answer在选择题里存正确选项下标从 0 开始填空题存字符串。字段说明如下字段类型含义typechoice / fill选择题或填空题optionsstring[]选择题选项下标从 0 开始answernumber / string正确选项下标或填空答案analysisstring答完后的解析文案不要只给options和answer不给analysis。诗词答题和普通题库类小程序最大的差异在于“解析感”答错后能看到完整出处和全诗用户才会觉得有价值。替换题库时保持字段名不变页面逻辑基本不用动。如果题目超过 500 首本地数组会明显撑大包体最好换成云开发数据库或按分类请求加载。4.2 随机抽题和选项洗牌先复制再洗避免影响正确答案诗词题用户很容易记住选项位置所以每局都应该洗牌。常见错误是直接在onLoad里this.setData({ questions: poems })这样每局顺序完全一样另一种错误是sort(() Math.random() - 0.5)这个写法概率分布不均匀。我一般会写一个单独的洗牌函数并同步更新答案下标const poems require(../../utils/poems.js); function shuffle(arr) { const a arr.slice(); for (let i a.length - 1; i 0; i--) { const j Math.floor(Math.random() * (i 1)); [a[i], a[j]] [a[j], a[i]]; } return a; } function buildQuiz(source, count) { return source.slice(0, count).map(item { if (item.type choice) { const correct item.options[item.answer]; const options shuffle(item.options); return { ...item, options, answer: options.indexOf(correct) }; } return { ...item }; }); } Page({ data: { questions: [], current: 0 }, onLoad() { this.setData({ questions: buildQuiz(poems, 10) }); } });逻辑说明source.slice(0, count)先截断避免一局读入全部题库导致首次渲染卡顿shuffle使用 Fisher-Yates 算法从尾部向前遍历每个位置与随机位置交换概率分布均匀。洗牌后必须用options.indexOf(correct)重新定位正确答案否则答案会永久指向旧下标。count参数表示一局的题目数通常取 5、10、20如果想从全题库随机抽且不重复就把source.slice(0, count)改成先洗牌再slice。整个流程不修改原始poems多局游戏之间不会互相污染。4.3 判题逻辑与填空题归一化答案比对发生在“干净数据”上选择题用数据下标比较最可靠在 wxml 绑定>onTapOption(e) { const { index } e.currentTarget.dataset; const { current, questions } this.data; const q questions[current]; if (index q.answer) { this.setData({ selected: index, correctCount: this.data.correctCount 1 }); } else { this.setData({ selected: index }); } }这里用下标而不是文本比较能规避用户复制题干时带入不可见字符。填空题更麻烦输入内容可能带中文标点、首尾空格甚至 iOS 输入法会混入全角字符。判定前先归一化function normalize(str) { return String(str || ) .replace(/[。、\s]/g, ) .trim(); } if (normalize(inputValue) normalize(q.answer)) { // 答对处理 }正则把中文逗号、句号、问号和所有空白符全部删除再做全等比较。注意analysis里的标点不影响判定因为比较的是用户输入和answer字段。对于“给出上句填下句”的题最好把normalize放到utils/format.js统一导出答题页和结果页都引用同一个判题入口避免两处逻辑不一致导致分数对不上。5. 诗词答题小程序常见掉分点setData、分包与倒计时清理5.1 setData 大对象是答题页卡顿的隐形元凶答题过程中反复执行this.setData({ questions: this.data.questions })是非常典型的误用。小程序每次调用setData都会把数据以 JSON 形式从逻辑层传到视图层整个题库数组反复全量传输中低端 Android 上会明显卡顿。正确做法是只更新和当前视图相关的字段当前题号、选中态、得分和按钮禁用状态。this.setData({ current: next, selected: -1, answered: false, correctCount: this.data.correctCount 1 });current是当前题号selected重置为-1让上一次选中态消失answered控制解析区域是否展示。题库数组只在onLoad时setData一次后面不要整组覆盖。如果需要展示解析单独用analysis字段缓存当前题的文本而不是把questions[current]整个传进 data。另外图片资源不要转成 base64 字符串塞进 wxml这类数据会直接撑大setData报文。5.2 主包超过 2MB 时用分包和远程资源别把音频塞进 assets诗词答题项目特别容易超包每首诗配一张水墨图、一段朗诵音频几十首就能把主包塞满。微信开发者工具上方会直接显示主包大小超过限制就无法真机预览更无法上传。通常首页、答题页和结果页放主包诗词详情页和资源页拆到分包。app.json中的分包写法{ pages: [ pages/index/index, pages/quiz/index, pages/result/index ], subPackages: [ { root: packagePoem, pages: [ pages/detail/index ] } ] }root是分包根目录pages下面不能加packagePoem/前缀。跳转时 URL 要写成/packagePoem/pages/detail/index。如果源码里出现了wx.env.user_data_path说明作者尝试把远程资源下载到用户目录这个路径在真机上对应每个用户自己的存储空间适合缓存图片和音频但要注意及时清理失效文件。远程下载依赖合法域名开发期可以临时开“不校验域名”真机预览必须把下载域名加到小程序后台的 downloadFile 合法域名列表里。资源类型建议位置说明题目 JSutils/poems.js跟随主包控制题目量音频/大图CDN 或云存储可控缓存避免打爆包体用户缓存wx.env.user_data_path二次读取更快需要清理机制5.3 倒计时不清空会导致每局时间越走越快诗词答题一般都有单题倒计时最常见的实现是setInterval每秒减一次。如果只在onLoad里启动定时器、在onUnload里清理用户中途切到后台再回来定时器仍然在走就会出现“离开 3 分钟回来只剩 0 秒”的诡异体验。正确做法是区分onHide和onShowPage({ onLoad() { this.startTimer(); }, onHide() { this.clearTimer(); }, onUnload() { this.clearTimer(); }, onShow() { if (this.data.remaining 0 !this.data.answered) { this.startTimer(); } }, startTimer() { this.clearTimer(); this.timer setInterval(() { if (this.data.remaining 0) { this.clearTimer(); this.handleTimeout(); return; } this.setData({ remaining: this.data.remaining - 1 }); }, 1000); }, clearTimer() { if (this.timer) { clearInterval(this.timer); this.timer null; } } });核心是把timer保存在this上不要放进 data因为 data 里的数据会被序列化到视图层定时器 ID 没有意义还会造成额外开销。onHide清空定时器onShow时如果当前题还没答完就重新开始。remaining初始值建议从options参数或全局配置读取比如totalTime: 15。如果源码里只有onUnload没有onHide当用户按 Home 键切后台再回来倒计时会被瞬间扣光。6. 把「诗词答题」的启动加载页做成能过审的自定义封面6.1 用首屏页面模拟品牌启动页注意别让 redirect 变成返回陷阱原生小程序的 launch 启动图只能配置固定图片不支持自定义动画。常见做法是把一个pages/loading/index页面放到app.json的pages数组第一位让用户先看到品牌色和一句诗词再跳到真正的答题首页。注意这个页面不能停留太久超过 3 秒会明显增加跳出率1.5 到 2 秒比较合适。app.json调整为{ pages: [ pages/loading/index, pages/index/index, pages/quiz/index ] }然后在pages/loading/index.js中实现展示与跳转Page({ data: { quote: 海内存知己 }, timer: null, onLoad() { const quotes [海内存知己, 天涯若比邻, 清风徐来, 天地一沙鸥]; this.setData({ quote: quotes[Math.floor(Math.random() * quotes.length)] }); this.timer setTimeout(() { wx.redirectTo({ url: /pages/index/index }); }, 1800); }, onUnload() { if (this.timer) { clearTimeout(this.timer); } } });这里用wx.redirectTo而不是wx.navigateTo目的是把 loading 页从页面栈移除避免用户按左上角返回时又看到启动页。如果index被配置成 tabBar 页面redirectTo不允许跳转需要换成wx.switchTab。timer存在this上onUnload里清理防止页面销毁后定时器再执行。验证页面栈是否干净可以打开开发者工具“调试器 → 页面栈”跳转后里面应该只剩一个页面。配合分享可以进一步把加载页做成流量入口。在答题页里重写onShareAppMessageonShareAppMessage() { return { title: 我在诗词答题已答对${this.data.correctCount}题来挑战, path: /pages/quiz/index?fromshare }; }path必须指向已注册页面路径fromshare是自定义参数方便统计分享回流。测试时用两个微信号互发检查从分享卡片进入后onLoad(options)中能否正确拿到fromshare拿不到时多半是path少写了前导/或者参数被解析到其他位置。本文还有配套的精品资源点击获取