From 8a7599f630df302e1d81335e34329dea010ec008 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Sat, 23 Aug 2025 13:53:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=9B=AE=E6=A0=87?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=BC=95=E5=AF=BC=E5=8A=9F=E8=83=BD=E5=8F=8A?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在目标页面中集成用户引导功能,帮助用户了解页面各项功能 - 创建 GoalsPageGuide 组件,支持多步骤引导和动态高亮功能区域 - 实现引导状态的检查、标记和重置功能,确保用户体验 - 添加开发测试按钮,方便开发者重置引导状态 - 更新相关文档,详细描述引导功能的实现和使用方法 --- app/(tabs)/goals.tsx | 51 +++- components/GoalsPageGuide.tsx | 457 ++++++++++++++++++++++++++++++ components/GuideTestButton.tsx | 53 ++++ components/TaskCard.tsx | 31 +- docs/user-guide-implementation.md | 158 +++++++++++ utils/devTools.ts | 13 + utils/guideHelpers.ts | 67 +++++ 7 files changed, 819 insertions(+), 11 deletions(-) create mode 100644 components/GoalsPageGuide.tsx create mode 100644 components/GuideTestButton.tsx create mode 100644 docs/user-guide-implementation.md create mode 100644 utils/guideHelpers.ts diff --git a/app/(tabs)/goals.tsx b/app/(tabs)/goals.tsx index 3f609b0..4d2c13f 100644 --- a/app/(tabs)/goals.tsx +++ b/app/(tabs)/goals.tsx @@ -1,4 +1,6 @@ import CreateGoalModal from '@/components/CreateGoalModal'; +import { GoalsPageGuide } from '@/components/GoalsPageGuide'; +import { GuideTestButton } from '@/components/GuideTestButton'; import { TaskCard } from '@/components/TaskCard'; import { TaskFilterTabs, TaskFilterType } from '@/components/TaskFilterTabs'; import { TaskProgressCard } from '@/components/TaskProgressCard'; @@ -10,6 +12,7 @@ import { useColorScheme } from '@/hooks/useColorScheme'; import { clearErrors, createGoal } from '@/store/goalsSlice'; import { clearErrors as clearTaskErrors, fetchTasks, loadMoreTasks } from '@/store/tasksSlice'; import { CreateGoalRequest, TaskListItem } from '@/types/goals'; +import { checkGuideCompleted, markGuideCompleted } from '@/utils/guideHelpers'; import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import { useFocusEffect } from '@react-navigation/native'; import dayjs from 'dayjs'; @@ -46,15 +49,32 @@ export default function GoalsScreen() { const [refreshing, setRefreshing] = useState(false); const [selectedFilter, setSelectedFilter] = useState('all'); const [modalKey, setModalKey] = useState(0); // 用于强制重新渲染弹窗 + const [showGuide, setShowGuide] = useState(false); // 控制引导显示 // 页面聚焦时重新加载数据 useFocusEffect( useCallback(() => { console.log('useFocusEffect - loading tasks'); loadTasks(); + checkAndShowGuide(); }, [dispatch]) ); + // 检查并显示用户引导 + const checkAndShowGuide = async () => { + try { + const hasCompletedGuide = await checkGuideCompleted('GOALS_PAGE'); + if (!hasCompletedGuide) { + // 延迟显示引导,确保页面完全加载 + setTimeout(() => { + setShowGuide(true); + }, 1000); + } + } catch (error) { + console.error('检查引导状态失败:', error); + } + }; + // 加载任务列表 const loadTasks = async () => { try { @@ -182,6 +202,17 @@ export default function GoalsScreen() { setSelectedFilter(filter); }; + // 处理引导完成 + const handleGuideComplete = async () => { + try { + await markGuideCompleted('GOALS_PAGE'); + setShowGuide(false); + } catch (error) { + console.error('保存引导状态失败:', error); + setShowGuide(false); + } + }; + // 渲染任务项 const renderTaskItem = ({ item }: { item: TaskListItem }) => ( - 等待完成 + 今日目标 让我们检查你的目标! @@ -290,6 +321,9 @@ export default function GoalsScreen() { style={[styles.cardGoalsButton, { borderColor: colorTokens.primary }]} onPress={handleNavigateToGoals} > + + 回顾 + + + {/* 目标页面引导 */} + + + {/* 开发测试按钮 */} + ); @@ -505,6 +549,11 @@ const styles = StyleSheet.create({ backgroundColor: '#F3F4F6', borderWidth: 1, }, + cardGoalsButtonText: { + fontSize: 12, + fontWeight: '600', + color: '#374151', + }, cardAddButton: { width: 28, height: 28, diff --git a/components/GoalsPageGuide.tsx b/components/GoalsPageGuide.tsx new file mode 100644 index 0000000..ef1359a --- /dev/null +++ b/components/GoalsPageGuide.tsx @@ -0,0 +1,457 @@ +import { TaskListItem } from '@/types/goals'; +import MaterialIcons from '@expo/vector-icons/MaterialIcons'; +import React, { useEffect, useRef, useState } from 'react'; +import { + Animated, + Dimensions, + Modal, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; + +interface GoalsPageGuideProps { + visible: boolean; + onComplete: () => void; + tasks?: TaskListItem[]; // 添加任务数据,用于智能引导 +} + +const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); + +// 计算精确的高亮位置 +const calculateHighlightPosition = (stepIndex: number, hasTasks: boolean) => { + const baseTop = 120; // 状态栏 + 标题区域高度 + const cardHeight = 180; // 任务进度卡片高度 + const filterHeight = 60; // 筛选标签高度 + const listHeight = 300; // 任务列表高度 + + switch (stepIndex) { + case 0: // 欢迎标题 + return { + top: baseTop - 40, + left: 20, + right: 20, + height: 60, + borderRadius: 12, + }; + case 1: // 任务进度卡片 + return { + top: baseTop + 20, + left: 20, + right: 20, + height: cardHeight, + borderRadius: 16, + }; + case 2: // 目标管理按钮(有任务时) + if (hasTasks) { + return { + top: baseTop + 40, + right: 60, + width: 40, + height: 28, + borderRadius: 14, + }; + } else { + return { + top: baseTop + 40, + right: 20, + width: 28, + height: 28, + borderRadius: 14, + }; + } + case 3: // 创建新目标按钮(有任务时) + if (hasTasks) { + return { + top: baseTop + 40, + right: 20, + width: 28, + height: 28, + borderRadius: 14, + }; + } else { + return null; // 没有这一步 + } + case 4: // 任务筛选标签 + return { + top: baseTop + cardHeight + 40, + left: 20, + right: 20, + height: filterHeight, + borderRadius: 24, + }; + case 5: // 任务列表 + return { + top: baseTop + cardHeight + filterHeight + 60, + left: 20, + right: 20, + height: listHeight, + borderRadius: 24, + }; + default: + return null; + } +}; + +export const GoalsPageGuide: React.FC = ({ + visible, + onComplete, + tasks = [], +}) => { + const [currentStep, setCurrentStep] = useState(0); + const fadeAnim = useRef(new Animated.Value(0)).current; + const scaleAnim = useRef(new Animated.Value(0.8)).current; + + // 根据任务数据智能生成引导步骤 + const generateSteps = () => { + const hasTasks = tasks.length > 0; + const hasCompletedTasks = tasks.some(task => task.status === 'completed'); + const hasPendingTasks = tasks.some(task => task.status === 'pending'); + + const baseSteps = [ + { + title: '欢迎来到目标页面', + description: '这里是您的目标管理中心,让我们一起来了解各个功能。', + icon: 'flag', + }, + { + title: '任务进度统计', + description: '这里显示您当天的任务完成情况,包括待完成、已完成和已跳过的任务数量。', + icon: 'analytics', + }, + ]; + + // 根据任务状态添加不同的引导内容 + if (!hasTasks) { + baseSteps.push({ + title: '创建您的第一个目标', + description: '点击加号按钮,创建您的第一个目标,系统会自动生成相应的任务。', + icon: 'add', + }); + } else { + baseSteps.push( + { + title: '目标管理', + description: '点击右上角的目标按钮,可以查看和管理您的所有目标。', + icon: 'flag', + }, + { + title: '创建新目标', + description: '点击加号按钮,可以快速创建新的目标。', + icon: 'add', + } + ); + } + + baseSteps.push({ + title: '任务筛选', + description: '使用这些标签可以筛选查看不同状态的任务。', + icon: 'filter-list', + }); + + // 根据任务状态调整任务列表的引导内容 + if (!hasTasks) { + baseSteps.push({ + title: '任务列表', + description: '创建目标后,您的任务将显示在这里。', + icon: 'list', + }); + } else if (!hasPendingTasks && hasCompletedTasks) { + baseSteps.push({ + title: '任务列表', + description: '您已完成所有任务!可以创建新目标或查看历史记录。', + icon: 'check-circle', + }); + } else { + baseSteps.push({ + title: '任务列表', + description: '这里显示您的所有任务,可以标记完成或跳过。', + icon: 'list', + }); + } + + return baseSteps; + }; + + const steps = generateSteps(); + const hasTasks = tasks.length > 0; + const currentHighlightPosition = calculateHighlightPosition(currentStep, hasTasks); + + useEffect(() => { + if (visible) { + Animated.parallel([ + Animated.timing(fadeAnim, { + toValue: 1, + duration: 300, + useNativeDriver: true, + }), + Animated.timing(scaleAnim, { + toValue: 1, + duration: 300, + useNativeDriver: true, + }), + ]).start(); + } else { + Animated.parallel([ + Animated.timing(fadeAnim, { + toValue: 0, + duration: 200, + useNativeDriver: true, + }), + Animated.timing(scaleAnim, { + toValue: 0.8, + duration: 200, + useNativeDriver: true, + }), + ]).start(); + } + }, [visible, fadeAnim, scaleAnim]); + + const handleNext = () => { + if (currentStep < steps.length - 1) { + setCurrentStep(currentStep + 1); + } else { + handleComplete(); + } + }; + + const handlePrevious = () => { + if (currentStep > 0) { + setCurrentStep(currentStep - 1); + } + }; + + const handleComplete = () => { + Animated.parallel([ + Animated.timing(fadeAnim, { + toValue: 0, + duration: 200, + useNativeDriver: true, + }), + Animated.timing(scaleAnim, { + toValue: 0.8, + duration: 200, + useNativeDriver: true, + }), + ]).start(() => { + setCurrentStep(0); + onComplete(); + }); + }; + + const handleSkip = () => { + handleComplete(); + }; + + if (!visible || !currentHighlightPosition) return null; + + const currentStepData = steps[currentStep]; + + return ( + + + + {/* 背景遮罩 */} + + {/* 高亮区域 */} + + + + + {/* 引导内容 */} + + {/* 步骤指示器 */} + + {steps.map((_, index) => ( + + ))} + + + {/* 图标 */} + + + + + {/* 标题 */} + {currentStepData.title} + + {/* 描述 */} + {currentStepData.description} + + {/* 按钮区域 */} + + + 跳过 + + + + {currentStep > 0 && ( + + 回顾 + + )} + + + + {currentStep === steps.length - 1 ? '完成' : '下一步'} + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.8)', + justifyContent: 'center', + alignItems: 'center', + }, + highlightArea: { + position: 'absolute', + justifyContent: 'center', + alignItems: 'center', + }, + highlightBorder: { + width: '100%', + height: '100%', + borderWidth: 2, + borderColor: '#7A5AF8', + backgroundColor: 'rgba(122, 90, 248, 0.08)', + shadowColor: '#7A5AF8', + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 4, + }, + guideContainer: { + position: 'absolute', + bottom: 120, + left: 20, + right: 20, + backgroundColor: '#FFFFFF', + borderRadius: 20, + padding: 24, + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 4, + }, + shadowOpacity: 0.25, + shadowRadius: 12, + elevation: 8, + }, + stepIndicator: { + flexDirection: 'row', + marginBottom: 20, + }, + stepDot: { + width: 8, + height: 8, + borderRadius: 4, + backgroundColor: '#E5E7EB', + marginHorizontal: 4, + }, + stepDotActive: { + backgroundColor: '#7A5AF8', + }, + iconContainer: { + width: 80, + height: 80, + borderRadius: 40, + backgroundColor: '#F3F4F6', + justifyContent: 'center', + alignItems: 'center', + marginBottom: 16, + }, + title: { + fontSize: 20, + fontWeight: '700', + color: '#1F2937', + textAlign: 'center', + marginBottom: 12, + }, + description: { + fontSize: 16, + color: '#6B7280', + textAlign: 'center', + lineHeight: 24, + marginBottom: 24, + }, + buttonContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + }, + navigationButtons: { + flexDirection: 'row', + gap: 12, + }, + skipButton: { + paddingHorizontal: 20, + paddingVertical: 12, + borderRadius: 12, + borderWidth: 1, + borderColor: '#E5E7EB', + }, + skipButtonText: { + fontSize: 16, + fontWeight: '600', + color: '#6B7280', + }, + previousButton: { + paddingHorizontal: 20, + paddingVertical: 12, + borderRadius: 12, + borderWidth: 1, + borderColor: '#7A5AF8', + backgroundColor: 'rgba(122, 90, 248, 0.1)', + }, + previousButtonText: { + fontSize: 16, + fontWeight: '600', + color: '#7A5AF8', + }, + nextButton: { + paddingHorizontal: 24, + paddingVertical: 12, + borderRadius: 12, + backgroundColor: '#7A5AF8', + }, + nextButtonText: { + fontSize: 16, + fontWeight: '600', + color: '#FFFFFF', + }, +}); diff --git a/components/GuideTestButton.tsx b/components/GuideTestButton.tsx new file mode 100644 index 0000000..0b761bf --- /dev/null +++ b/components/GuideTestButton.tsx @@ -0,0 +1,53 @@ +import { resetGuideStates } from '@/utils/devTools'; +import React from 'react'; +import { Alert, StyleSheet, Text, TouchableOpacity } from 'react-native'; + +interface GuideTestButtonProps { + visible?: boolean; +} + +export const GuideTestButton: React.FC = ({ visible = false }) => { + if (!visible) return null; + + const handleResetGuides = async () => { + Alert.alert( + '重置用户引导', + '确定要重置所有用户引导状态吗?这将清除用户已完成的引导记录。', + [ + { text: '取消', style: 'cancel' }, + { + text: '确定', + style: 'destructive', + onPress: async () => { + await resetGuideStates(); + Alert.alert('成功', '用户引导状态已重置,下次进入页面时将重新显示引导。'); + }, + }, + ] + ); + }; + + return ( + + 重置引导 + + ); +}; + +const styles = StyleSheet.create({ + button: { + position: 'absolute', + top: 50, + right: 20, + backgroundColor: '#FF6B6B', + paddingHorizontal: 12, + paddingVertical: 8, + borderRadius: 8, + zIndex: 1000, + }, + buttonText: { + color: '#FFFFFF', + fontSize: 12, + fontWeight: '600', + }, +}); diff --git a/components/TaskCard.tsx b/components/TaskCard.tsx index 9994f88..49eb94f 100644 --- a/components/TaskCard.tsx +++ b/components/TaskCard.tsx @@ -7,7 +7,7 @@ import { TaskListItem } from '@/types/goals'; import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import { useRouter } from 'expo-router'; import React from 'react'; -import { Alert, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { Alert, Animated, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; interface TaskCardProps { task: TaskListItem; @@ -21,6 +21,20 @@ export const TaskCard: React.FC = ({ const dispatch = useAppDispatch(); const { showConfirm } = useGlobalDialog(); const router = useRouter(); + + // 创建进度条动画值 + const progressAnimation = React.useRef(new Animated.Value(0)).current; + + // 当任务进度变化时,启动动画 + React.useEffect(() => { + const targetProgress = task.progressPercentage > 0 ? Math.min(task.progressPercentage, 100) : 2; + + Animated.timing(progressAnimation, { + toValue: targetProgress, + duration: 800, // 动画持续时间800毫秒 + useNativeDriver: false, // 因为我们要动画width属性,所以不能使用原生驱动 + }).start(); + }, [task.progressPercentage, progressAnimation]); const getStatusText = (status: string) => { @@ -191,18 +205,15 @@ export const TaskCard: React.FC = ({ {/* 进度条 */} - 0 ? `${Math.min(task.progressPercentage, 100)}%` : '2%', - backgroundColor: task.progressPercentage >= 100 - ? '#10B981' - : task.progressPercentage >= 50 - ? '#F59E0B' - : task.progressPercentage > 0 - ? colorTokens.primary - : '#E5E7EB', + width: progressAnimation.interpolate({ + inputRange: [0, 100], + outputRange: ['0%', '100%'], + }), + backgroundColor: task.progressPercentage > 0 ? colorTokens.primary : '#E5E7EB', }, ]} /> diff --git a/docs/user-guide-implementation.md b/docs/user-guide-implementation.md new file mode 100644 index 0000000..1a8496e --- /dev/null +++ b/docs/user-guide-implementation.md @@ -0,0 +1,158 @@ +# 用户引导功能实现文档 + +## 功能概述 + +用户引导功能用于在用户首次进入特定页面时,提供功能说明和操作指导,帮助用户更好地理解和使用应用功能。 + +## 已实现的引导 + +### 1. Goals页面 - 全面功能引导 + +**位置**: `app/(tabs)/goals.tsx` + +**引导内容**: +- 欢迎介绍:介绍目标页面的整体功能 +- 任务进度统计:说明任务进度卡片显示当天任务的完成情况 +- 目标管理:说明如何查看和管理目标 +- 创建新目标:说明如何快速创建新目标 +- 任务筛选:说明如何使用标签筛选任务 +- 任务列表:说明任务列表的功能和操作 + +**组件**: `GoalsPageGuide` + +## 技术实现 + +### 核心组件 + +1. **GoalsPageGuide** (`components/GoalsPageGuide.tsx`) + - 引导弹窗组件 + - 支持多步骤引导 + - 动态高亮不同功能区域 + - 平滑动画效果 + - 回顾功能支持 + +2. **引导状态管理** (`utils/guideHelpers.ts`) + - 使用AsyncStorage存储引导完成状态 + - 提供检查、标记、重置功能 + +### 引导状态键 + +```typescript +const GUIDE_KEYS = { + GOALS_PAGE: '@guide_goals_page_completed', +} as const; +``` + +## 使用方法 + +### 在页面中集成引导 + +1. **导入必要组件和工具**: +```typescript +import { GoalsPageGuide } from '@/components/GoalsPageGuide'; +import { checkGuideCompleted, markGuideCompleted } from '@/utils/guideHelpers'; +``` + +2. **添加状态管理**: +```typescript +const [showGuide, setShowGuide] = useState(false); +``` + +3. **检查引导状态**: +```typescript +const checkAndShowGuide = async () => { + try { + const hasCompletedGuide = await checkGuideCompleted('GOALS_PAGE'); + if (!hasCompletedGuide) { + setTimeout(() => { + setShowGuide(true); + }, 1000); + } + } catch (error) { + console.error('检查引导状态失败:', error); + } +}; +``` + +4. **处理引导完成**: +```typescript +const handleGuideComplete = async () => { + try { + await markGuideCompleted('GOALS_PAGE'); + setShowGuide(false); + } catch (error) { + console.error('保存引导状态失败:', error); + setShowGuide(false); + } +}; +``` + +5. **在页面中渲染组件**: +```typescript + +``` + +## 开发测试 + +### 测试按钮 + +在开发模式下,Goals页面右上角会显示"重置引导"按钮,用于测试引导功能。 + +### 手动重置引导状态 + +```typescript +import { resetGuideStates } from '@/utils/devTools'; + +// 重置所有引导状态 +await resetGuideStates(); +``` + +### 检查引导状态 + +```typescript +import { getAllGuideStatus } from '@/utils/guideHelpers'; + +// 获取所有引导状态 +const status = await getAllGuideStatus(); +console.log('引导状态:', status); +``` + +## 添加新的引导 + +### 1. 创建引导组件 + +参考 `TaskProgressCardGuide` 的结构创建新的引导组件。 + +### 2. 添加状态键 + +在 `utils/guideHelpers.ts` 中添加新的引导键: + +```typescript +const GUIDE_KEYS = { + GOALS_PAGE: '@guide_goals_page_completed', + NEW_GUIDE: '@guide_new_guide_completed', // 新增 +} as const; +``` + +### 3. 在页面中集成 + +按照上述使用方法在目标页面中集成引导功能。 + +## 设计原则 + +1. **用户友好**: 引导内容简洁明了,避免信息过载 +2. **可跳过**: 用户可以选择跳过引导 +3. **可回顾**: 用户可以回顾之前的步骤 +4. **一次性**: 引导只显示一次,避免重复打扰 +5. **响应式**: 引导内容适应不同屏幕尺寸 +6. **可测试**: 提供开发测试工具 + +## 注意事项 + +1. 引导应该在页面完全加载后显示,避免布局问题 +2. 引导状态应该持久化存储,避免重复显示 +3. 引导内容应该与页面功能紧密相关 +4. 测试时注意重置引导状态,确保功能正常 diff --git a/utils/devTools.ts b/utils/devTools.ts index 7f94b19..d30578a 100644 --- a/utils/devTools.ts +++ b/utils/devTools.ts @@ -1,5 +1,6 @@ import { STORAGE_KEYS } from '@/services/api'; import AsyncStorage from '@react-native-async-storage/async-storage'; +import { resetAllGuides } from './guideHelpers'; /** * 开发工具函数 - 清除隐私同意状态 @@ -45,3 +46,15 @@ export const clearAllUserData = async (): Promise => { console.error('清除用户数据失败:', error); } }; + +/** + * 重置所有用户引导状态(仅用于开发测试) + */ +export const resetGuideStates = async () => { + try { + await resetAllGuides(); + console.log('✅ 所有用户引导状态已重置'); + } catch (error) { + console.error('❌ 重置用户引导状态失败:', error); + } +}; diff --git a/utils/guideHelpers.ts b/utils/guideHelpers.ts new file mode 100644 index 0000000..74b599d --- /dev/null +++ b/utils/guideHelpers.ts @@ -0,0 +1,67 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; + +// 引导状态存储键 +const GUIDE_KEYS = { + GOALS_PAGE: '@guide_goals_page_completed', +} as const; + +/** + * 检查用户是否已经完成特定引导 + * @param guideKey 引导键名 + * @returns Promise 是否已完成 + */ +export const checkGuideCompleted = async (guideKey: keyof typeof GUIDE_KEYS): Promise => { + try { + const completed = await AsyncStorage.getItem(GUIDE_KEYS[guideKey]); + return completed === 'true'; + } catch (error) { + console.error('检查引导状态失败:', error); + return false; + } +}; + +/** + * 标记引导为已完成 + * @param guideKey 引导键名 + */ +export const markGuideCompleted = async (guideKey: keyof typeof GUIDE_KEYS): Promise => { + try { + await AsyncStorage.setItem(GUIDE_KEYS[guideKey], 'true'); + } catch (error) { + console.error('保存引导状态失败:', error); + } +}; + +/** + * 重置所有引导状态(用于测试或重置用户引导) + */ +export const resetAllGuides = async (): Promise => { + try { + const keys = Object.values(GUIDE_KEYS); + await AsyncStorage.multiRemove(keys); + console.log('所有引导状态已重置'); + } catch (error) { + console.error('重置引导状态失败:', error); + } +}; + +/** + * 获取所有引导状态 + * @returns Promise> 所有引导的完成状态 + */ +export const getAllGuideStatus = async (): Promise> => { + try { + const result: Record = {}; + const keys = Object.values(GUIDE_KEYS); + + for (const key of keys) { + const completed = await AsyncStorage.getItem(key); + result[key] = completed === 'true'; + } + + return result; + } catch (error) { + console.error('获取引导状态失败:', error); + return {}; + } +};