Ver código fonte

feat(health): 添加权限引导弹窗并修复文本换行问题

- 在健康提醒页面添加权限引导弹窗,引导用户开启消息通知
- 修复弹窗中文本换行显示异常的问题,将 text 组件替换为 view 组件
- 实现弹窗显示与隐藏逻辑,根据订阅状态控制弹窗展示
- 添加打开设置页面和关闭弹窗的功能函数
- 编写详细的 uni-app 弹窗文本换行问题解决文档
mcbaiyun 1 mês atrás
pai
commit
9c68e3abc9
2 arquivos alterados com 192 adições e 0 exclusões
  1. 48 0
      docs/uniapp-modal-multiline-text-fix.md
  2. 144 0
      src/pages/health/reminder.vue

+ 48 - 0
docs/uniapp-modal-multiline-text-fix.md

@@ -0,0 +1,48 @@
+# uni-app 中弹窗文本多行显示问题解决经验
+
+## 问题描述
+在 uni-app 项目中,健康提醒页面的权限引导弹窗中,引导文本应该以多行显示,但实际显示时全部文本被挤压在同一行,导致可读性差。
+
+## 原来的解决方案
+最初的代码使用了 `<text>` 元素来显示引导文本,并通过 CSS 设置 `white-space: pre-line;` 来支持换行符(`\n`)的显示。
+
+```vue
+<text class="modal-text">为了更好地为您提供健康提醒服务,请您按以下步骤开启消息通知权限:{{ '\n\n' }}1. 点击下方【去开启】按钮{{ '\n' }}2. 进入【通知管理】选项{{ '\n' }}3. 开启【接收通知】开关{{ '\n' }}4. 确保各项健康提醒均为【接收】状态{{ '\n\n' }}开启后,您将及时收到血压、血糖等重要健康提醒</text>
+```
+
+对应的 CSS:
+```css
+.modal-text {
+  font-size: 28rpx;
+  color: #666;
+  line-height: 1.5;
+  white-space: pre-line; /* 添加这行来支持换行 */
+}
+```
+
+## 机制导致的失败原因
+在 uni-app 框架中,`<text>` 组件是专门用于显示单行文本的内联元素。它默认不支持多行文本显示,即使通过 CSS 设置 `white-space: pre-line;` 来保留换行符,也无法在小程序环境中正确渲染多行文本。这是因为 `<text>` 组件在底层实现上被设计为单行显示,忽略了换行相关的样式属性。
+
+## 新的解决方案
+将 `<text>` 元素替换为 `<view>` 元素。`<view>` 是块级元素,支持多行文本显示,并能正确响应 `white-space: pre-line;` 样式。
+
+修改后的代码:
+```vue
+<view class="modal-text">为了更好地为您提供健康提醒服务,请您按以下步骤开启消息通知权限:{{ '\n\n' }}1. 点击下方【去开启】按钮{{ '\n' }}2. 进入【通知管理】选项{{ '\n' }}3. 开启【接收通知】开关{{ '\n' }}4. 确保各项健康提醒均为【接收】状态{{ '\n\n' }}开启后,您将及时收到血压、血糖等重要健康提醒</view>
+```
+
+CSS 保持不变:
+```css
+.modal-text {
+  font-size: 28rpx;
+  color: #666;
+  line-height: 1.5;
+  white-space: pre-line;
+}
+```
+
+## 总结经验
+1. **了解组件特性**:在 uni-app 中,`<text>` 和 `<view>` 组件有不同的用途。`<text>` 适合单行文本,`<view>` 适合多行或复杂布局。
+2. **样式兼容性**:即使 CSS 属性在 Web 端有效,也不一定在小程序环境中生效。需要根据 uni-app 的文档和实践经验选择合适的组件。
+3. **调试方法**:遇到样式不生效时,先检查组件类型是否合适,然后考虑使用更灵活的块级元素。
+4. **最佳实践**:对于需要多行显示的文本内容,优先使用 `<view>` 元素,并结合适当的 CSS 来控制格式。

+ 144 - 0
src/pages/health/reminder.vue

@@ -20,6 +20,24 @@
       </view>
     </view>
   </view>
+  
+  <!-- 权限引导弹窗 -->
+  <view class="permission-modal" v-if="showPermissionGuide">
+    <view class="modal-mask" @click="closePermissionGuide"></view>
+    <view class="modal-content">
+      <view class="modal-header">
+        <text class="modal-title">开启消息通知</text>
+      </view>
+      <view class="modal-body">
+        <view class="modal-text">为了更好地为您提供健康提醒服务,请您按以下步骤开启消息通知权限:{{ '\n\n' }}1. 点击下方【去开启】按钮{{ '\n' }}2. 进入【通知管理】选项{{ '\n' }}3. 开启【接收通知】开关{{ '\n' }}4. 确保各项健康提醒均为【接收】状态{{ '\n\n' }}开启后,您将及时收到血压、血糖等重要健康提醒</view>
+      </view>
+      <view class="modal-footer">
+        <button class="modal-button cancel" @click="closePermissionGuide">取消</button>
+        <button class="modal-button confirm" @click="openSettings">去开启</button>
+      </view>
+    </view>
+  </view>
+  
   <TabBar />
 </template>
 
