hookehuyr

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>
...@@ -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 ### 优化
......
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>
......
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,
......