# 常见任务和模式 ## 图标库使用规范 - 禁止使用 MaterialIcons **最后更新**: 2025-10-24 ### 重要规则 **项目中不允许使用 MaterialIcons**,所有图标必须使用 Ionicons 以保持图标库的一致性。 ### 问题描述 在项目中发现使用 MaterialIcons 的情况,需要将所有 MaterialIcons 替换为 Ionicons,以保持图标库的一致性。 ### 解决方案 将所有 MaterialIcons 导入和使用替换为对应的 Ionicons。 ### 实现模式 #### 1. 替换导入语句 ```typescript // ❌ 禁止使用 import { MaterialIcons } from '@expo/vector-icons'; // ✅ 正确写法 import { Ionicons } from '@expo/vector-icons'; ``` #### 2. 替换图标名称和属性 ```typescript // ❌ 禁止使用 // ✅ 正确写法 - 使用 HeaderBar 中的返回按钮实现 ``` #### 3. 常见图标映射 - `arrow-back-ios` → `chevron-back` (返回按钮) - `auto-awesome` → `star` (星星/自动推荐) - `tips-and-updates` → `bulb` (提示/建议) - `fact-check` → `checkbox` (检查/确认) - `check-circle` → `checkmark-circle` (勾选圆圈) - `remove` → `remove` (移除/删除,名称相同) ### 重要注意事项 1. **图标大小调整**:Ionicons 和 MaterialIcons 的默认大小可能不同,需要适当调整 2. **图标名称差异**:两个图标库的图标名称不同,需要找到对应的功能图标 3. **样式一致性**:确保替换后的图标在视觉上与原设计保持一致 4. **Liquid Glass 兼容性**:替换后的图标需要继续支持 Liquid Glass 效果 5. **代码审查**:在代码审查中需要特别检查是否使用了 MaterialIcons ### 参考实现 - `components/ui/HeaderBar.tsx` - 返回按钮的标准实现 - `components/model/MembershipModal.tsx` - 完整的 MaterialIcons 替换示例 ## 按钮组件 Liquid Glass 兼容性 **最后更新**: 2025-10-24 ### 重要原则 **所有按钮组件都需要尝试兼容 Liquid Glass**,这是项目的设计要求。 ### 实现模式 #### 1. 导入必要的组件 ```typescript import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; ``` #### 2. 检查设备支持情况 ```typescript const isGlassAvailable = isLiquidGlassAvailable(); ``` #### 3. 实现条件渲染的按钮 ```typescript {isLiquidGlassAvailable() ? ( ) : ( )} ``` #### 4. 定义样式 ```typescript const styles = StyleSheet.create({ button: { width: 40, height: 40, borderRadius: 20, // 圆形按钮 alignItems: 'center', justifyContent: 'center', overflow: 'hidden', // 保证玻璃边界圆角效果 // 其他通用样式... }, fallbackButton: { backgroundColor: 'rgba(255, 255, 255, 0.9)', borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.3)', }, }); ``` ### 重要注意事项 1. **兼容性检查**:必须使用 `isLiquidGlassAvailable()` 检查设备支持情况 2. **overflow: 'hidden'**:GlassView 组件需要设置此属性以保证圆角效果 3. **降级样式**:为不支持 Liquid Glass 的设备提供视觉上相似的替代方案 4. **交互反馈**:设置 `isInteractive={true}` 启用原生的触觉反馈 5. **图标居中**:确保使用 `alignItems: 'center'` 和 `justifyContent: 'center'` 使图标完全居中 6. **色调自定义**:通过 `tintColor` 属性自定义按钮的颜色主题 ### 常用配置 - **glassEffectStyle**: "clear"(透明)或 "regular"(常规) - **tintColor**: 根据按钮功能选择合适的颜色 - 返回/导航操作:白色系 `rgba(255, 255, 255, 0.3)` - 删除操作:红色系 `rgba(244, 67, 54, 0.2)` - 确认操作:绿色系 `rgba(76, 175, 80, 0.2)` - 信息操作:蓝色系 `rgba(33, 150, 243, 0.2)` ### 参考实现 - `components/model/MembershipModal.tsx` - 悬浮返回按钮 - `components/glass/button.tsx` - 通用 Glass 按钮组件 - `app/(tabs)/_layout.tsx` - 标签栏按钮实现 ## HeaderBar 顶部距离处理 **最后更新**: 2025-10-16 ### 问题描述 当使用 HeaderBar 组件时,需要正确处理内容区域的顶部距离,确保内容不会被状态栏或刘海屏遮挡。 ### 解决方案 使用 `useSafeAreaTop` hook 获取安全区域顶部距离,并应用到内容容器的样式中。 ### 实现模式 #### 1. 导入必要的 hook ```typescript import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding'; ``` #### 2. 在组件中获取 safeAreaTop ```typescript const safeAreaTop = useSafeAreaTop() ``` #### 3. 应用到内容容器 ```typescript // 方式1: 直接应用到 View 组件 // 方式2: 应用到 ScrollView 的 contentContainerStyle // 方式3: 应用到 SectionList 的 style ``` ### 重要注意事项 1. **不要在 StyleSheet 中使用变量**:不能在 `StyleSheet.create()` 中直接使用 `safeAreaTop` 变量 2. **使用动态样式**:必须通过内联样式或数组样式的方式动态应用 `safeAreaTop` 3. **不需要额外偏移**:通常只需要 `safeAreaTop`,不需要添加额外的固定像素值 ### 示例代码 ```typescript // ❌ 错误写法 - 在 StyleSheet 中使用变量 const styles = StyleSheet.create({ filterContainer: { paddingTop: safeAreaTop, // 这会导致错误 }, }); // ✅ 正确写法 - 使用动态样式 ``` ### 参考页面 - `app/steps/detail.tsx` - `app/water/detail.tsx` - `app/profile/goals.tsx` - `app/workout/history.tsx` - `app/challenges/[id]/leaderboard.tsx` ## Liquid Glass 风格图标按钮实现 **最后更新**: 2025-10-16 ### 问题描述 在应用中实现符合 Liquid Glass 设计风格的图标按钮,需要考虑毛玻璃效果和兼容性处理。 ### 解决方案 使用 `GlassView` 组件实现毛玻璃效果,并提供不支持 Liquid Glass 的设备的降级方案。 ### 实现模式 #### 1. 导入必要的组件和函数 ```typescript import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; ``` #### 2. 检查设备支持情况 ```typescript const isGlassAvailable = isLiquidGlassAvailable(); ``` #### 3. 实现条件渲染的按钮 ```typescript {isGlassAvailable ? ( ) : ( )} ``` #### 4. 定义样式 ```typescript const styles = StyleSheet.create({ glassButton: { width: 36, height: 36, borderRadius: 18, // 圆形按钮 alignItems: 'center', justifyContent: 'center', overflow: 'hidden', // 保证玻璃边界圆角效果 }, fallbackButton: { borderWidth: 1, borderColor: 'rgba(244, 67, 54, 0.3)', backgroundColor: 'rgba(244, 67, 54, 0.1)', }, }); ``` ### 重要注意事项 1. **兼容性处理**:必须使用 `isLiquidGlassAvailable()` 检查设备支持情况 2. **overflow: 'hidden'**:GlassView 组件需要设置此属性以保证圆角效果 3. **降级样式**:为不支持 Liquid Glass 的设备提供视觉上相似的替代方案 4. **交互反馈**:设置 `isInteractive={true}` 启用原生的触觉反馈 5. **色调自定义**:通过 `tintColor` 属性自定义按钮的颜色主题 ### 常用配置 - **glassEffectStyle**: "clear"(透明)或 "regular"(常规) - **tintColor**: 根据按钮功能选择合适的颜色 - 删除操作:红色系 `rgba(244, 67, 54, 0.2)` - 确认操作:绿色系 `rgba(76, 175, 80, 0.2)` - 信息操作:蓝色系 `rgba(33, 150, 243, 0.2)` ### 参考实现 - `app/food/nutrition-analysis-history.tsx` - 删除按钮实现 - `components/glass/button.tsx` - 通用 Glass 按钮组件 - `app/(tabs)/_layout.tsx` - 标签栏按钮实现 ## 登录验证实现模式 **最后更新**: 2025-10-16 ### 问题描述 在应用中实现需要登录才能访问的功能时,需要判断用户是否已登录,未登录时先跳转到登录页面。 ### 解决方案 使用 `useAuthGuard` hook 中的 `pushIfAuthedElseLogin` 方法处理需要登录验证的导航操作,使用 `ensureLoggedIn` 方法处理需要登录验证的功能实现。 ### 权限校验原则 **重要**: 功能实现如果包含服务端接口的调用,需要使用 `ensureLoggedIn` 来判断用户是否登录。 ### 实现模式 #### 1. 导入必要的 hook ```typescript import { useAuthGuard } from '@/hooks/useAuthGuard'; ``` #### 2. 在组件中获取方法 ```typescript const { pushIfAuthedElseLogin, ensureLoggedIn } = useAuthGuard(); ``` #### 3. 替换导航操作 ```typescript // ❌ 原来的写法 - 没有登录验证 router.push('/food/nutrition-analysis-history')} activeOpacity={0.7} > // ✅ 修改后的写法 - 带登录验证 pushIfAuthedElseLogin('/food/nutrition-analysis-history')} activeOpacity={0.7} > ``` #### 4. 服务端接口调用的登录验证 对于需要调用服务端接口的功能,使用 `ensureLoggedIn` 进行登录验证: ```typescript // ❌ 原来的写法 - 没有登录验证 startNewAnalysis(imageUri)} activeOpacity={0.8} > // ✅ 修改后的写法 - 带登录验证 { // 先验证登录状态 const isLoggedIn = await ensureLoggedIn(); if (isLoggedIn) { startNewAnalysis(imageUri); } }} activeOpacity={0.8} > ``` #### 5. 完整示例(包含 Liquid Glass 兼容性处理) ```typescript {isLiquidGlassAvailable() ? ( pushIfAuthedElseLogin('/food/nutrition-analysis-history')} activeOpacity={0.7} > ) : ( pushIfAuthedElseLogin('/food/nutrition-analysis-history')} style={[styles.historyButton, styles.fallbackBackground]} activeOpacity={0.7} > )} ``` ### 重要注意事项 1. **统一体验**:使用 `pushIfAuthedElseLogin` 可以确保登录后自动跳转到目标页面 2. **参数传递**:该方法支持传递路由参数,格式为 `pushIfAuthedElseLogin('/path', { param: value })` 3. **登录重定向**:登录页面会接收 `redirectTo` 和 `redirectParams` 参数用于登录后跳转 4. **兼容性**:与 Liquid Glass 设计风格完全兼容,可以同时使用 5. **服务端接口调用**:所有调用服务端接口的功能必须使用 `ensureLoggedIn` 进行登录验证 6. **异步处理**:`ensureLoggedIn` 是异步函数,需要使用 `await` 等待结果 ### 其他可用方法 - `ensureLoggedIn()` - 检查登录状态,未登录时跳转到登录页面,返回布尔值表示是否已登录 - `guardHandler(fn, options)` - 包装一个函数,在执行前确保用户已登录 - `isLoggedIn` - 布尔值,表示当前用户是否已登录 ### 使用场景选择 - **页面导航**:使用 `pushIfAuthedElseLogin` 处理页面跳转 - **服务端接口调用**:使用 `ensureLoggedIn` 验证登录状态后再执行功能 - **函数包装**:使用 `guardHandler` 包装需要登录验证的函数 ### 参考实现 - `app/food/nutrition-label-analysis.tsx` - 成分表分析功能登录验证 - `app/(tabs)/personal.tsx` - 个人中心编辑按钮 - `hooks/useAuthGuard.ts` - 完整的认证守卫实现 ## 路由常量管理 **最后更新**: 2025-10-16 ### 问题描述 在应用开发中,所有路由路径都应该使用常量定义,而不是硬编码字符串。这样可以确保路由的一致性,便于维护和重构。 ### 解决方案 将所有路由路径定义在 `constants/Routes.ts` 文件中,并在组件中使用这些常量。 ### 实现模式 #### 1. 添加新路由常量 在 `constants/Routes.ts` 文件中添加新的路由常量: ```typescript export const ROUTES = { // 现有路由... // 新增路由 FOOD_CAMERA: '/food/camera', } as const; ``` #### 2. 在组件中使用路由常量 导入并使用路由常量,而不是硬编码路径: ```typescript import { ROUTES } from '@/constants/Routes'; // ❌ 错误写法 - 硬编码路径 router.push('/food/camera?mealType=dinner'); // ✅ 正确写法 - 使用路由常量 router.push(`${ROUTES.FOOD_CAMERA}?mealType=dinner`); ``` #### 3. 结合登录验证使用 对于需要登录验证的路由,结合 `pushIfAuthedElseLogin` 使用: ```typescript import { ROUTES } from '@/constants/Routes'; import { useAuthGuard } from '@/hooks/useAuthGuard'; const { pushIfAuthedElseLogin } = useAuthGuard(); // 在需要登录验证的路由中使用 pushIfAuthedElseLogin(`${ROUTES.FOOD_CAMERA}?mealType=${currentMealType}`)} activeOpacity={0.7} > ``` ### 重要注意事项 1. **统一管理**:所有路由路径都必须在 `constants/Routes.ts` 中定义 2. **命名规范**:使用大写字母和下划线,如 `FOOD_CAMERA` 3. **路径一致性**:常量名应该清晰表达路由的用途 4. **参数处理**:查询参数和路径参数在使用时动态拼接 5. **类型安全**:使用 `as const` 确保类型推导 ### 路由分类 按照功能模块对路由进行分组: ```typescript export const ROUTES = { // Tab路由 TAB_EXPLORE: '/explore', TAB_COACH: '/coach', // 营养相关路由 NUTRITION_RECORDS: '/nutrition/records', FOOD_LIBRARY: '/food-library', FOOD_CAMERA: '/food/camera', // 用户相关路由 AUTH_LOGIN: '/auth/login', PROFILE_EDIT: '/profile/edit', } as const; ``` ### 参考实现 - `constants/Routes.ts` - 路由常量定义 - `components/NutritionRadarCard.tsx` - 使用路由常量和登录验证 - `app/food/camera.tsx` - 食物拍照页面实现