Ver código fonte

feat(docs): 新增微信小程序消息订阅功能解析文档

- 添加关于如何判断用户是否勾选"总是保持以上选择"的详细分析
- 提供 wx.getSetting 与 requestSubscribeMessage 的使用对比
- 给出实际代码示例和应用场景建议
- 补充了订阅状态检测与本地存储同步的实现逻辑说明
mcbaiyun 1 semana atrás
pai
commit
7f8514fe84

+ 153 - 0
docs/微信小程序消息订阅功能解析.md

@@ -0,0 +1,153 @@
+# 微信小程序消息订阅功能解析:如何判断用户是否勾选"总是保持以上选择"
+
+## 一、消息订阅功能概述
+
+微信小程序的消息订阅功能允许开发者在用户同意的情况下,向用户发送通知消息。这个功能对于提升用户体验和服务闭环非常重要。
+
+### 消息订阅类型:
+- **一次性订阅**:用户授权一次,开发者可发送一条消息
+- **长期订阅**:需特定资质,用户授权后可多次发送消息(仅对政务、医疗、交通等特定行业开放)
+
+对于普通开发者来说,通常只能使用一次性订阅功能。
+
+## 二、问题的提出
+
+通过合理设计,一次性订阅也能实现类似长期订阅的效果:
+- 用户在订阅时勾选"**总是保持以上选择,不再询问**"
+- 之后的订阅调用不会再弹窗,而是保持之前的选择
+
+**核心问题**:如何知道用户是否在订阅时勾选了"总是保持以上选择,不再询问"?
+
+## 三、技术实现解析
+
+### 3.1 订阅请求的返回值对比
+
+无论用户是否勾选"总是保持以上选择,不再询问",调用`requestSubscribeMessage`函数都会返回相同的结果:
+
+```javascript
+// 订阅请求返回结果(两种情况下相同)
+{
+  "ACS7cwcbx0F0Y_YaB4GZr7rWP7BO2-7wQOtYsnUjmFI": "accept",
+  "errMsg": "requestSubscribeMessage:ok"
+}
+```
+
+### 3.2 获取设置信息的区别
+
+通过`wx.getSetting`(配合`withSubscriptions: true`参数)可以观察到关键差异:
+
+#### 情况一:用户勾选了"总是保持以上选择,不再询问"
+
+```javascript
+wx.getSetting({
+  withSubscriptions: true,
+  success(res) {
+    console.log("获取成功:", res.authSetting);
+    console.log("完整订阅消息设置:", JSON.stringify(res.subscriptionsSetting, null, 2));
+  }
+});
+
+// 返回结果:
+{
+  "mainSwitch": true,
+  "itemSettings": {
+    "ACS7cwcbx0F0Y_YaB4GZr7rWP7BO2-7wQOtYsnUjmFI": "accept"
+  },
+  "ACS7cwcbx0F0Y_YaB4GZr7rWP7BO2-7wQOtYsnUjmFI": "accept"
+}
+```
+
+#### 情况二:用户没有勾选"总是保持以上选择,不再询问"
+
+```javascript
+// 返回结果:
+{
+  "mainSwitch": true
+}
+```
+
+## 四、核心答案与判断逻辑
+
+### 4.1 核心判断方法
+
+**判断用户是否勾选"总是保持以上选择"的核心方法是:检查`wx.getSetting`返回结果中是否存在`subscriptionsSetting.itemSettings`对象及其内容。**
+
+### 4.2 关键差异点分析
+
+| 特征 | 勾选了"不再询问" | 未勾选"不再询问" |
+|------|----------------|------------------|
+| `mainSwitch` | true | true |
+| `itemSettings` | 存在且包含模板ID | 不存在 |
+| 模板ID属性 | 存在(如`ACS7cw...: "accept"`) | 不存在 |
+
+### 4.3 代码实现示例
+
+```javascript
+/**
+ * 判断用户是否永久订阅某个模板
+ * @param {string} templateId - 消息模板ID
+ * @returns {boolean} - 是否永久订阅
+ */
+function checkPermanentSubscription(templateId) {
+  return new Promise((resolve) => {
+    wx.getSetting({
+      withSubscriptions: true,
+      success(res) {
+        const { subscriptionsSetting = {} } = res;
+        
+        // 关键判断逻辑
+        if (subscriptionsSetting.itemSettings && 
+            subscriptionsSetting.itemSettings[templateId] === 'accept') {
+          // 用户勾选了"不再询问"且接受了该模板
+          resolve(true);
+        } else {
+          // 用户未勾选"不再询问"或未接受该模板
+          resolve(false);
+        }
+      },
+      fail() {
+        resolve(false);
+      }
+    });
+  });
+}
+```
+
+## 五、实际应用建议
+
+### 5.1 优化用户体验
+- 如果用户已永久订阅,界面应显示"已订阅"状态
+- 避免对已永久订阅的用户重复弹出订阅请求
+
+### 5.2 订阅策略优化
+```javascript
+async function smartSubscribe(templateIds) {
+  const setting = await getSubscriptionSettings();
+  
+  // 过滤出需要请求订阅的模板
+  const needSubscribeIds = templateIds.filter(id => {
+    return !(setting.itemSettings && setting.itemSettings[id] === 'accept');
+  });
+  
+  if (needSubscribeIds.length === 0) {
+    console.log('所有模板已永久订阅');
+    return Promise.resolve();
+  }
+  
+  // 只请求未永久订阅的模板
+  return wx.requestSubscribeMessage({
+    tmplIds: needSubscribeIds
+  });
+}
+```
+
+### 5.3 状态展示与管理
+建议在设置页面提供清晰的订阅状态展示,让用户了解当前的订阅情况,并可以方便地管理自己的订阅偏好。
+
+## 六、总结
+
+通过分析`wx.getSetting`返回的`subscriptionsSetting`对象结构,我们可以准确判断用户是否勾选了"总是保持以上选择,不再询问":
+1. **存在`itemSettings`对象且包含对应的模板ID** → 用户勾选了"不再询问"
+2. **只有`mainSwitch: true`** → 用户未勾选"不再询问"
+
+这种判断机制为开发者提供了更精细的订阅管理能力,能够根据用户的不同选择优化订阅流程,提升用户体验,同时避免不必要的订阅弹窗干扰。

