简介:本资源是一个面向微信小程序初学者的轻量级实战项目——“投骰子”小游戏,适用于移动开发入门者、前端学习者及微信生态开发者,帮助快速掌握小程序核心开发范式。压缩包共12个文件(9KB),包含4个JS逻辑文件(含页面主逻辑与工具函数)、4个JSON配置文件(app.json、project.config.json等)、2个WXSS样式文件、1个WXML视图文件及1份README说明文档,结构清晰、模块分离明确,便于理解页面生命周期、数据绑定、事件响应与随机数生成等关键机制。已有1484人学习下载,项目代码简洁规范,完整复现了从界面搭建(按钮+结果展示)到交互逻辑(bindtap触发rollDice、setData更新视图)的全流程,特别适合作为小程序开发第一课的练手案例,亦可作为扩展功能(如动画反馈、多骰子联动、历史记录)的优质基底。
1. 一个能立刻跑起来的微信小程序投骰子游戏:不是玩具,是理解小程序生命周期与状态管理的最小闭环
你打开微信开发者工具,新建项目,删掉默认的pages/index/index里所有花哨动画,只留一个按钮和一个数字——点击后随机显示 1 到 6 ——这看似简单的“投骰子”,恰恰是微信小程序开发中最典型的状态驱动交互范式。它不依赖后端、不涉及复杂路由,却完整覆盖app.json的页面注册逻辑、project.config.json的本地开发配置、setData的异步更新机制、以及WXML与JS之间数据绑定的边界控制。很多初学者卡在“为什么点按钮没反应”,本质是没理清this.setData()和直接赋值的区别;更多人改完app.json报错[app.json 文件内容错误],其实是忽略了pages数组必须是非空字符串路径、且所有路径需真实存在。本文面向刚通过「搭建微信小程序的流程」完成环境配置的开发者,也服务于正在做「微信小程序毕业设计」需要快速验证交互逻辑的同学——我们不写框架、不套模板,就用原生小程序语法,从零写出可调试、可扩展、可提交体验版的投骰子实例。
2. 用app.json注册页面并配置基础结构:为什么pages数组顺序决定 tabBar 显示优先级
微信小程序的页面组织完全由app.json控制,它不是辅助配置文件,而是运行时页面调度的唯一依据。app.json中的pages字段定义了小程序所有合法页面路径,window字段控制全局导航栏样式,tabBar决定底部标签栏行为。对“投骰子”这类单页轻交互应用,tabBar可省略,但pages必须精确声明,否则开发者工具会直接报错app.json: pages 字段不能为空或更具体的[app.json 文件内容错误]。
2.1 创建标准目录结构并写入app.json
首先,在项目根目录下创建以下结构(注意大小写与斜杠方向):
├── app.js ├── app.json ├── app.wxss ├── project.config.json ├── pages/ │ └── dice/ │ ├── dice.js │ ├── dice.wxml │ ├── dice.wxss │ └── dice.json提示:
dice.json是可选页面级配置,用于覆盖app.json中的window设置。本例中暂不使用,但必须存在(内容为空对象{}),否则dice页面无法被正确识别。
然后编辑app.json,关键字段如下:
{ "pages": [ "pages/dice/dice" ], "window": { "navigationBarTitleText": "投骰子", "navigationBarBackgroundColor": "#4a9ff5", "navigationBarTextStyle": "white" }, "style": "v2", "sitemapLocation": "sitemap.json" }参数说明:
"pages":必须为绝对路径字符串数组,路径以pages/开头,不带.wxml后缀;顺序决定页面栈压入顺序,首个路径即启动页;"navigationBarTitleText":顶部导航栏标题,中文无需编码;"style": "v2":强制启用新版组件样式(如button默认无边框),避免旧版兼容问题;"sitemapLocation":搜索收录配置,开发阶段可保留默认值。
若此处路径写成"dice"或"pages/dice",或数组为空,开发者工具会在控制台抛出[app.json 文件内容错误]app.json:并中断编译。这是最常被忽略的硬性校验规则。
2.2 配置project.config.json确保本地开发环境一致
project.config.json不影响线上运行,但决定开发者工具如何加载项目。尤其当团队协作或切换设备时,miniprogramRoot和libVersion的错配会导致lib: 3.8.10类似提示失效。
{ "description": "微信小程序投骰子实例", "packOptions": { "ignore": [] }, "setting": { "urlCheck": true, "es6": true, "postcss": true, "minified": true, "newFeature": true, "coverView": true, "nodeModules": false, "autoAudits": false, "showShadowRootInWxmlPanel": true, "scopeDataCheck": false, "enhanceMultiThread": false, "useMultiWindow": false, "babelSetting": { "ignore": [], "disablePlugins": [], "outputPath": "" } }, "compileType": "miniprogram", "libVersion": "3.8.10", "appid": "wx1234567890abcdef", "projectName": "投骰子", "debugOptions": { "hidedInDevtools": [] }, "isGameProject": false, "simulatorType": "wechat", "simulatorPluginLibVersion": {}, "condition": { "search": { "current": -1, "list": [] }, "conversation": { "current": -1, "list": [] }, "game": { "currentL": -1, "list": [] }, "miniprogram": { "current": 0, "list": [ { "id": 0, "name": "投骰子", "pathName": "pages/dice/dice", "query": "", "scene": null } ] } } }关键参数解释:
"libVersion": "3.8.10":对应微信客户端基础库版本,必须与真机调试环境匹配;若填错(如写成3.8.9),可能触发env: windows,mp,1.06.2209190; lib: 3.8.10这类版本不一致警告;"appid":测试号可用wx1234567890abcdef占位,正式发布前替换为真实 AppID;"condition.miniprogram.list":定义「编译模式」入口页,确保点击「编译」时自动打开pages/dice/dice,而非默认首页。
此时保存所有文件,重启开发者工具,应能看到空白页面加载成功,控制台无app.json错误提示——这是后续所有交互开发的前提。
3. 实现骰子核心逻辑:setData的三重约束与Math.random()的正确用法
骰子的本质是生成 1~6 的整数随机数,并将结果同步到视图层。看似一行代码Math.floor(Math.random() * 6) + 1就能解决,但在小程序中,数据变更必须通过this.setData()触发视图更新,直接this.diceValue = ...不会刷新 WXML 绑定。
3.1 编写dice.wxml:声明式绑定与事件监听
<!-- pages/dice/dice.wxml --> <view class="container"> <text class="title">🎲 投骰子</text> <view class="dice-display" bindtap="rollDice"> <text class="dice-number">{{diceValue}}</text> </view> <button class="roll-btn" bindtap="rollDice">点击投掷</button> <view class="history"> <text class="history-title">历史记录(最近5次)</text> <view class="history-list"> <block wx:for="{{history}}" wx:key="index"> <text class="history-item">{{item}}</text> </block> </view> </view> </view>结构说明:
{{diceValue}}是 Mustache 语法,绑定dice.js中data.diceValue;bindtap="rollDice"声明点击事件,对应dice.js中rollDice方法;<block wx:for>用于循环渲染历史记录,wx:key避免列表复用错误;- 所有
class名称需在dice.wxss中定义,否则样式不生效。
3.2 编写dice.js:setData的原子性、异步性与路径更新
// pages/dice/dice.js Page({ data: { diceValue: 0, history: [] }, rollDice() { const newValue = Math.floor(Math.random() * 6) + 1; // ✅ 正确:使用 setData 更新状态 this.setData({ diceValue: newValue, history: [newValue, ...this.data.history.slice(0, 4)] }, () => { console.log('骰子已更新为:', this.data.diceValue); }); }, onReady() { console.log('骰子页面已就绪'); } });关键细节解析:
Math.random()返回[0,1)区间浮点数,*6得[0,6),Math.floor()截断为0~5,+1得1~6—— 这是唯一符合骰子语义的写法;this.setData()必须传入对象,不能传字符串路径(如this.setData('diceValue', 3)是错误的);history更新采用「新数组拼接」:[newValue, ...this.data.history.slice(0, 4)],保证仅保留最近 5 条,避免内存泄漏;setData第二个参数是回调函数,在视图更新完成后执行,适合日志或后续动作;onReady是页面初次渲染完成的钩子,比onLoad更晚触发,适合初始化动画或 DOM 查询。
注意:若在
rollDice中写this.diceValue = newValue,视图不会变化,因为小程序不监听原始属性变更;若setData传入null或undefined,会清空对应字段,导致{{diceValue}}显示为空。
3.3 编写dice.wxss:响应式布局与视觉反馈
/* pages/dice/dice.wxss */ .container { display: flex; flex-direction: column; align-items: center; padding: 40rpx 0; background-color: #f8f9fa; } .title { font-size: 48rpx; font-weight: bold; margin-bottom: 60rpx; color: #333; } .dice-display { width: 200rpx; height: 200rpx; border-radius: 100rpx; background: linear-gradient(135deg, #4a9ff5, #1e6bc0); display: flex; justify-content: center; align-items: center; margin-bottom: 40rpx; box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.15); } .dice-number { font-size: 80rpx; font-weight: bold; color: white; text-shadow: 0 2rpx 4rpx rgba(0,0,0,0.3); } .roll-btn { width: 300rpx; height: 80rpx; background-color: #4a9ff5; color: white; font-size: 32rpx; border-radius: 8rpx; margin-bottom: 60rpx; } .history { width: 90%; background: white; border-radius: 12rpx; padding: 30rpx; box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05); } .history-title { font-size: 32rpx; color: #666; margin-bottom: 20rpx; } .history-list { display: flex; flex-wrap: wrap; gap: 12rpx; } .history-item { display: inline-block; padding: 8rpx 16rpx; background-color: #eef7ff; color: #4a9ff5; border-radius: 6rpx; font-size: 28rpx; }单位与适配要点:
- 使用
rpx(responsive pixel)实现屏幕宽度自适应,750rpx = 屏幕宽度; box-shadow模拟轻微立体感,增强骰子点击反馈;flex-wrap: wrap让历史记录自动换行,避免溢出;- 所有颜色值用十六进制,避免
rgb()在部分基础库版本中解析失败。
此时点击「点击投掷」按钮,数字应实时变化,历史记录滚动更新——这是小程序数据流的最小可行验证。
4. 调试与排错:定位app.json错误、setData失效与真机差异的三类典型场景
即使代码逻辑正确,开发中仍会遇到app.json校验失败、setData不刷新、真机表现异常等问题。这些不是 Bug,而是小程序运行机制的显性暴露。
4.1app.json文件内容错误的三种高频原因及修复方案
| 错误现象 | 根本原因 | 修复操作 |
|---|---|---|
[app.json 文件内容错误]app.json:(无具体提示) | pages数组中存在不存在的路径,或路径末尾多了一个/ | 检查pages/dice/dice是否真实存在四个文件(.js/.wxml/.wxss/.json),确认路径无拼写错误 |
app.json: pages 字段不能为空 | pages数组为空或未声明 | 确保app.json中"pages": ["pages/dice/dice"]存在且非空 |
app.json: window.navigationBarTitleText 字段类型错误 | navigationBarTitleText值为null或数字 | 改为字符串,如"投骰子" |
提示:开发者工具右上角「详情」→「本地设置」→ 勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」可临时屏蔽部分网络相关报错,但
app.json校验无法绕过。
4.2setData不生效的三个检查点
作用域错误:在
setTimeout或Promise.then中调用setData时,this指向可能丢失。
✅ 正确写法:setTimeout(() => { this.setData({ diceValue: 3 }); // 箭头函数保持 this }, 500);数据路径错误:
setData仅支持一级属性或点路径(如obj.key),不支持嵌套对象深层修改。
❌ 错误:this.data.obj = { a: 1 }; this.setData({ 'obj.a': 2 }); // 无效,obj 未在 data 中声明✅ 正确:
this.setData({ obj: { a: 2 } }); // 整体替换异步竞争:连续多次
setData可能被合并,导致中间状态丢失。
✅ 解决:使用回调链或await this.nextTick()(基础库 2.25.2+):await this.nextTick(); this.setData({ diceValue: 4 });
4.3 真机调试差异:iOS 渲染机制与wx:for性能陷阱
在 iOS 设备上,<block wx:for>渲染大量历史记录时可能出现卡顿,这是因为 iOS WebView 对动态列表的重绘优化较弱。
优化方案:限制历史记录长度,并添加wx:key强制 key 唯一性:
<!-- dice.wxml --> <block wx:for="{{history}}" wx:key="item_{{index}}"> <text class="history-item">{{item}}</text> </block>同时在 JS 中严格控制数组长度:
// dice.js this.setData({ history: [newValue, ...this.data.history.slice(0, 4)] });这样既保证最多显示 5 条,又避免slice(0, 100)导致内存膨胀。真机测试时,打开「调试」→「Performance」可观察帧率,低于 50fps 即需优化。
5. 进阶技巧:为骰子添加物理动效与本地持久化存储
纯数字显示缺乏游戏感。我们通过wx.createAnimation()添加投掷动画,并用wx.setStorageSync()保存历史记录,实现关闭小程序后再次打开仍可见上次结果。
5.1 用wx.createAnimation实现骰子旋转动效
// dice.js Page({ data: { diceValue: 0, history: [], animationData: {} }, rollDice() { const animation = wx.createAnimation({ duration: 600, timingFunction: 'ease-in-out' }); // 添加旋转动画 animation.rotateZ(360).step(); this.setData({ animationData: animation.export() }); // 动画结束后更新数值 setTimeout(() => { const newValue = Math.floor(Math.random() * 6) + 1; this.setData({ diceValue: newValue, history: [newValue, ...this.data.history.slice(0, 4)], animationData: {} // 重置动画 }); }, 600); } });<!-- dice.wxml --> <view class="dice-display" animation="{{animationData}}" bindtap="rollDice"> <text class="dice-number">{{diceValue}}</text> </view>动画参数说明:
duration: 600:动画持续 600ms,过短显得突兀,过长降低响应感;timingFunction: 'ease-in-out':先慢后快再慢,模拟真实旋转惯性;animation.export()返回序列化动画对象,必须赋给animation属性才能生效;setTimeout时间需与duration严格一致,否则数值更新与动画不同步。
5.2 使用wx.setStorageSync持久化历史记录
// dice.js onLoad() { try { const saved = wx.getStorageSync('diceHistory') || []; this.setData({ history: saved }); } catch (e) { console.error('读取本地历史失败', e); } }, rollDice() { // ... 动画逻辑 ... setTimeout(() => { const newValue = Math.floor(Math.random() * 6) + 1; const newHistory = [newValue, ...this.data.history.slice(0, 4)]; this.setData({ diceValue: newValue, history: newHistory, animationData: {} }); // 持久化保存 try { wx.setStorageSync('diceHistory', newHistory); } catch (e) { console.error('保存本地历史失败', e); } }, 600); }存储注意事项:
wx.setStorageSync最大容量为 10MB,diceHistory数组远小于此;wx.getStorageSync返回null时需|| []提供默认值,避免slice报错;- 不建议在
setData回调中调用setStorageSync,因setData本身有延迟,可能导致数据不一致。
此时重新启动小程序,历史记录依然存在——这是「微信小程序页面设计」中提升用户体验的关键一环,也是「微信小程序毕业设计」答辩时可展示的实用功能点。
本文还有配套的精品资源,点击获取