feat: 新增 TabBar 红点提醒功能(预开发)
- 添加功能开关配置系统(src/config/features.js) - 支持全局开关控制功能启用/禁用 - 可配置红点字段名称和显示阈值 - 方便灰度发布和功能回滚 - TabBar 组件自动从 Store 读取红点状态 - 移除手动传递 badges prop - 组件内部自动管理红点显示逻辑 - 只处理"我的"按钮的红点 - User Store 新增红点状态自动计算 - 新增 tabBarBadges 计算属性 - 根据用户信息自动计算红点状态 - 支持数字和布尔类型字段 - 响应式更新,无需手动管理 - 首页添加用户信息自动刷新 - useShow 生命周期自动刷新 - 只在已登录状态下请求 - 添加错误处理 - 创建完整的使用文档(docs/features/tabbar-badge.md) - 功能说明和启用方法 - 数据流程图和测试方法 - 常见问题解答 功能默认关闭(features.tabbarBadge = false) 当前使用 unread_count 字段(待确认) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Showing
6 changed files
with
509 additions
and
5 deletions
| ... | @@ -5,6 +5,50 @@ | ... | @@ -5,6 +5,50 @@ |
| 5 | 5 | ||
| 6 | --- | 6 | --- |
| 7 | 7 | ||
| 8 | +## [2026-02-03] - 新增 TabBar 红点提醒功能(预开发) | ||
| 9 | + | ||
| 10 | +### 新增 | ||
| 11 | +- TabBar 红点提醒功能(`src/components/TabBar.vue`) | ||
| 12 | + - 在"我的"按钮右上角显示红点,提示用户有未读消息 | ||
| 13 | + - 红点状态由用户信息接口返回的 `unread_count` 字段决定 | ||
| 14 | + - 支持通过配置文件全局开关功能 | ||
| 15 | +- 功能开关配置系统(`src/config/features.js`) | ||
| 16 | + - 集中管理功能的启用/禁用状态 | ||
| 17 | + - 支持灰度发布和功能回滚 | ||
| 18 | + - 可配置红点字段名称和显示阈值 | ||
| 19 | +- User Store 红点状态自动计算(`src/stores/user.js`) | ||
| 20 | + - 新增 `tabBarBadges` 计算属性,自动根据用户信息计算红点状态 | ||
| 21 | + - 支持数字和布尔类型字段 | ||
| 22 | + - 响应式更新,无需手动管理 | ||
| 23 | +- 首页用户信息自动刷新(`src/pages/index/index.vue`) | ||
| 24 | + - 页面显示时自动刷新用户信息,更新红点状态 | ||
| 25 | + - 只在已登录状态下请求,避免无效调用 | ||
| 26 | + - 添加错误处理,提升健壮性 | ||
| 27 | + | ||
| 28 | +### 优化 | ||
| 29 | +- TabBar 组件自动从 Store 读取红点状态 | ||
| 30 | + - 移除手动传递的 `badges` prop | ||
| 31 | + - 组件内部自动管理红点显示逻辑 | ||
| 32 | + - 简化使用方式,降低维护成本 | ||
| 33 | + | ||
| 34 | +--- | ||
| 35 | + | ||
| 36 | +**详细信息**: | ||
| 37 | +- **影响文件**: | ||
| 38 | + - `src/config/features.js` (新建) | ||
| 39 | + - `src/components/TabBar.vue` | ||
| 40 | + - `src/stores/user.js` | ||
| 41 | + - `src/pages/index/index.vue` | ||
| 42 | +- **技术栈**: Vue 3, Pinia, Composition API, Computed | ||
| 43 | +- **测试状态**: ⚠️ 预开发阶段(功能开关默认关闭) | ||
| 44 | +- **备注**: | ||
| 45 | + - 功能默认关闭,等后端接口字段确定后再开启 | ||
| 46 | + - 当前使用 `unread_count` 字段,后续可能调整 | ||
| 47 | + - 红点显示阈值默认为 1(即有未读消息时显示) | ||
| 48 | + - 完整使用文档:`docs/features/tabbar-badge.md` | ||
| 49 | + | ||
| 50 | +--- | ||
| 51 | + | ||
| 8 | ## [2026-02-03] - 优化搜索页清空逻辑和引导文案 | 52 | ## [2026-02-03] - 优化搜索页清空逻辑和引导文案 |
| 9 | 53 | ||
| 10 | ### 优化 | 54 | ### 优化 | ... | ... |
docs/features/tabbar-badge.md
0 → 100644
| 1 | +# TabBar 红点提醒功能 | ||
| 2 | + | ||
| 3 | +## 📖 功能说明 | ||
| 4 | + | ||
| 5 | +TabBar 红点提醒功能用于在"我的"按钮右上角显示红点,提示用户有未读消息或通知。 | ||
| 6 | + | ||
| 7 | +## ⚙️ 功能开关 | ||
| 8 | + | ||
| 9 | +### 配置文件:`src/config/features.js` | ||
| 10 | + | ||
| 11 | +```javascript | ||
| 12 | +export const features = { | ||
| 13 | + // 🔴 功能总开关(默认关闭) | ||
| 14 | + tabbarBadge: false, | ||
| 15 | + | ||
| 16 | + // 📊 字段名称(根据后端实际返回调整) | ||
| 17 | + tabbarBadgeField: 'unread_count', // 当前使用 'unread_count' | ||
| 18 | + | ||
| 19 | + // 🎯 显示阈值 | ||
| 20 | + tabbarBadgeThreshold: 1 // 当 unread_count >= 1 时显示红点 | ||
| 21 | +} | ||
| 22 | +``` | ||
| 23 | + | ||
| 24 | +## 🚀 启用功能 | ||
| 25 | + | ||
| 26 | +### 方式 1: 修改配置文件(推荐) | ||
| 27 | + | ||
| 28 | +```javascript | ||
| 29 | +// src/config/features.js | ||
| 30 | +export const features = { | ||
| 31 | + tabbarBadge: true, // ✅ 改为 true 启用功能 | ||
| 32 | + // ... | ||
| 33 | +} | ||
| 34 | +``` | ||
| 35 | + | ||
| 36 | +### 方式 2: 临时测试(在浏览器控制台) | ||
| 37 | + | ||
| 38 | +```javascript | ||
| 39 | +import { useUserStore } from '@/stores/user' | ||
| 40 | +const userStore = useUserStore() | ||
| 41 | + | ||
| 42 | +// 测试红点显示 | ||
| 43 | +userStore.userInfo = { | ||
| 44 | + ...userStore.userInfo, | ||
| 45 | + unread_count: 5 // 模拟 5 条未读消息 | ||
| 46 | +} | ||
| 47 | +``` | ||
| 48 | + | ||
| 49 | +## 📊 数据流 | ||
| 50 | + | ||
| 51 | +``` | ||
| 52 | +┌─────────────────────────────────────────────────┐ | ||
| 53 | +│ 页面显示(useShow) │ | ||
| 54 | +│ userStore.fetchUserInfo() │ | ||
| 55 | +└──────────────────┬──────────────────────────────┘ | ||
| 56 | + │ | ||
| 57 | + ▼ | ||
| 58 | +┌─────────────────────────────────────────────────┐ | ||
| 59 | +│ 后端接口返回用户信息 │ | ||
| 60 | +│ { │ | ||
| 61 | +│ user: { │ | ||
| 62 | +│ unread_count: 5, ← 红点字段 │ | ||
| 63 | +│ ... │ | ||
| 64 | +│ } │ | ||
| 65 | +│ } │ | ||
| 66 | +└──────────────────┬──────────────────────────────┘ | ||
| 67 | + │ | ||
| 68 | + ▼ | ||
| 69 | +┌─────────────────────────────────────────────────┐ | ||
| 70 | +│ User Store 计算红点状态 │ | ||
| 71 | +│ computed tabBarBadges() │ | ||
| 72 | +│ └─ 读取 features.tabbarBadge │ | ||
| 73 | +│ └─ 读取 userInfo.unread_count │ | ||
| 74 | +│ └─ 返回 ['me'] 或 [] │ | ||
| 75 | +└──────────────────┬──────────────────────────────┘ | ||
| 76 | + │ | ||
| 77 | + ▼ | ||
| 78 | +┌─────────────────────────────────────────────────┐ | ||
| 79 | +│ TabBar 组件 │ | ||
| 80 | +│ 自动读取 userStore.tabBarBadges │ | ||
| 81 | +│ 响应式更新红点显示 ✨ │ | ||
| 82 | +└─────────────────────────────────────────────────┘ | ||
| 83 | +``` | ||
| 84 | + | ||
| 85 | +## 🔄 刷新时机 | ||
| 86 | + | ||
| 87 | +### 自动刷新(已实现) | ||
| 88 | + | ||
| 89 | +```javascript | ||
| 90 | +// ✅ 首页 - 已添加 | ||
| 91 | +// pages/index/index.vue | ||
| 92 | +useShow(() => { | ||
| 93 | + if (userStore.isLoggedIn) { | ||
| 94 | + userStore.fetchUserInfo() | ||
| 95 | + } | ||
| 96 | +}) | ||
| 97 | +``` | ||
| 98 | + | ||
| 99 | +### 手动刷新(可选) | ||
| 100 | + | ||
| 101 | +```javascript | ||
| 102 | +// 在其他页面需要时添加 | ||
| 103 | +import { useShow } from '@tarojs/taro' | ||
| 104 | +import { useUserStore } from '@/stores/user' | ||
| 105 | + | ||
| 106 | +const userStore = useUserStore() | ||
| 107 | + | ||
| 108 | +useShow(() => { | ||
| 109 | + userStore.fetchUserInfo() | ||
| 110 | +}) | ||
| 111 | +``` | ||
| 112 | + | ||
| 113 | +## 🎨 字段类型支持 | ||
| 114 | + | ||
| 115 | +### 数字类型(默认) | ||
| 116 | + | ||
| 117 | +```javascript | ||
| 118 | +// 用户信息 | ||
| 119 | +{ | ||
| 120 | + unread_count: 5 // 数字 | ||
| 121 | +} | ||
| 122 | + | ||
| 123 | +// 配置 | ||
| 124 | +tabbarBadgeField: 'unread_count' | ||
| 125 | +tabbarBadgeThreshold: 1 // >= 1 显示红点 | ||
| 126 | +``` | ||
| 127 | + | ||
| 128 | +### 布尔类型 | ||
| 129 | + | ||
| 130 | +```javascript | ||
| 131 | +// 用户信息 | ||
| 132 | +{ | ||
| 133 | + has_notification: true // 布尔 | ||
| 134 | +} | ||
| 135 | + | ||
| 136 | +// 配置 | ||
| 137 | +tabbarBadgeField: 'has_notification' | ||
| 138 | +tabbarBadgeThreshold: 1 // 布尔类型无效 | ||
| 139 | +``` | ||
| 140 | + | ||
| 141 | +## 🔧 调整字段名称 | ||
| 142 | + | ||
| 143 | +当后端接口字段变化时,只需修改配置文件: | ||
| 144 | + | ||
| 145 | +```javascript | ||
| 146 | +// 场景 1: 字段改名 | ||
| 147 | +tabbarBadgeField: 'message_badge' // 从 'unread_count' 改名 | ||
| 148 | + | ||
| 149 | +// 场景 2: 改用布尔值 | ||
| 150 | +tabbarBadgeField: 'has_unread_message' | ||
| 151 | + | ||
| 152 | +// 场景 3: 嵌套字段(需要扩展逻辑) | ||
| 153 | +tabbarBadgeField: 'notification.unread.count' | ||
| 154 | +``` | ||
| 155 | + | ||
| 156 | +## 🧪 测试方法 | ||
| 157 | + | ||
| 158 | +### 方法 1: 模拟数据 | ||
| 159 | + | ||
| 160 | +```javascript | ||
| 161 | +// 在浏览器控制台 | ||
| 162 | +import { useUserStore } from '@/stores/user' | ||
| 163 | +const userStore = useUserStore() | ||
| 164 | + | ||
| 165 | +// 测试显示红点 | ||
| 166 | +userStore.userInfo = { | ||
| 167 | + ...userStore.userInfo, | ||
| 168 | + unread_count: 5 | ||
| 169 | +} | ||
| 170 | + | ||
| 171 | +// 测试隐藏红点 | ||
| 172 | +userStore.userInfo = { | ||
| 173 | + ...userStore.userInfo, | ||
| 174 | + unread_count: 0 | ||
| 175 | +} | ||
| 176 | +``` | ||
| 177 | + | ||
| 178 | +### 方法 2: 修改配置 | ||
| 179 | + | ||
| 180 | +```javascript | ||
| 181 | +// 临时关闭功能 | ||
| 182 | +import { features } from '@/config/features' | ||
| 183 | +features.tabbarBadge = false | ||
| 184 | + | ||
| 185 | +// 临时开启功能 | ||
| 186 | +features.tabbarBadge = true | ||
| 187 | +``` | ||
| 188 | + | ||
| 189 | +## 📋 上线检查清单 | ||
| 190 | + | ||
| 191 | +在正式上线前,确认: | ||
| 192 | + | ||
| 193 | +- [ ] ✅ 功能开关已启用:`features.tabbarBadge = true` | ||
| 194 | +- [ ] ✅ 字段名称正确:与后端接口返回字段一致 | ||
| 195 | +- [ ] ✅ 阈值设置合理:`tabbarBadgeThreshold` | ||
| 196 | +- [ ] ✅ 关键页面已添加刷新逻辑(首页、我的) | ||
| 197 | +- [ ] ✅ 红点显示正常测试通过 | ||
| 198 | +- [ ] ✅ 性能测试:频繁切换页面无卡顿 | ||
| 199 | + | ||
| 200 | +## 🎯 最佳实践 | ||
| 201 | + | ||
| 202 | +### 1. 防止频繁请求 | ||
| 203 | + | ||
| 204 | +```javascript | ||
| 205 | +// ✅ 好:只在页面显示时请求 | ||
| 206 | +useShow(() => { | ||
| 207 | + userStore.fetchUserInfo() | ||
| 208 | +}) | ||
| 209 | + | ||
| 210 | +// ❌ 坏:使用定时器频繁请求 | ||
| 211 | +setInterval(() => { | ||
| 212 | + userStore.fetchUserInfo() | ||
| 213 | +}, 5000) | ||
| 214 | +``` | ||
| 215 | + | ||
| 216 | +### 2. 条件刷新 | ||
| 217 | + | ||
| 218 | +```javascript | ||
| 219 | +// ✅ 好:只在已登录时刷新 | ||
| 220 | +useShow(() => { | ||
| 221 | + if (userStore.isLoggedIn) { | ||
| 222 | + userStore.fetchUserInfo() | ||
| 223 | + } | ||
| 224 | +}) | ||
| 225 | +``` | ||
| 226 | + | ||
| 227 | +### 3. 错误处理 | ||
| 228 | + | ||
| 229 | +```javascript | ||
| 230 | +// ✅ 好:捕获错误 | ||
| 231 | +useShow(() => { | ||
| 232 | + userStore.fetchUserInfo().catch(err => { | ||
| 233 | + console.error('刷新用户信息失败:', err) | ||
| 234 | + }) | ||
| 235 | +}) | ||
| 236 | +``` | ||
| 237 | + | ||
| 238 | +## 🐛 常见问题 | ||
| 239 | + | ||
| 240 | +### Q: 红点不显示? | ||
| 241 | + | ||
| 242 | +**检查清单**: | ||
| 243 | +1. 功能开关是否启用:`features.tabbarBadge === true` | ||
| 244 | +2. 用户信息是否存在:`userStore.userInfo` | ||
| 245 | +3. 字段值是否满足条件:`unread_count >= 1` | ||
| 246 | +4. 是否已登录:`userStore.isLoggedIn === true` | ||
| 247 | + | ||
| 248 | +### Q: 红点一直显示? | ||
| 249 | + | ||
| 250 | +**原因**:后端返回的 `unread_count` 值 >= 1 | ||
| 251 | + | ||
| 252 | +**解决**: | ||
| 253 | +- 等待后端更新数据 | ||
| 254 | +- 或用户查看消息后,后端将字段值改为 0 | ||
| 255 | + | ||
| 256 | +### Q: 性能问题? | ||
| 257 | + | ||
| 258 | +**优化**: | ||
| 259 | +- 减少刷新频率 | ||
| 260 | +- 添加防抖(500ms) | ||
| 261 | +- 只在关键页面刷新 | ||
| 262 | + | ||
| 263 | +## 📚 相关文件 | ||
| 264 | + | ||
| 265 | +- 📄 `src/config/features.js` - 功能配置 | ||
| 266 | +- 📄 `src/stores/user.js` - 用户状态管理 | ||
| 267 | +- 📄 `src/components/TabBar.vue` - TabBar 组件 | ||
| 268 | +- 📄 `src/pages/index/index.vue` - 首页(已添加刷新逻辑) | ||
| 269 | + | ||
| 270 | +## 🔄 更新日志 | ||
| 271 | + | ||
| 272 | +### 2026-02-03 | ||
| 273 | +- ✨ 新增 TabBar 红点提醒功能 | ||
| 274 | +- ✅ 添加功能开关配置 | ||
| 275 | +- ✅ 实现自动刷新逻辑 | ||
| 276 | +- 📝 创建使用文档 |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2026-01-29 20:33:23 | 2 | * @Date: 2026-01-29 20:33:23 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2026-01-30 21:03:18 | 4 | + * @LastEditTime: 2026-02-03 22:06:37 |
| 5 | * @FilePath: /manulife-weapp/src/components/TabBar.vue | 5 | * @FilePath: /manulife-weapp/src/components/TabBar.vue |
| 6 | * @Description: 通用底部导航栏组件,用于页面底部固定导航栏,展示页面标题。 | 6 | * @Description: 通用底部导航栏组件,用于页面底部固定导航栏,展示页面标题。 |
| 7 | --> | 7 | --> |
| ... | @@ -11,7 +11,11 @@ | ... | @@ -11,7 +11,11 @@ |
| 11 | <view class="flex items-center justify-around py-[32rpx]"> | 11 | <view class="flex items-center justify-around py-[32rpx]"> |
| 12 | <view class="flex-1 flex flex-col items-center justify-center" v-for="(item, index) in tabs" :key="index" | 12 | <view class="flex-1 flex flex-col items-center justify-center" v-for="(item, index) in tabs" :key="index" |
| 13 | @tap="handleTabClick(item)"> | 13 | @tap="handleTabClick(item)"> |
| 14 | - <IconFont :name="item.icon" :class="[current === item.key ? 'text-blue-600' : 'text-gray-400']" size="24" /> | 14 | + <view class="relative"> |
| 15 | + <IconFont :name="item.icon" :class="[current === item.key ? 'text-blue-600' : 'text-gray-400']" size="24" /> | ||
| 16 | + <!-- 红点提醒标记 --> | ||
| 17 | + <view v-if="badges.includes(item.key)" class="tabbar-badge"></view> | ||
| 18 | + </view> | ||
| 15 | <text class="text-[20rpx] mt-[8rpx]" :class="[current === item.key ? 'text-blue-600' : 'text-gray-400']">{{ | 19 | <text class="text-[20rpx] mt-[8rpx]" :class="[current === item.key ? 'text-blue-600' : 'text-gray-400']">{{ |
| 16 | item.label }}</text> | 20 | item.label }}</text> |
| 17 | </view> | 21 | </view> |
| ... | @@ -20,10 +24,11 @@ | ... | @@ -20,10 +24,11 @@ |
| 20 | </template> | 24 | </template> |
| 21 | 25 | ||
| 22 | <script setup> | 26 | <script setup> |
| 23 | -import { shallowRef } from 'vue' | 27 | +import { shallowRef, computed } from 'vue' |
| 24 | import IconFont from '@/components/IconFont.vue' | 28 | import IconFont from '@/components/IconFont.vue' |
| 25 | import { useGo } from '@/hooks/useGo' | 29 | import { useGo } from '@/hooks/useGo' |
| 26 | import Taro from '@tarojs/taro' | 30 | import Taro from '@tarojs/taro' |
| 31 | +import { useUserStore } from '@/stores/user' | ||
| 27 | 32 | ||
| 28 | const props = defineProps({ | 33 | const props = defineProps({ |
| 29 | current: { | 34 | current: { |
| ... | @@ -33,6 +38,10 @@ const props = defineProps({ | ... | @@ -33,6 +38,10 @@ const props = defineProps({ |
| 33 | }) | 38 | }) |
| 34 | 39 | ||
| 35 | const go = useGo() | 40 | const go = useGo() |
| 41 | +const userStore = useUserStore() | ||
| 42 | + | ||
| 43 | +// 自动从 user store 读取红点状态 | ||
| 44 | +const badges = computed(() => userStore.tabBarBadges) | ||
| 36 | 45 | ||
| 37 | const tabs = shallowRef([ | 46 | const tabs = shallowRef([ |
| 38 | { | 47 | { |
| ... | @@ -104,3 +113,19 @@ const handleTabClick = (item) => { | ... | @@ -104,3 +113,19 @@ const handleTabClick = (item) => { |
| 104 | } | 113 | } |
| 105 | } | 114 | } |
| 106 | </script> | 115 | </script> |
| 116 | + | ||
| 117 | +<style lang="less"> | ||
| 118 | +/* 红点提醒标记样式 */ | ||
| 119 | +.tabbar-badge { | ||
| 120 | + position: absolute; | ||
| 121 | + top: -10rpx; | ||
| 122 | + right: -10rpx; | ||
| 123 | + width: 16rpx; | ||
| 124 | + height: 16rpx; | ||
| 125 | + background-color: #ff4d4f; | ||
| 126 | + border-radius: 50%; | ||
| 127 | + border: 2rpx solid #fff; | ||
| 128 | + box-shadow: 0 2rpx 8rpx rgba(255, 77, 79, 0.3); | ||
| 129 | + z-index: 1; | ||
| 130 | +} | ||
| 131 | +</style> | ... | ... |
src/config/features.js
0 → 100644
| 1 | +/** | ||
| 2 | + * 功能开关配置 | ||
| 3 | + * | ||
| 4 | + * @description 用于控制功能的启用/禁用状态,方便灰度发布和功能回滚 | ||
| 5 | + * @module config/features | ||
| 6 | + */ | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 功能配置项 | ||
| 10 | + * | ||
| 11 | + * @property {boolean} tabbarBadge - TabBar 红点提醒功能 | ||
| 12 | + * - true: 启用红点提醒(根据后端返回的 unread_count 判断) | ||
| 13 | + * - false: 禁用红点提醒 | ||
| 14 | + * | ||
| 15 | + * @property {string} tabbarBadgeField - 红点字段名称 | ||
| 16 | + * - 从用户信息接口读取该字段判断是否显示红点 | ||
| 17 | + * - 当前使用 'unread_count',后续可能根据实际接口调整 | ||
| 18 | + * | ||
| 19 | + * @property {number} tabbarBadgeThreshold - 红点显示阈值 | ||
| 20 | + * - 当 unread_count >= 该值时显示红点 | ||
| 21 | + * - 默认为 1,即有未读消息时显示 | ||
| 22 | + */ | ||
| 23 | +export const features = { | ||
| 24 | + /** | ||
| 25 | + * TabBar 红点提醒功能开关 | ||
| 26 | + * | ||
| 27 | + * @type {boolean} | ||
| 28 | + * @default false - 默认关闭,等接口字段确定后再开启 | ||
| 29 | + */ | ||
| 30 | + tabbarBadge: false, | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 红点字段名称 | ||
| 34 | + * | ||
| 35 | + * @type {string} | ||
| 36 | + * @default 'unread_count' | ||
| 37 | + * | ||
| 38 | + * @example | ||
| 39 | + * // 如果后端返回不同字段,修改这里即可 | ||
| 40 | + * tabbarBadgeField: 'has_notification' // 布尔值 | ||
| 41 | + * tabbarBadgeField: 'unread_count' // 数字 | ||
| 42 | + * tabbarBadgeField: 'message_badge' // 对象 | ||
| 43 | + */ | ||
| 44 | + tabbarBadgeField: 'unread_count', | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 红点显示阈值 | ||
| 48 | + * | ||
| 49 | + * @type {number} | ||
| 50 | + * @default 1 | ||
| 51 | + * | ||
| 52 | + * @description | ||
| 53 | + * - 当字段为数字时:unread_count >= 1 显示红点 | ||
| 54 | + * - 当字段为布尔值时:此配置无效 | ||
| 55 | + */ | ||
| 56 | + tabbarBadgeThreshold: 1 | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +/** | ||
| 60 | + * 检查功能是否启用 | ||
| 61 | + * | ||
| 62 | + * @param {string} featureName - 功能名称 | ||
| 63 | + * @returns {boolean} 功能是否启用 | ||
| 64 | + * | ||
| 65 | + * @example | ||
| 66 | + * import { isFeatureEnabled } from '@/config/features' | ||
| 67 | + * | ||
| 68 | + * if (isFeatureEnabled('tabbarBadge')) { | ||
| 69 | + * // 显示红点 | ||
| 70 | + * } | ||
| 71 | + */ | ||
| 72 | +export function isFeatureEnabled(featureName) { | ||
| 73 | + return features[featureName] === true | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +/** | ||
| 77 | + * 获取功能配置 | ||
| 78 | + * | ||
| 79 | + * @param {string} featureName - 功能名称 | ||
| 80 | + * @returns {*} 功能配置值 | ||
| 81 | + * | ||
| 82 | + * @example | ||
| 83 | + * import { getFeatureConfig } from '@/config/features' | ||
| 84 | + * | ||
| 85 | + * const field = getFeatureConfig('tabbarBadgeField') | ||
| 86 | + * console.log(field) // 'unread_count' | ||
| 87 | + */ | ||
| 88 | +export function getFeatureConfig(featureName) { | ||
| 89 | + return features[featureName] | ||
| 90 | +} |
| ... | @@ -164,10 +164,11 @@ | ... | @@ -164,10 +164,11 @@ |
| 164 | 164 | ||
| 165 | <script setup> | 165 | <script setup> |
| 166 | import { ref, shallowRef } from 'vue'; | 166 | import { ref, shallowRef } from 'vue'; |
| 167 | -import Taro, { useShareAppMessage, useLoad } from '@tarojs/taro'; | 167 | +import Taro, { useShareAppMessage, useLoad, useShow } from '@tarojs/taro'; |
| 168 | import { useGo } from '@/hooks/useGo'; | 168 | import { useGo } from '@/hooks/useGo'; |
| 169 | import { useListItemClick, ListType } from '@/composables/useListItemClick'; | 169 | import { useListItemClick, ListType } from '@/composables/useListItemClick'; |
| 170 | import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons'; | 170 | import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons'; |
| 171 | +import { useUserStore } from '@/stores/user'; | ||
| 171 | import TabBar from '@/components/TabBar.vue'; | 172 | import TabBar from '@/components/TabBar.vue'; |
| 172 | import IconFont from '@/components/IconFont.vue'; | 173 | import IconFont from '@/components/IconFont.vue'; |
| 173 | import PlanPopup from '@/components/PlanPopup/index.vue'; | 174 | import PlanPopup from '@/components/PlanPopup/index.vue'; |
| ... | @@ -176,6 +177,9 @@ import SchemeB from '@/components/PlanSchemes/SchemeB.vue'; | ... | @@ -176,6 +177,9 @@ import SchemeB from '@/components/PlanSchemes/SchemeB.vue'; |
| 176 | import ListItemActions from '@/components/ListItemActions/index.vue'; | 177 | import ListItemActions from '@/components/ListItemActions/index.vue'; |
| 177 | import { listAPI } from '@/api/get_product'; | 178 | import { listAPI } from '@/api/get_product'; |
| 178 | 179 | ||
| 180 | +// User Store | ||
| 181 | +const userStore = useUserStore(); | ||
| 182 | + | ||
| 179 | // Plan Popup State | 183 | // Plan Popup State |
| 180 | const showPlanPopup = ref(false); | 184 | const showPlanPopup = ref(false); |
| 181 | const currentScheme = ref('A'); | 185 | const currentScheme = ref('A'); |
| ... | @@ -337,6 +341,16 @@ useLoad(() => { | ... | @@ -337,6 +341,16 @@ useLoad(() => { |
| 337 | fetchHotProducts(); | 341 | fetchHotProducts(); |
| 338 | }); | 342 | }); |
| 339 | 343 | ||
| 344 | +// 页面显示时刷新用户信息(更新 TabBar 红点状态) | ||
| 345 | +useShow(() => { | ||
| 346 | + // 只在已登录状态下刷新 | ||
| 347 | + if (userStore.isLoggedIn) { | ||
| 348 | + userStore.fetchUserInfo().catch(err => { | ||
| 349 | + console.error('刷新用户信息失败:', err); | ||
| 350 | + }); | ||
| 351 | + } | ||
| 352 | +}); | ||
| 353 | + | ||
| 340 | useShareAppMessage(() => { | 354 | useShareAppMessage(() => { |
| 341 | return { | 355 | return { |
| 342 | title: '臻奇智荟圈', | 356 | title: '臻奇智荟圈', | ... | ... |
| ... | @@ -6,10 +6,11 @@ | ... | @@ -6,10 +6,11 @@ |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | import { defineStore } from 'pinia' | 8 | import { defineStore } from 'pinia' |
| 9 | -import { ref } from 'vue' | 9 | +import { ref, computed } from 'vue' |
| 10 | import Taro from '@tarojs/taro' | 10 | import Taro from '@tarojs/taro' |
| 11 | import { loginStatusAPI, loginAPI, getProfileAPI, logoutAPI } from '@/api/user' | 11 | import { loginStatusAPI, loginAPI, getProfileAPI, logoutAPI } from '@/api/user' |
| 12 | import { ensureOpenidAuthorized } from '@/utils/openid' | 12 | import { ensureOpenidAuthorized } from '@/utils/openid' |
| 13 | +import { isFeatureEnabled, getFeatureConfig } from '@/config/features' | ||
| 13 | 14 | ||
| 14 | export const useUserStore = defineStore('user', () => { | 15 | export const useUserStore = defineStore('user', () => { |
| 15 | // ========== 状态 ========== | 16 | // ========== 状态 ========== |
| ... | @@ -161,6 +162,57 @@ export const useUserStore = defineStore('user', () => { | ... | @@ -161,6 +162,57 @@ export const useUserStore = defineStore('user', () => { |
| 161 | } | 162 | } |
| 162 | } | 163 | } |
| 163 | 164 | ||
| 165 | + /** | ||
| 166 | + * TabBar 红点状态 | ||
| 167 | + * | ||
| 168 | + * @description 根据 userInfo 中的字段计算是否显示红点 | ||
| 169 | + * - 只在功能开关启用时生效 | ||
| 170 | + * - 只处理 'me' 按钮的红点 | ||
| 171 | + * - 根据 unread_count 字段判断(可配置) | ||
| 172 | + * | ||
| 173 | + * @returns {string[]} 需要显示红点的 tab key 数组 | ||
| 174 | + * | ||
| 175 | + * @example | ||
| 176 | + * // 返回 ['me'] 表示在 '我的' 按钮显示红点 | ||
| 177 | + * // 返回 [] 表示不显示红点 | ||
| 178 | + */ | ||
| 179 | + const tabBarBadges = computed(() => { | ||
| 180 | + // 1. 检查功能开关 | ||
| 181 | + if (!isFeatureEnabled('tabbarBadge')) { | ||
| 182 | + return [] | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + // 2. 检查用户信息是否存在 | ||
| 186 | + if (!userInfo.value) { | ||
| 187 | + return [] | ||
| 188 | + } | ||
| 189 | + | ||
| 190 | + // 3. 获取配置的字段名和阈值 | ||
| 191 | + const fieldName = getFeatureConfig('tabbarBadgeField') // 'unread_count' | ||
| 192 | + const threshold = getFeatureConfig('tabbarBadgeThreshold') // 1 | ||
| 193 | + | ||
| 194 | + // 4. 读取字段值 | ||
| 195 | + const fieldValue = userInfo.value[fieldName] | ||
| 196 | + | ||
| 197 | + // 5. 判断是否显示红点 | ||
| 198 | + const badges = [] | ||
| 199 | + | ||
| 200 | + // 处理数字类型(如 unread_count: 5) | ||
| 201 | + if (typeof fieldValue === 'number') { | ||
| 202 | + if (fieldValue >= threshold) { | ||
| 203 | + badges.push('me') | ||
| 204 | + } | ||
| 205 | + } | ||
| 206 | + // 处理布尔类型(如 has_notification: true) | ||
| 207 | + else if (typeof fieldValue === 'boolean') { | ||
| 208 | + if (fieldValue) { | ||
| 209 | + badges.push('me') | ||
| 210 | + } | ||
| 211 | + } | ||
| 212 | + | ||
| 213 | + return badges | ||
| 214 | + }) | ||
| 215 | + | ||
| 164 | // ========== 返回 ========== | 216 | // ========== 返回 ========== |
| 165 | return { | 217 | return { |
| 166 | // 状态 | 218 | // 状态 |
| ... | @@ -169,6 +221,9 @@ export const useUserStore = defineStore('user', () => { | ... | @@ -169,6 +221,9 @@ export const useUserStore = defineStore('user', () => { |
| 169 | isLoggedIn, | 221 | isLoggedIn, |
| 170 | loading, | 222 | loading, |
| 171 | 223 | ||
| 224 | + // 计算属性 | ||
| 225 | + tabBarBadges, | ||
| 226 | + | ||
| 172 | // 方法 | 227 | // 方法 |
| 173 | checkLoginStatus, | 228 | checkLoginStatus, |
| 174 | fetchUserInfo, | 229 | fetchUserInfo, | ... | ... |
-
Please register or login to post a comment