+ 62 - 6
docs/微信订阅消息状态检测与同步方案.md

@@ -55,14 +55,37 @@ const checkSubscriptionStatus = () => {
           const templateStatus = itemSettings[TEMPLATE_ID];
           const templateStatus = itemSettings[TEMPLATE_ID];
           
           
           // 根据状态更新本地存储和UI
           // 根据状态更新本地存储和UI
-          if (mainSwitch === false || templateStatus === 'reject' || templateStatus === 'ban') {
-            // 更新为未订阅状态
+          // 将 templateStatus !== 'accept' 视为未授权(例如 'reject'、'ban' 或 undefined)
+          if (mainSwitch === false || templateStatus !== 'accept') {
+            // 更新为未订阅状态(将状态保存在 patientReminders.notificationsEnabled)
             notificationsEnabled.value = false;
             notificationsEnabled.value = false;
-            uni.setStorageSync('notificationsEnabled', 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') {
           } else if (mainSwitch === true && templateStatus === 'accept') {
-            // 更新为已订阅状态
+            // 更新为已订阅状态(保存到 patientReminders)
             notificationsEnabled.value = true;
             notificationsEnabled.value = true;
-            uni.setStorageSync('notificationsEnabled', 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) {}
           }
           }
         }
         }
       },
       },
@@ -83,4 +106,37 @@ const checkSubscriptionStatus = () => {
 
 
 ## 总结
 ## 总结
 
 
-通过使用 `uni.getSetting` 配合 `withSubscriptions: true` 参数,我们可以准确地检测用户对订阅消息的设置,实现前端独立的状态管理,无需依赖后端服务。这种方法能够实时反映用户在微信设置中的操作,提升用户体验和应用的准确性。
+通过使用 `uni.getSetting` 配合 `withSubscriptions: true` 参数,我们可以准确地检测用户对订阅消息的设置,实现前端独立的状态管理,无需依赖后端服务。这种方法能够实时反映用户在微信设置中的操作,提升用户体验和应用的准确性。
+
+## 本项目中已做的同步变更
+
+在本仓库的 `src/pages/patient/health/reminder.vue` 中,我们把原来分散的订阅检查逻辑合并为一个统一的函数 `checkSubscriptionStatus`,并为其新增了一个 `passive` 参数,用来区分“被动检查”和“主动检查”的行为:
+
+- `checkSubscriptionStatus(passive = false)`
+  - passive = false(默认,主动检查):用于用户交互触发的场景(例如用户在设置页返回后、或用户明确请求刷新订阅状态时)。在此模式下:
+    - 如果 `mainSwitch === false` 或模板状态非 `accept`(例如 `reject`、`ban` 或未返回模板状态 `undefined`),将把 `notificationsEnabled` 设为 `false`、显示权限引导(`showPermissionGuide = true`,当主开关关闭时),并保存本地状态。
+    - 如果 `mainSwitch === true` 且模板状态为 `accept`,将把 `notificationsEnabled` 设为 `true`、关闭权限引导,并保存本地状态。
+  - passive = true(被动检查):用于生命周期或自动触发的场景(例如页面 `onShow` 时自动检测)。在此模式下:
+    - 仅在检测到 `mainSwitch === false` 或模板状态非 `accept`(例如 `reject`、`ban` 或未返回模板状态 `undefined`)时,才会把 `notificationsEnabled` 设为 `false` 并保存本地状态;不会在检测到 `accept` 时把本地开关置为 `true`,也不会显示权限引导(遵循“被动不打扰用户”的原则)。
+
+修改要点:
+
+- 将 `onShow` 中原本重复的 `uni.getSetting({...})` 代码替换为 `checkSubscriptionStatus(true)`,实现代码复用和行为统一(onShow 执行被动检查)。
+- `openSettings` 中在用户从系统设置返回时仍然调用 `checkSubscriptionStatus()`(默认主动模式),以便在用户在系统设置中开启权限后,将本地状态同步为已开启并关闭权限引导。
+
+示例调用:
+
+```javascript
+// 页面返回前台时(被动检查)
+checkSubscriptionStatus(true)
+
+// 用户在设置页返回时(主动检查)
+checkSubscriptionStatus()
+```
+
+为什么这样做:
+
+- 被动检查(如 onShow)不应在检测到接受时主动修改用户在界面上的显式选择,避免悖论或意外更改;但当检测到权限被明确关闭时,应被动同步并关闭本地功能,防止应用继续假定有推送能力。
+- 主动检查(如用户明确进入设置或手动刷新)则可以安全地将本地状态与系统设置对齐,并在必要时显示权限引导帮助用户恢复权限。
+
+已在代码中完成该改动,且相关逻辑已保存到 `reminder.vue` 中的注释与实现里。

+ 279 - 207
src/pages/patient/health/reminder.vue

@@ -87,7 +87,7 @@
       </view>
       </view>
     </view>
     </view>
   </view>
   </view>
-  
+
   <!-- 权限引导弹窗 -->
   <!-- 权限引导弹窗 -->
   <view class="permission-modal" v-if="showPermissionGuide">
   <view class="permission-modal" v-if="showPermissionGuide">
     <view class="modal-mask" @click="closePermissionGuide"></view>
     <view class="modal-mask" @click="closePermissionGuide"></view>
@@ -97,7 +97,8 @@
       </view>
       </view>
       <view class="modal-body">
       <view class="modal-body">
         <view class="modal-text">检测到您可能误关闭了消息通知权限,或之前曾拒绝过授权。{{ '\n' }}消息通知权限能帮助您及时接收血压、血糖等重要健康提醒!{{ '\n' }}
         <view class="modal-text">检测到您可能误关闭了消息通知权限,或之前曾拒绝过授权。{{ '\n' }}消息通知权限能帮助您及时接收血压、血糖等重要健康提醒!{{ '\n' }}
-    若您希望开启通知,请按以下步骤操作{{ '\n' }}(若您无需通知,可点击下方取消按钮):{{ '\n' }}{{ '\n' }}1. 点击下方【去开启】按钮{{ '\n' }}2. 进入【通知管理】选项{{ '\n' }}3. 开启【接收通知】开关{{ '\n' }}4. 确保各项健康提醒均为【接收】状态</view>
+          若您希望开启通知,请按以下步骤操作{{ '\n' }}(若您无需通知,可点击下方取消按钮):{{ '\n' }}{{ '\n' }}1. 点击下方【去开启】按钮{{ '\n' }}2. 进入【通知管理】选项{{ '\n'
+          }}3. 开启【接收通知】开关{{ '\n' }}4. 确保各项健康提醒均为【接收】状态</view>
       </view>
       </view>
       <view class="modal-footer">
       <view class="modal-footer">
         <button class="modal-button cancel" @click="closePermissionGuide">取消</button>
         <button class="modal-button cancel" @click="closePermissionGuide">取消</button>
@@ -127,7 +128,7 @@
       </view>
       </view>
     </view>
     </view>
   </view>
   </view>
-  
+
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
@@ -199,6 +200,68 @@ const loadUser = () => {
   }
   }
 }
 }
 
 
