做一款只需要一个按钮的手电筒
用 uni-app 为微信小程序实现一个克制、可靠的手电筒:一个主操作,清楚的状态反馈,以及完整的权限与异常处理。
手电筒大概是最容易被低估的小工具之一。它的目标只有一个:用户按下按钮,灯亮;再按一次,灯灭。功能越少,每一个细节反而越难藏起来。
这个项目位于 tinyapp/hello,使用 uni-app 和 Vue 3 编写,目标运行环境是微信小程序。整个产品只有一个页面,但它需要同时处理视觉反馈、相机权限、闪光灯状态、应用生命周期和设备异常。
先把界面减到只剩必要信息
页面保留了三组信息:顶部显示“手电筒”和当前状态,中间是唯一的电源按钮,底部说明为什么需要相机权限。没有工具栏、模式切换或额外入口,因为这些元素都不能帮助用户更快地点亮手电筒。
按钮采用接近实体开关的同心圆结构。关闭时使用深灰金属质感;开启后,边框、电源图标和状态点变为暖黄色,同时出现克制的环境光。颜色在这里不是装饰,而是状态的一部分。
交互文案也随着状态变化:
- 默认状态显示“已关闭”和“轻触开启”;
- 启动过程中显示“正在开启”和“请稍候”,并暂时禁用按钮;
- 开启后显示“已开启”和“轻触关闭”;
- 权限被拒绝后,主操作会变成“轻触授权”。
用 camera 组件驱动闪光灯
微信小程序没有独立的手电筒开关接口。实现中挂载一个几乎不可见的后置 camera 组件,并将 flash 设置为 torch,借此让闪光灯保持常亮。
<camera
v-if="cameraMounted"
device-position="back"
flash="torch"
@initdone="handleCameraInit"
@error="handleCameraError"
@stop="handleCameraStop"
/>
这里不能把“组件已挂载”等同于“灯已经亮了”。页面先进入 isStarting 状态,等 initdone 回调到达后才把 isOn 设为 true。同时保留一个 900 毫秒的回退计时器,避免少数设备没有及时返回初始化事件时,界面一直停在等待状态。
权限不是弹窗,而是一段流程
第一次开启时,应用通过 uni.authorize 请求 scope.camera。如果用户拒绝,页面不会反复弹出同一个请求,而是记录 permissionDenied,给出明确提示,并在下一次点击时调用 uni.openSetting,让用户主动恢复权限。
核心状态被拆成五个变量:
isOn:闪光灯是否已经开启;isStarting:是否正在等待相机初始化;cameraMounted:隐藏的相机组件是否挂载;permissionDenied:是否需要引导用户前往设置;errorMessage:展示权限或设备异常的具体原因。
这种拆分让界面文案、按钮禁用状态和底层相机生命周期都能由同一组事实驱动,避免出现“界面说已开启,但相机其实启动失败”的矛盾。
离开页面时必须主动关灯
对这类直接操作硬件的工具,清理逻辑和开启逻辑同样重要。页面在 onHide 与 onUnload 中都会调用 turnOff():清除计时器、卸载相机组件,并把所有进行中的状态复位。
成功开启和关闭时还会触发一次轻量震动。视觉变化告诉用户结果,触觉反馈确认操作已经发生,两者一起让这个大按钮更像一个可信的物理开关。
一个小工具的完成度,不取决于功能数量,而取决于正常路径和失败路径是否都被认真对待。
目前的边界
当前版本针对微信小程序真机实现。在其他构建目标中,应用会直接提示“请在微信小程序真机中使用”,而不是假装功能可用。Android 清单已经声明相机与闪光灯相关权限,但跨平台适配仍需要分别验证各端的硬件接口和权限模型。
如果继续迭代,下一步会是真机覆盖测试:检查不同品牌手机的初始化回调、后台切换后的关闭行为,以及没有闪光灯的设备能否得到准确提示。界面已经足够简单,剩下的工作就是让这一个按钮在更多真实环境里保持可靠。