微信订阅消息状态检测与同步方案.md 7.7 KB

微信小程序订阅消息状态检查解决方案

问题背景

在微信小程序开发中,订阅消息是一种重要的用户触达方式。然而,开发者经常面临一个问题:如何准确地检测用户是否订阅了特定的模板消息?当用户在设置中手动关闭订阅权限时,如何在前端及时反映这一状态变化?

传统的做法是依赖本地存储来记录用户订阅状态,但这存在明显的缺陷:当用户在微信设置中手动关闭订阅权限时,应用无法感知这一变化,导致界面上显示的状态与实际状态不一致。

解决方案

核心API:uni.getSetting withSubscriptions参数

微信小程序提供了检查订阅消息状态的官方API,关键在于使用 withSubscriptions: true 参数:

uni.getSetting({
  withSubscriptions: true, // 关键参数
  success(res) {
    console.log(res.subscriptionsSetting);
    // 包含 mainSwitch 和 itemSettings 字段
  }
});

subscriptionsSetting结构说明

返回的 subscriptionsSetting 对象包含以下重要字段:

  1. mainSwitch: Boolean类型,表示订阅消息的总开关
  2. itemSettings: 对象类型,包含每个模板ID的具体状态
    • 模板ID作为键名
    • 状态值包括:'accept'(接受)、'reject'(拒绝)、'ban'(封禁)

实现步骤

  1. 在页面加载时调用检查函数
  2. 使用 withSubscriptions: true 参数调用 getSetting
  3. 解析 subscriptionsSetting 中的 mainSwitch 和 itemSettings
  4. 根据状态更新本地存储和界面显示

代码示例

const checkSubscriptionStatus = () => {
  if (typeof uni.getSetting === 'function') {
    uni.getSetting({
      withSubscriptions: true,
      success: (res) => {
        if (res.subscriptionsSetting) {
          // 检查主开关
          const mainSwitch = res.subscriptionsSetting.mainSwitch;
          
          // 检查特定模板状态
          const itemSettings = res.subscriptionsSetting.itemSettings || {};
          const templateStatus = itemSettings[TEMPLATE_ID];
          
          // 根据状态更新本地存储和UI
          // 将 templateStatus !== 'accept' 视为未授权(例如 'reject'、'ban' 或 undefined)
          if (mainSwitch === false || templateStatus !== 'accept') {
            // 更新为未订阅状态(将状态保存在 patientReminders.notificationsEnabled)
            notificationsEnabled.value = false;
            try {
              const reminders = uni.getStorageSync('patientReminders') || {
                bloodPressureReminder: { enabled: false, times: [] },
                bloodSugarReminder: { enabled: false, times: [] },
                heartRateReminder: { enabled: false, times: [] },
                medicationReminder: { enabled: false, times: [] },
                notificationsEnabled: false,
                subscriptionAvailable: true
              };
              reminders.notificationsEnabled = false;
              uni.setStorageSync('patientReminders', reminders);
            } catch (e) {}
          } else if (mainSwitch === true && templateStatus === 'accept') {
            // 更新为已订阅状态(保存到 patientReminders)
            notificationsEnabled.value = true;
            try {
              const reminders = uni.getStorageSync('patientReminders') || {
                bloodPressureReminder: { enabled: false, times: [] },
                bloodSugarReminder: { enabled: false, times: [] },
                heartRateReminder: { enabled: false, times: [] },
                medicationReminder: { enabled: false, times: [] },
                notificationsEnabled: false,
                subscriptionAvailable: true
              };
              reminders.notificationsEnabled = true;
              uni.setStorageSync('patientReminders', reminders);
            } catch (e) {}
          }
        }
      },
      fail: (err) => {
        console.error('Failed to get user settings:', err);
      }
    });
  }
};

注意事项

  1. API兼容性:确保目标平台支持 withSubscriptions 参数
  2. 状态同步:定期检查状态以确保与用户设置同步
  3. 用户体验:当检测到订阅状态变更时,应给予用户适当提示
  4. 错误处理:妥善处理API调用失败的情况

总结

通过使用 uni.getSetting 配合 withSubscriptions: true 参数,我们可以准确地检测用户对订阅消息的设置,实现前端独立的状态管理,无需依赖后端服务。这种方法能够实时反映用户在微信设置中的操作,提升用户体验和应用的准确性。

本项目中已做的同步变更

在本仓库的 src/pages/patient/health/reminder.vue 中,我们把原来分散的订阅检查逻辑合并为一个统一的函数 checkSubscriptionStatus,并为其新增了一个 passive 参数,用来区分“被动检查”和“主动检查”的行为:

  • checkSubscriptionStatus(passive = false)
    • passive = false(默认,主动检查):用于用户交互触发的场景(例如用户在设置页返回后、或用户明确请求刷新订阅状态时)。在此模式下:
    • 如果 mainSwitch === false 或模板状态非 accept(例如 rejectban 或未返回模板状态 undefined),将把 notificationsEnabled 设为 false、显示权限引导(showPermissionGuide = true,当主开关关闭时),并保存本地状态。
    • 如果 mainSwitch === true 且模板状态为 accept,将把 notificationsEnabled 设为 true、关闭权限引导,并保存本地状态。
    • passive = true(被动检查):用于生命周期或自动触发的场景(例如页面 onShow 时自动检测)。在此模式下:
    • 仅在检测到 mainSwitch === false 或模板状态非 accept(例如 rejectban 或未返回模板状态 undefined)时,才会把 notificationsEnabled 设为 false 并保存本地状态;不会在检测到 accept 时把本地开关置为 true,也不会显示权限引导(遵循“被动不打扰用户”的原则)。

修改要点:

  • onShow 中原本重复的 uni.getSetting({...}) 代码替换为 checkSubscriptionStatus(true),实现代码复用和行为统一(onShow 执行被动检查)。
  • openSettings 中在用户从系统设置返回时仍然调用 checkSubscriptionStatus()(默认主动模式),以便在用户在系统设置中开启权限后,将本地状态同步为已开启并关闭权限引导。

示例调用:

// 页面返回前台时(被动检查)
checkSubscriptionStatus(true)

// 用户在设置页返回时(主动检查)
checkSubscriptionStatus()

为什么这样做:

  • 被动检查(如 onShow)不应在检测到接受时主动修改用户在界面上的显式选择,避免悖论或意外更改;但当检测到权限被明确关闭时,应被动同步并关闭本地功能,防止应用继续假定有推送能力。
  • 主动检查(如用户明确进入设置或手动刷新)则可以安全地将本地状态与系统设置对齐,并在必要时显示权限引导帮助用户恢复权限。

已在代码中完成该改动,且相关逻辑已保存到 reminder.vue 中的注释与实现里。