+// 读取本地 patientReminders(已简化,不包含旧的手动关闭标记)
+
+// 将页面来源识别逻辑抽成函数,便于复用与测试
+const determineEntrySource = () => {
+  try {
+    const pages = (getCurrentPages as any)()
+    console.log('当前页面栈:', pages)
+
+    if (pages && pages.length >= 1) {
+      const currentPage = pages[pages.length - 1]
+      const currentRoute = currentPage?.route || currentPage?.__route || currentPage?.$page?.route
+      const currentOptions = currentPage?.options || {}
+
+      console.log('当前页面路由:', currentRoute)
+      console.log('当前页面参数:', currentOptions)
+
+      // 检查当前页面是否包含 from=subscribe 参数
+      if (currentOptions.from === 'subscribe') {
+        entrySource.value = 'subscribe'
+        console.log('通过当前页面参数识别为订阅消息来源')
+      }
+
+      // 检查当前页面是否包含 templateId 相关参数
+      if (currentOptions.templateId === TEMPLATE_ID || currentOptions.template_id === TEMPLATE_ID) {
+        entrySource.value = 'subscribe'
+        console.log('通过当前页面的模板ID参数识别为订阅消息来源')
+      }
+    }
+
+    if (pages && pages.length >= 2) {
+      const prev = pages[pages.length - 2]
+      const prevRoute = prev?.route || prev?.__route || prev?.$page?.route
+      console.log('上一页路由:', prevRoute)
+      if (prevRoute && String(prevRoute).includes('pages/public/health/index')) {
+        entrySource.value = 'healthIndex'
+        console.log('识别来源为健康首页')
+      }
+    } else {
+      // 如果没有上一页信息,检查小程序启动参数(例如用户通过订阅消息打开)
+      if ((uni as any).getLaunchOptionsSync) {
+        const launch = (uni as any).getLaunchOptionsSync()
+        console.log('启动参数:', launch)
+        if (launch && launch.query) {
+          console.log('查询参数:', launch.query)
+          // 如果你在推送消息里把 from=subscribe 作为参数传入,这里会命中
+          if (launch.query.from === 'subscribe') {
+            entrySource.value = 'subscribe'
+            console.log('通过 from 参数识别为订阅消息来源')
+          }
+          // 有些平台会把 templateId 或其它字段放在 query 中,可据此判断
+          if (launch.query.templateId === TEMPLATE_ID || launch.query.template_id === TEMPLATE_ID) {
+            entrySource.value = 'subscribe'
+            console.log('通过模板ID参数识别为订阅消息来源')
+          }
+        }
+      }
+    }
+  } catch (e) {
+    console.error('识别入口来源时出错:', e)
+  }
+}
+
 // 获取当前用户ID (保持为字符串以避免JavaScript Number精度问题)
 // 获取当前用户ID (保持为字符串以避免JavaScript Number精度问题)
 const currentUserId = computed(() => user.value.id || undefined)
 const currentUserId = computed(() => user.value.id || undefined)
 
 
@@ -215,6 +278,9 @@ const entrySourceText = computed(() => {
   }
   }
 })
 })
 
 
