fix(vibrate): 修复旧用户震动失效 + 新增震动强度选项

根因:user_settings 早期无 vibrate 字段,getSettings 未兜底,
_remind 的 !s.vibrate 误判 undefined 为关闭,导致震动静默失效,
需手动操作开关才恢复。

- storage: getSettings 兜底 vibrate/voiceGuide/vibrateIntensity 默认值
- timer: !s.vibrate 改 === false,字段缺失视为开(与 voiceGuide 一致)
- timer: vibrate 按 vibrateIntensity 选 API(light/medium=vibrateShort,
  heavy=vibrateLong),fail 回调辅助排查
- settings: 新增"震动强度"三档(短促/标准/强烈),默认强烈

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 10:36:14 +08:00
parent a7ed017267
commit ed16ac5efd
5 changed files with 58 additions and 5 deletions
+16
View File
@@ -58,6 +58,12 @@ Page({
{ key: 'female', name: '女声' },
{ key: 'male', name: '男声' }
],
vibrateIntensity: 'heavy',
vibrateIntensityOptions: [
{ key: 'light', name: '短促' },
{ key: 'medium', name: '标准' },
{ key: 'heavy', name: '强烈' }
],
icons: iconsMod.build(),
nickName: '',
avatarUrl: '',
@@ -92,6 +98,7 @@ Page({
voiceGuide: s.voiceGuide,
vibrate: s.vibrate,
voiceGender: s.voiceGender || 'female',
vibrateIntensity: s.vibrateIntensity || 'heavy',
currentThemeId: theme.id,
theme,
icons: iconsMod.build(theme),
@@ -253,6 +260,15 @@ Page({
this.setData({ voiceGender: key })
},
onSelectVibrateIntensity(e) {
const key = e.currentTarget.dataset.key
if (key === this.data.vibrateIntensity) return
const s = storage.getSettings()
s.vibrateIntensity = key
storage.saveSettings(s)
this.setData({ vibrateIntensity: key })
},
onCopyWechat() {
wx.setClipboardData({
data: config.developer,
+15
View File
@@ -140,6 +140,21 @@
</view>
<switch checked="{{vibrate}}" bindchange="onToggleVibrate" color="{{theme.primary}}"/>
</view>
<view class="setting-row">
<view class="setting-left">
<image class="setting-icon" src="{{icons.notificationFill}}" mode="aspectFit"></image>
<text class="setting-label">震动强度</text>
</view>
<view class="segmented">
<view
class="segmented-item {{vibrateIntensity === item.key ? 'active' : ''}}"
wx:for="{{vibrateIntensityOptions}}"
wx:key="key"
data-key="{{item.key}}"
bindtap="onSelectVibrateIntensity"
>{{item.name}}</view>
</view>
</view>
</ui-card>
<!-- 数据管理 -->
+16 -2
View File
@@ -132,13 +132,27 @@ Page({
const s = storage.getSettings()
const { remaining, elapsed, duration } = tick
const vibrate = (n, ms) => {
if (!s.vibrate) return
// `=== false` (not `!s.vibrate`) so an undefined field (legacy users
// whose settings predate the toggle) is treated as ON, matching the
// voiceGuide check below. `!s.vibrate` used to see undefined as falsy
// and silently skip vibration until the user toggled the switch.
if (s.vibrate === false) return
const intensity = s.vibrateIntensity || 'heavy'
// light/medium = vibrateShort(type); heavy = vibrateLong(400ms).
// vibrateLong is the most reliable on iOS real devices; the short
// types are nicer but may be silent on some devices - user picks.
const doVibrate = () => {
const fail = (err) => console.warn('[vibrate] failed:', err.errMsg || err)
if (intensity === 'light') wx.vibrateShort({ type: 'light', fail })
else if (intensity === 'medium') wx.vibrateShort({ type: 'medium', fail })
else wx.vibrateLong({ fail })
}
try {
for (let i = 0; i < n; i++) {
// Track timers so pause/stop/unload can cancel pending bursts
// instead of firing vibrations on a page that's already torn down.
const id = setTimeout(() => {
wx.vibrateShort()
doVibrate()
const idx = this._vibrateTimers.indexOf(id)
if (idx > -1) this._vibrateTimers.splice(idx, 1)
}, i * (ms + 30))