@@ -50,6 +68,9 @@ const TEMPLATE_NO = '7536'
 // 全局消息开关
 const notificationsEnabled = ref<boolean>(false)
 
+// 是否显示权限引导
+const showPermissionGuide = ref<boolean>(false)
+
 // 记录页面进入来源:'healthIndex' | 'subscribe' | 'unknown'
 const entrySource = ref<'healthIndex' | 'subscribe' | 'unknown'>('unknown')
 const entrySourceText = computed(() => {
@@ -174,6 +195,8 @@ const checkSubscriptionStatus = () => {
           if (mainSwitch === false || templateStatus === 'reject' || templateStatus === 'ban') {
             console.log('用户关闭了订阅设置,正在更新本地状态')
             notificationsEnabled.value = false
+            // 显示权限引导
+            showPermissionGuide.value = (mainSwitch === false)
             try {
               (uni as any).setStorageSync('notificationsEnabled', false)
             } catch (e) {
@@ -184,6 +207,7 @@ const checkSubscriptionStatus = () => {
           else if (mainSwitch === true && templateStatus === 'accept') {
             console.log('用户开启了订阅设置,正在更新本地状态')
             notificationsEnabled.value = true
+            showPermissionGuide.value = false
             try {
               (uni as any).setStorageSync('notificationsEnabled', true)
             } catch (e) {
@@ -203,6 +227,37 @@ const checkSubscriptionStatus = () => {
   }
 }
 
+/**
+ * 打开微信设置页面
+ */
+const openSettings = () => {
+  console.log('用户点击前往设置')
+  showPermissionGuide.value = false
+  if (typeof (uni as any).openSetting === 'function') {
+    (uni as any).openSetting({
+      success: (res: any) => {
+        console.log('打开设置成功:', res)
+        // 重新检查订阅状态
+        checkSubscriptionStatus()
+      },
+      fail: (err: any) => {
+        console.error('打开设置失败:', err)
+        uni.showToast({ title: '打开设置失败', icon: 'none' })
+      }
+    })
+  } else {
+    uni.showToast({ title: '当前环境不支持打开设置', icon: 'none' })
+  }
+}
+
+/**
+ * 关闭权限引导弹窗
+ */
+const closePermissionGuide = () => {
+  console.log('用户关闭权限引导')
+  showPermissionGuide.value = false
+}
+
 /**
  * 顶部总开关变更
  * - 打开时:调用 uni.requestSubscribeMessage 请求用户同意订阅 TEMPLATE_ID
@@ -232,6 +287,8 @@ const onNotificationChange = (e: any) => {
             // 忽略存储错误
           }
           uni.showToast({ title: '订阅成功', icon: 'success' })
+          // 隐藏权限引导
+          showPermissionGuide.value = false
         } else {
           console.log('用户未接受订阅,结果:', result)
           // 用户拒绝或关闭了弹窗
@@ -240,6 +297,8 @@ const onNotificationChange = (e: any) => {
             ;(uni as any).setStorageSync('notificationsEnabled', false)
           } catch (err) {}
           uni.showToast({ title: '订阅被拒绝', icon: 'none' })
+          // 显示权限引导
+          showPermissionGuide.value = true
         }
       },
       fail(err: any) {
@@ -251,6 +310,8 @@ const onNotificationChange = (e: any) => {
         // 根据错误类型显示不同的提示信息
         if (err.errCode === 20004) {
           uni.showToast({ title: '推送权限已关闭', icon: 'none' })
+          // 显示权限引导
+          showPermissionGuide.value = true
         } else {
           uni.showToast({ title: '订阅请求失败', icon: 'none' })
         }
@@ -264,6 +325,8 @@ const onNotificationChange = (e: any) => {
       ;(uni as any).setStorageSync('notificationsEnabled', false)
     } catch (err) {}
     uni.showToast({ title: '已关闭通知', icon: 'none' })
+    // 隐藏权限引导
+    showPermissionGuide.value = false
   }
 }
 
@@ -345,4 +408,85 @@ const toggleReminder = (index: number) => {
   color: #8a8a8a;
   margin-top: 6rpx;
 }
+
+/* 权限引导弹窗样式 */
+.permission-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 9999;
+}
+
+.modal-mask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.6);
+}
+
+.modal-content {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  width: 80%;
+  background-color: #fff;
+  border-radius: 12rpx;
+  overflow: hidden;
+}
+
+.modal-header {
+  padding: 30rpx;
+  text-align: center;
+  border-bottom: 1rpx solid #eee;
+}
+
+.modal-title {
+  font-size: 32rpx;
+  color: #333;
+  font-weight: bold;
+}
+
+.modal-body {
+  padding: 30rpx;
+  text-align: center;
+}
+
+.modal-text {
+  font-size: 28rpx;
+  color: #666;
+  line-height: 1.5;
+  white-space: pre-line; /* 添加这行来支持换行 */
+}
+
+.modal-footer {
+  display: flex;
+  border-top: 1rpx solid #eee;
+}
+
+.modal-button {
+  flex: 1;
+  border: none;
+  padding: 20rpx 0;
+  font-size: 28rpx;
+}
+
+.modal-button.cancel {
+  background-color: #f5f5f5;
+  color: #666;
+}
+
+.modal-button.confirm {
+  background-color: #07c160;
+  color: #fff;
+}
+
+.modal-button:after {
+  border: none;
+}
+
 </style>