+// 标志位:onMounted 是否已执行(某些平台上 onShow 可能早于 onMounted)
+const mountedDone = ref(false)
+
 // 各个提醒项的开关控制函数
 // 各个提醒项的开关控制函数
 const toggleBloodPressureReminder = () => {
 const toggleBloodPressureReminder = () => {
   bloodPressureReminder.value.enabled = !bloodPressureReminder.value.enabled
   bloodPressureReminder.value.enabled = !bloodPressureReminder.value.enabled
@@ -275,7 +341,7 @@ const confirmTime = () => {
   if (editingReminderType.value) {
   if (editingReminderType.value) {
     const time = timeOptions.value[selectedTimeIndex.value]
     const time = timeOptions.value[selectedTimeIndex.value]
     let times: string[] = []
     let times: string[] = []
-    
+
     switch (editingReminderType.value) {
     switch (editingReminderType.value) {
       case 'bloodPressure':
       case 'bloodPressure':
         times = bloodPressureReminder.value.times
         times = bloodPressureReminder.value.times
@@ -287,7 +353,7 @@ const confirmTime = () => {
         times = heartRateReminder.value.times
         times = heartRateReminder.value.times
         break
         break
     }
     }
-    
+
     if (!times.includes(time)) {
     if (!times.includes(time)) {
       times.push(time)
       times.push(time)
       saveReminders()
       saveReminders()
@@ -296,132 +362,57 @@ const confirmTime = () => {
   closeTimePicker()
   closeTimePicker()
 }
 }
 
 
-onMounted(() => {
+onMounted(async () => {
+  console.log('onMounted')
   loadUser()
   loadUser()
-  
-  // 加载提醒设置
+
+  // 加载远端提醒设置
+
+  // 加载提醒设置(等待完成后触发 onShow 的工作逻辑)
   loadReminders()
   loadReminders()
-  
+
   // 加载用药时间点
   // 加载用药时间点
   loadMedicationTimes()
   loadMedicationTimes()
-  
-  // 检查用户订阅状态
-  // checkSubscriptionStatus()
-  
-  // 尝试判断来源:优先检查页面栈的上一个页面;若无(直接打开),则检查 launch options 的 query
-  try {
-    const pages = (getCurrentPages as any)()
-    console.log('当前页面栈:', pages)
-    
-    if (pages && pages.length >= 1) {
-      const currentPage = pages[pages.length - 1]
-      const currentRoute = currentPage?.route || currentPage?.__route || currentPage?.$page?.route
-      const currentOptions = currentPage?.options || {}
-      
-      console.log('当前页面路由:', currentRoute)
-      console.log('当前页面参数:', currentOptions)
-      
-      // 检查当前页面是否包含 from=subscribe 参数
-      if (currentOptions.from === 'subscribe') {
-        entrySource.value = 'subscribe'
-        console.log('通过当前页面参数识别为订阅消息来源')
-      }
-      
-      // 检查当前页面是否包含 templateId 相关参数
-      if (currentOptions.templateId === TEMPLATE_ID || currentOptions.template_id === TEMPLATE_ID) {
-        entrySource.value = 'subscribe'
-        console.log('通过当前页面的模板ID参数识别为订阅消息来源')
-      }
-    }
 
 
-    if (pages && pages.length >= 2) {
-      const prev = pages[pages.length - 2]
-      const prevRoute = prev?.route || prev?.__route || prev?.$page?.route
-      console.log('上一页路由:', prevRoute)
-      if (prevRoute && String(prevRoute).includes('pages/public/health/index')) {
-        entrySource.value = 'healthIndex'
-        console.log('识别来源为健康首页')
-      }
-    } else {
-      // 如果没有上一页信息,检查小程序启动参数(例如用户通过订阅消息打开)
-      if ((uni as any).getLaunchOptionsSync) {
-        const launch = (uni as any).getLaunchOptionsSync()
-        console.log('启动参数:', launch)
-        if (launch && launch.query) {
-          console.log('查询参数:', launch.query)
-          // 如果你在推送消息里把 from=subscribe 作为参数传入,这里会命中
-          if (launch.query.from === 'subscribe') {
-            entrySource.value = 'subscribe'
-            console.log('通过 from 参数识别为订阅消息来源')
-          }
-          // 有些平台会把 templateId 或其它字段放在 query 中,可据此判断
-          if (launch.query.templateId === TEMPLATE_ID || launch.query.template_id === TEMPLATE_ID) {
-            entrySource.value = 'subscribe'
-            console.log('通过模板ID参数识别为订阅消息来源')
-          }
-        }
-      }
-    }
-  } catch (e) {
-    console.error('识别入口来源时出错:', e)
-  }
-  
   // 显示来源提示(用于测试),延迟一点以保证 UI 已就绪
   // 显示来源提示(用于测试),延迟一点以保证 UI 已就绪
   try {
   try {
     setTimeout(() => {
     setTimeout(() => {
+      // 尝试判断来源:优先检查页面栈的上一个页面;若无(直接打开),则检查 launch options 的 query
+      determineEntrySource()
       console.log('入口来源:', entrySource.value, entrySourceText.value)
       console.log('入口来源:', entrySource.value, entrySourceText.value)
+      if (entrySource.value === 'unknown') {
+        console.log('入口来源默认为未知')
+      }
     }, 250)
     }, 250)
   } catch (e) {
   } catch (e) {
-    // 忽略错误
-  }
-  
-  if (entrySource.value === 'unknown') {
-    console.log('入口来源默认为未知')
+    console.error('[reminder] 尝试判断入口来源时出错:', e)
   }
   }
+  // 标记 onMounted 已执行
+  mountedDone.value = true
 })
 })
 
 
 // 监听页面显示/隐藏(用于检测用户将小程序/APP切到后台或再次回到前台)
 // 监听页面显示/隐藏(用于检测用户将小程序/APP切到后台或再次回到前台)
-onShow(() => {
+// 抽取 onShow 的可重用逻辑,支持强制触发(force=true)
+const doOnShowWork = (force = false) => {
+  console.log('onShow')
   console.log('[reminder] 页面/应用返回前台(onShow)', { entrySource: entrySource.value })
   console.log('[reminder] 页面/应用返回前台(onShow)', { entrySource: entrySource.value })
-  loadMedicationTimes()  // 加载用药时间点
-  // 在页面返回前台时,检查订阅设置;如果用户关闭了通知订阅主开关或对本模板拒绝,则主动关闭本地消息开关
+  // 如果 onMounted 尚未执行完(某些平台上 onShow 可能先于 onMounted),则跳过 onShow 的后续逻辑
+  if (!mountedDone.value && !force) {
+    console.log('[reminder][onShow] onMounted 未完成,跳过 onShow 逻辑')
+    return
+  }
+
+  loadMedicationTimes() // 加载用药时间点
+  // 在页面返回前台时进行被动检查(仅在发现被关闭/拒绝时强制关闭本地开关,不在接受时修改本地开关或显示引导)
   try {
   try {
-    if (typeof (uni as any).getSetting === 'function') {
-      ;(uni as any).getSetting({
-        withSubscriptions: true,
-        success(res: any) {
-          try {
-            const subs = res.subscriptionsSetting || {}
-            const mainSwitch = subs.mainSwitch
-            const itemSettings = subs.itemSettings || {}
-            const templateStatus = itemSettings[TEMPLATE_ID]
-            console.log('[reminder][onShow] 订阅设置检查结果:', { mainSwitch, templateStatus })
-            // 若主开关关闭或模板被拒绝/屏蔽,则主动关闭本地通知开关
-            if (mainSwitch === false || templateStatus === 'reject' || templateStatus === 'ban') {
-              console.log('[reminder][onShow] 发现订阅被关闭或本模板被拒绝,主动关闭通知开关')
-              notificationsEnabled.value = false
-              //showPermissionGuide.value = (mainSwitch === false)
-              try {
-                ;(uni as any).setStorageSync('notificationsEnabled', false)
-              } catch (e) {}
-            } else {
-              // 订阅为开启或未明确拒绝,不修改本地开关(按要求)
-              console.log('[reminder][onShow] 订阅设置未发现关闭,不更改本地开关状态')
-            }
-          } catch (e) {
-            console.error('[reminder][onShow] 处理 getSetting 返回值时出错:', e)
-          }
-        },
-        fail(err: any) {
-          console.error('[reminder][onShow] 获取订阅设置失败:', err)
-        }
-      })
-    } else {
-      console.log('[reminder][onShow] 当前环境不支持 getSetting,跳过订阅检查')
-    }
+    checkSubscriptionStatus(true)
   } catch (e) {
   } catch (e) {
-    console.error('[reminder][onShow] 检查订阅设置时异常:', e)
+    console.error('[reminder][onShow] 被动检查订阅设置时异常:', e)
   }
   }
+}
+
+onShow(() => {
+  doOnShowWork()
 })
 })
 
 
 onHide(() => {
 onHide(() => {
@@ -455,12 +446,12 @@ onMounted(() => {
   // 额外注册 uni 全局的 App 前后台事件(部分平台需要)
   // 额外注册 uni 全局的 App 前后台事件(部分平台需要)
   try {
   try {
     if ((uni as any) && typeof (uni as any).onAppShow === 'function') {
     if ((uni as any) && typeof (uni as any).onAppShow === 'function') {
-      ;(uni as any).onAppShow((res: any) => {
+      ; (uni as any).onAppShow((res: any) => {
         console.log('[reminder][uni.onAppShow] App 回到前台', res)
         console.log('[reminder][uni.onAppShow] App 回到前台', res)
       })
       })
     }
     }
     if ((uni as any) && typeof (uni as any).onAppHide === 'function') {
     if ((uni as any) && typeof (uni as any).onAppHide === 'function') {
-      ;(uni as any).onAppHide(() => {
+      ; (uni as any).onAppHide(() => {
         console.log('[reminder][uni.onAppHide] App 进入后台')
         console.log('[reminder][uni.onAppHide] App 进入后台')
       })
       })
     }
     }
@@ -484,42 +475,57 @@ onUnmounted(() => {
  */
  */
 const loadReminders = async () => {
 const loadReminders = async () => {
   if (!currentUserId.value) return
   if (!currentUserId.value) return
-  
+
   try {
   try {
     const res = await getPatientReminderOverview()
     const res = await getPatientReminderOverview()
     if (res && res.data && res.data.code === 200) {
     if (res && res.data && res.data.code === 200) {
       const reminderData = res.data.data?.reminder
       const reminderData = res.data.data?.reminder
-      
+
       if (reminderData) {
       if (reminderData) {
         // 更新各个提醒项的状态
         // 更新各个提醒项的状态
         bloodPressureReminder.value = {
         bloodPressureReminder.value = {
           enabled: reminderData.bloodPressureEnabled,
           enabled: reminderData.bloodPressureEnabled,
           times: reminderData.bloodPressureTimes || []
           times: reminderData.bloodPressureTimes || []
         }
         }
-        
+
         bloodSugarReminder.value = {
         bloodSugarReminder.value = {
           enabled: reminderData.bloodSugarEnabled,
           enabled: reminderData.bloodSugarEnabled,
           times: reminderData.bloodSugarTimes || []
           times: reminderData.bloodSugarTimes || []
         }
         }
-        
+
         heartRateReminder.value = {
         heartRateReminder.value = {
           enabled: reminderData.heartRateEnabled,
           enabled: reminderData.heartRateEnabled,
           times: reminderData.heartRateTimes || []
           times: reminderData.heartRateTimes || []
         }
         }
-        
+
         medicationReminder.value = {
         medicationReminder.value = {
           enabled: reminderData.medicationEnabled,
           enabled: reminderData.medicationEnabled,
           times: medicationReminder.value.times // 保留已加载的用药时间点
           times: medicationReminder.value.times // 保留已加载的用药时间点
         }
         }
-        
+
         // 更新全局消息开关和一次性订阅开关
         // 更新全局消息开关和一次性订阅开关
         notificationsEnabled.value = reminderData.notificationEnabled
         notificationsEnabled.value = reminderData.notificationEnabled
         subscriptionAvailable.value = reminderData.subscriptionAvailable
         subscriptionAvailable.value = reminderData.subscriptionAvailable
-        
+
         try {
         try {
-          (uni as any).setStorageSync('notificationsEnabled', reminderData.notificationEnabled)
+          const remindersToSave = {
+            bloodPressureReminder: bloodPressureReminder.value,
+            bloodSugarReminder: bloodSugarReminder.value,
+            heartRateReminder: heartRateReminder.value,
+            medicationReminder: medicationReminder.value,
+            notificationsEnabled: notificationsEnabled.value,
+            subscriptionAvailable: subscriptionAvailable.value
+          };
+          (uni as any).setStorageSync('patientReminders', remindersToSave)
+          // 本地保存后触发 onShow 的可重用逻辑,确保订阅/权限等状态被及时检查
+          try {
+            console.log('[loadReminders] 触发 onShow 逻辑')
+            doOnShowWork(true)
+          } catch (err) {
+            console.error('[loadReminders] 触发 onShow 逻辑失败:', err)
+          }
         } catch (e) {
         } catch (e) {
-          console.error('保存通知开关状态到本地存储失败:', e)
+          console.error('保存提醒设置到本地存储失败:', e)
         }
         }
       }
       }
     } else {
     } else {
@@ -538,7 +544,7 @@ const saveReminders = async () => {
     console.warn('用户未登录,无法保存提醒设置')
     console.warn('用户未登录,无法保存提醒设置')
     return
     return
   }
   }
-  
+
   try {
   try {
     // 构造要保存的提醒设置对象
     // 构造要保存的提醒设置对象
     const payload = {
     const payload = {
@@ -552,10 +558,10 @@ const saveReminders = async () => {
       heartRateTimes: heartRateReminder.value.times,
       heartRateTimes: heartRateReminder.value.times,
       medicationEnabled: medicationReminder.value.enabled
       medicationEnabled: medicationReminder.value.enabled
     }
     }
-    
+
     await savePatientReminder(payload)
     await savePatientReminder(payload)
     console.log('提醒设置保存成功')
     console.log('提醒设置保存成功')
-    
+
     // 同时也保存到本地存储以防万一
     // 同时也保存到本地存储以防万一
     try {
     try {
       const remindersToSave = {
       const remindersToSave = {
@@ -566,9 +572,8 @@ const saveReminders = async () => {
         notificationsEnabled: notificationsEnabled.value,
         notificationsEnabled: notificationsEnabled.value,
         subscriptionAvailable: subscriptionAvailable.value
         subscriptionAvailable: subscriptionAvailable.value
       }
       }
-      ;(uni as any).setStorageSync('patientReminders', remindersToSave)
-      ;(uni as any).setStorageSync('notificationsEnabled', notificationsEnabled.value)
-      
+        ; (uni as any).setStorageSync('patientReminders', remindersToSave)
+
       // 通知首页更新打卡任务
       // 通知首页更新打卡任务
       uni.$emit('reminderSettingsUpdated')
       uni.$emit('reminderSettingsUpdated')
     } catch (e) {
     } catch (e) {
@@ -580,6 +585,25 @@ const saveReminders = async () => {
   }
   }
 }
 }
 
 
+/**
+ * 将当前 in-memory 提醒状态保存到本地 storage(只使用 patientReminders 键)
+ */
+const saveLocalPatientReminders = () => {
+  try {
+    const remindersToSave = {
+      bloodPressureReminder: bloodPressureReminder.value,
+      bloodSugarReminder: bloodSugarReminder.value,
+      heartRateReminder: heartRateReminder.value,
+      medicationReminder: medicationReminder.value,
+      notificationsEnabled: notificationsEnabled.value,
+      subscriptionAvailable: subscriptionAvailable.value
+    }
+      ; (uni as any).setStorageSync('patientReminders', remindersToSave)
+  } catch (e) {
+    console.error('保存提醒设置到本地存储失败:', e)
+  }
+}
+
 /**
 /**
  * 加载用药时间点
  * 加载用药时间点
  */
  */
@@ -598,52 +622,90 @@ const loadMedicationTimes = async () => {
   }
   }
 }
 }
 
 
-const checkSubscriptionStatus = () => {
+const checkSubscriptionStatus = (passive = false) => {
   // 使用 uni.getSetting 检查用户授权状态
   // 使用 uni.getSetting 检查用户授权状态
   if (typeof (uni as any).getSetting === 'function') {
   if (typeof (uni as any).getSetting === 'function') {
     (uni as any).getSetting({
     (uni as any).getSetting({
       withSubscriptions: true, // 启用订阅设置查询
       withSubscriptions: true, // 启用订阅设置查询
       success: (res: any) => {
       success: (res: any) => {
         console.log('[checkSubscriptionStatus] 用户设置:', res)
         console.log('[checkSubscriptionStatus] 用户设置:', res)
-        
-        // 检查订阅消息设置
-        if (res.subscriptionsSetting) {
-          console.log('[checkSubscriptionStatus] 订阅设置详情:', JSON.stringify(res.subscriptionsSetting, null, 2))
-          
-          // 检查主开关
-          const mainSwitch = res.subscriptionsSetting.mainSwitch
-          console.log(`[checkSubscriptionStatus] 订阅主开关状态: ${mainSwitch ? '已开启(用户允许接收通知)' : '已关闭(用户禁止接收所有通知)'}`)
-          
-          // 检查针对特定模板的设置
-          const itemSettings = res.subscriptionsSetting.itemSettings || {}
-          const templateStatus = itemSettings[TEMPLATE_ID]
-          console.log(`[checkSubscriptionStatus] 模板 ${TEMPLATE_ID} 状态:`, templateStatus)
-          
-          // 如果主开关关闭或特定模板被拒绝,则更新本地状态
-          if (mainSwitch === false || templateStatus === 'reject' || templateStatus === 'ban') {
-            console.log('[checkSubscriptionStatus] 用户关闭了订阅设置,正在更新本地状态')
-            notificationsEnabled.value = false
-            // 显示权限引导
-            showPermissionGuide.value = (mainSwitch === false)
-            try {
-              (uni as any).setStorageSync('notificationsEnabled', false)
-            } catch (e) {
-              console.error('[checkSubscriptionStatus] 更新存储失败:', e)
+        try {
+          // 额外输出可序列化的 JSON 版本,方便在日志中完整查看返回结构
+          console.log('[checkSubscriptionStatus] 用户设置(JSON):', JSON.stringify(res, null, 2))
+        } catch (err) {
+          console.error('[checkSubscriptionStatus] 无法序列化 getSetting 返回值:', err)
+        }
+
+        try {
+          // 检查订阅消息设置
+          const subs = res.subscriptionsSetting
+          if (subs) {
+            console.log('[checkSubscriptionStatus] 订阅设置详情:', JSON.stringify(subs, null, 2))
+
+            // 检查主开关
+            const mainSwitch = subs.mainSwitch
+            console.log(`[checkSubscriptionStatus] 订阅主开关状态: ${mainSwitch ? '已开启(用户允许接收通知)' : '已关闭(用户禁止接收所有通知)'}`)
+
+            // 检查针对特定模板的设置
+            const itemSettings = subs.itemSettings || {}
+            const templateStatus = itemSettings[TEMPLATE_ID]
+            console.log(`[checkSubscriptionStatus] 模板 ${TEMPLATE_ID} 状态:`, templateStatus)
+            // console.log输出subscriptionAvailable
+            console.log(`[checkSubscriptionStatus] 一次性订阅开关状态: ${subscriptionAvailable.value}`)
+
+            // 若主开关关闭或模板为 'reject'、'ban' ,则主动关闭本地通知开关
+            if (mainSwitch === false || templateStatus === 'reject' || templateStatus === 'ban') {
+              console.log('[checkSubscriptionStatus] 发现订阅被关闭或本模板被拒绝,更新本地开关并尝试同步到服务器')
+              notificationsEnabled.value = false
+              // 在主动模式下显示权限引导;被动模式(如 onShow)只更新本地并同步服务器,但不主动弹窗
+              if (!passive) {
+                showPermissionGuide.value = (mainSwitch === false)
+              }
+              try {
+                // 先更新本地存储
+                saveLocalPatientReminders()
+              } catch (e) {
+                console.error('[checkSubscriptionStatus] 更新本地存储失败:', e)
+              }
+
+              // 异步上传到服务器:不阻塞 UI,记录成功/失败日志
+              try {
+                saveReminders()
+                  .then(() => {
+                    console.log('[checkSubscriptionStatus] 已将订阅状态同步到服务器')
+                  })
+                  .catch((err: any) => {
+                    console.error('[checkSubscriptionStatus] 同步订阅状态到服务器失败:', err)
+                  })
+              } catch (e) {
+                console.error('[checkSubscriptionStatus] 调用 saveReminders 异常:', e)
+              }
             }
             }
-          }
-          // 如果模板被接受且主开关开启,更新本地状态为true
-          else if (mainSwitch === true && templateStatus === 'accept') {
-            console.log('[checkSubscriptionStatus] 用户开启了订阅设置,正在更新本地状态')
-            notificationsEnabled.value = true
-            showPermissionGuide.value = false
-            try {
-              (uni as any).setStorageSync('notificationsEnabled', true)
-            } catch (e) {
-              console.error('[checkSubscriptionStatus] 更新存储失败:', e)
+            // 主开关开启且模板被接受:仅在主动模式下将本地开关置为 true 并关闭引导
+            else if (!passive && mainSwitch === true && templateStatus === 'accept') {
+              console.log('[checkSubscriptionStatus] 用户开启了订阅设置,正在更新本地状态并同步到服务器')
+              notificationsEnabled.value = true
+              showPermissionGuide.value = false
+              try {
+                saveLocalPatientReminders()
+              } catch (e) {
+                console.error('[checkSubscriptionStatus] 更新本地存储失败:', e)
+              }
+              try {
+                saveReminders()
+                  .then(() => console.log('[checkSubscriptionStatus] 同步订阅开启状态到服务器成功'))
+                  .catch((err: any) => console.error('[checkSubscriptionStatus] 同步订阅开启状态到服务器失败:', err))
+              } catch (e) {
+                console.error('[checkSubscriptionStatus] 调用 saveReminders 异常:', e)
+              }
+            } else {
+              console.log('[checkSubscriptionStatus] 订阅设置未发现需要变更,本次检查为被动=' + passive)
             }
             }
+          } else {
+            console.log('[checkSubscriptionStatus] 响应中未找到订阅设置')
           }
           }
-        } else {
-          console.log('[checkSubscriptionStatus] 响应中未找到订阅设置')
+        } catch (e) {
+          console.error('[checkSubscriptionStatus] 处理返回值时出错:', e)
         }
         }
       },
       },
       fail: (err: any) => {
       fail: (err: any) => {
@@ -695,54 +757,59 @@ const closePermissionGuide = () => {
 const onNotificationChange = (e: any) => {
 const onNotificationChange = (e: any) => {
   const newVal = e?.detail?.value
   const newVal = e?.detail?.value
   console.log('通知开关更改为:', newVal)
   console.log('通知开关更改为:', newVal)
-  
+
   if (newVal) {
   if (newVal) {
-    // 先将开关设置为开启状态
-    notificationsEnabled.value = true
-    
-    // 请求订阅(仅在微信/小程序有效)
-    ;(uni as any).requestSubscribeMessage({
-      tmplIds: [TEMPLATE_ID],
-      success(res: any) {
-        console.log('订阅消息请求成功结果:', res)
-        // res 可能形如 { "ACS7...": 'accept' }
-        const result = res && res[TEMPLATE_ID]
-        if (result === 'accept') {
-          console.log('用户接受了订阅')
-          // 设置一次性订阅开关为可用
-          subscriptionAvailable.value = true
-          saveReminders()
-          uni.showToast({ title: '订阅成功', icon: 'success' })
-          // 隐藏权限引导
-          showPermissionGuide.value = false
-        } else {
-          console.log('用户未接受订阅,结果:', result)
-          // 用户拒绝或关闭了弹窗
+  // 先将开关设置为开启状态
+  notificationsEnabled.value = true
+  try { saveLocalPatientReminders() } catch (e) { /* ignore */ }
+
+      // 请求订阅(仅在微信/小程序有效)
+      ; (uni as any).requestSubscribeMessage({
+        tmplIds: [TEMPLATE_ID],
+        success(res: any) {
+          console.log('订阅消息请求成功结果:', res)
+          // res 可能形如 { "ACS7...": 'accept' }
+          const result = res && res[TEMPLATE_ID]
+          if (result === 'accept') {
+            console.log('用户接受了订阅')
+            // 设置一次性订阅开关为可用
+            subscriptionAvailable.value = true
+            // 用户手动同意订阅并保存
+            saveReminders()
+            uni.showToast({ title: '订阅成功', icon: 'success' })
+            // 隐藏权限引导
+            showPermissionGuide.value = false
+          } else {
+            console.log('用户未接受订阅,结果:', result)
+            // 用户拒绝或关闭了弹窗
+            notificationsEnabled.value = false
+            // 用户主动拒绝订阅:更新并保存状态
+            saveReminders()
+            uni.showToast({ title: '订阅被拒绝', icon: 'none' })
+            // 显示权限引导
+            showPermissionGuide.value = true
+          }
+        },
+        fail(err: any) {
+          console.log('订阅消息请求失败:', err)
           notificationsEnabled.value = false
           notificationsEnabled.value = false
+          // 请求失败视为未开启订阅(非被动检查),认为是用户操作失败,保存当前状态
           saveReminders()
           saveReminders()
-          uni.showToast({ title: '订阅被拒绝', icon: 'none' })
-          // 显示权限引导
-          showPermissionGuide.value = true
-        }
-      },
-      fail(err: any) {
-        console.log('订阅消息请求失败:', err)
-        notificationsEnabled.value = false
-        saveReminders()
-        // 根据错误类型显示不同的提示信息
-        if (err.errCode === 20004) {
-          uni.showToast({ title: '推送权限已关闭', icon: 'none' })
-          // 显示权限引导
-          showPermissionGuide.value = true
-        } else {
-          uni.showToast({ title: '订阅请求失败', icon: 'none' })
+          // 根据错误类型显示不同的提示信息
+          if (err.errCode === 20004) {
+            uni.showToast({ title: '推送权限已关闭', icon: 'none' })
+            // 显示权限引导
+            showPermissionGuide.value = true
+          } else {
+            uni.showToast({ title: '订阅请求失败', icon: 'none' })
+          }
         }
         }
-      }
-    })
+      })
   } else {
   } else {
     // 关闭订阅:不需要额外调用接口,只改变本地记录
     // 关闭订阅:不需要额外调用接口,只改变本地记录
     console.log('通知开关已关闭')
     console.log('通知开关已关闭')
     notificationsEnabled.value = false
     notificationsEnabled.value = false
+    // 用户手动关闭开关 -> 保存当前状态
     saveReminders()
     saveReminders()
     uni.showToast({ title: '已关闭通知', icon: 'none' })
     uni.showToast({ title: '已关闭通知', icon: 'none' })
     // 隐藏权限引导
     // 隐藏权限引导
@@ -845,21 +912,25 @@ const onNotificationChange = (e: any) => {
   border-radius: 12rpx;
   border-radius: 12rpx;
   margin-top: 20rpx;
   margin-top: 20rpx;
 }
 }
+
 .notification-label {
 .notification-label {
   font-size: 32rpx;
   font-size: 32rpx;
   color: #000;
   color: #000;
 }
 }
+
 .template-info {
 .template-info {
   margin-top: 12rpx;
   margin-top: 12rpx;
   padding: 0 10rpx;
   padding: 0 10rpx;
   color: #666;
   color: #666;
   font-size: 24rpx;
   font-size: 24rpx;
 }
 }
+
 .tmpl-title {
 .tmpl-title {
   display: block;
   display: block;
   font-size: 26rpx;
   font-size: 26rpx;
   color: #333;
   color: #333;
 }
 }
+
 .tmpl-meta {
 .tmpl-meta {
   display: block;
   display: block;
   font-size: 22rpx;
   font-size: 22rpx;
@@ -918,7 +989,8 @@ const onNotificationChange = (e: any) => {
   font-size: 28rpx;
   font-size: 28rpx;
   color: #666;
   color: #666;
   line-height: 1.5;
   line-height: 1.5;
-  white-space: pre-line; /* 添加这行来支持换行 */
+  white-space: pre-line;
+  /* 添加这行来支持换行 */
 }
 }
 
 
 .modal-footer {
 .modal-footer {