- 将 MaterialIcons 替换为 Ionicons 以保持图标库一致性 - 重新设计会员购买界面,采用分段卡片布局和权益对比表格 - 添加 Liquid Glass 兼容的悬浮返回按钮 - 优化套餐卡片样式,使用渐变背景和标签展示 - 添加会员权益对比功能,清晰展示 VIP 与普通用户差异 - 更新任务文档,记录图标库使用规范和按钮组件 Liquid Glass 兼容性实现模式
15 KiB
常见任务和模式
图标库使用规范 - 禁止使用 MaterialIcons
最后更新: 2025-10-24
重要规则
项目中不允许使用 MaterialIcons,所有图标必须使用 Ionicons 以保持图标库的一致性。
问题描述
在项目中发现使用 MaterialIcons 的情况,需要将所有 MaterialIcons 替换为 Ionicons,以保持图标库的一致性。
解决方案
将所有 MaterialIcons 导入和使用替换为对应的 Ionicons。
实现模式
1. 替换导入语句
// ❌ 禁止使用
import { MaterialIcons } from '@expo/vector-icons';
// ✅ 正确写法
import { Ionicons } from '@expo/vector-icons';
2. 替换图标名称和属性
// ❌ 禁止使用
<MaterialIcons name="arrow-back-ios" size={20} color="#333" />
// ✅ 正确写法 - 使用 HeaderBar 中的返回按钮实现
<Ionicons name="chevron-back" size={24} color="#333" />
3. 常见图标映射
arrow-back-ios→chevron-back(返回按钮)auto-awesome→star(星星/自动推荐)tips-and-updates→bulb(提示/建议)fact-check→checkbox(检查/确认)check-circle→checkmark-circle(勾选圆圈)remove→remove(移除/删除,名称相同)
重要注意事项
- 图标大小调整:Ionicons 和 MaterialIcons 的默认大小可能不同,需要适当调整
- 图标名称差异:两个图标库的图标名称不同,需要找到对应的功能图标
- 样式一致性:确保替换后的图标在视觉上与原设计保持一致
- Liquid Glass 兼容性:替换后的图标需要继续支持 Liquid Glass 效果
- 代码审查:在代码审查中需要特别检查是否使用了 MaterialIcons
参考实现
components/ui/HeaderBar.tsx- 返回按钮的标准实现components/model/MembershipModal.tsx- 完整的 MaterialIcons 替换示例
按钮组件 Liquid Glass 兼容性
最后更新: 2025-10-24
重要原则
所有按钮组件都需要尝试兼容 Liquid Glass,这是项目的设计要求。
实现模式
1. 导入必要的组件
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
2. 检查设备支持情况
const isGlassAvailable = isLiquidGlassAvailable();
3. 实现条件渲染的按钮
<TouchableOpacity
onPress={handlePress}
disabled={isLoading}
activeOpacity={0.7}
>
{isLiquidGlassAvailable() ? (
<GlassView
style={styles.button}
glassEffectStyle="clear" // 或 "regular"
tintColor="rgba(255, 255, 255, 0.3)" // 自定义色调
isInteractive={true} // 启用交互反馈
>
<Ionicons name="icon-name" size={20} color="#333" />
</GlassView>
) : (
<View style={[styles.button, styles.fallbackButton]}>
<Ionicons name="icon-name" size={20} color="#333" />
</View>
)}
</TouchableOpacity>
4. 定义样式
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)',
},
});
重要注意事项
- 兼容性检查:必须使用
isLiquidGlassAvailable()检查设备支持情况 - overflow: 'hidden':GlassView 组件需要设置此属性以保证圆角效果
- 降级样式:为不支持 Liquid Glass 的设备提供视觉上相似的替代方案
- 交互反馈:设置
isInteractive={true}启用原生的触觉反馈 - 图标居中:确保使用
alignItems: 'center'和justifyContent: 'center'使图标完全居中 - 色调自定义:通过
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
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
2. 在组件中获取 safeAreaTop
const safeAreaTop = useSafeAreaTop()
3. 应用到内容容器
// 方式1: 直接应用到 View 组件
<View style={[styles.filterContainer, { paddingTop: safeAreaTop }]}>
// 方式2: 应用到 ScrollView 的 contentContainerStyle
<ScrollView
contentContainerStyle={{ paddingTop: safeAreaTop }}
>
// 方式3: 应用到 SectionList 的 style
<SectionList
style={{ paddingTop: safeAreaTop }}
>
重要注意事项
- 不要在 StyleSheet 中使用变量:不能在
StyleSheet.create()中直接使用safeAreaTop变量 - 使用动态样式:必须通过内联样式或数组样式的方式动态应用
safeAreaTop - 不需要额外偏移:通常只需要
safeAreaTop,不需要添加额外的固定像素值
示例代码
// ❌ 错误写法 - 在 StyleSheet 中使用变量
const styles = StyleSheet.create({
filterContainer: {
paddingTop: safeAreaTop, // 这会导致错误
},
});
// ✅ 正确写法 - 使用动态样式
<View style={[styles.filterContainer, { paddingTop: safeAreaTop }]}>
参考页面
app/steps/detail.tsxapp/water/detail.tsxapp/profile/goals.tsxapp/workout/history.tsxapp/challenges/[id]/leaderboard.tsx
Liquid Glass 风格图标按钮实现
最后更新: 2025-10-16
问题描述
在应用中实现符合 Liquid Glass 设计风格的图标按钮,需要考虑毛玻璃效果和兼容性处理。
解决方案
使用 GlassView 组件实现毛玻璃效果,并提供不支持 Liquid Glass 的设备的降级方案。
实现模式
1. 导入必要的组件和函数
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
2. 检查设备支持情况
const isGlassAvailable = isLiquidGlassAvailable();
3. 实现条件渲染的按钮
<TouchableOpacity
onPress={handlePress}
disabled={isLoading}
activeOpacity={0.7}
>
{isGlassAvailable ? (
<GlassView
style={styles.glassButton}
glassEffectStyle="clear" // 或 "regular"
tintColor="rgba(244, 67, 54, 0.2)" // 自定义色调
isInteractive={true} // 启用交互反馈
>
<Ionicons name="trash-outline" size={20} color="#F44336" />
</GlassView>
) : (
<View style={[styles.glassButton, styles.fallbackButton]}>
<Ionicons name="trash-outline" size={20} color="#F44336" />
</View>
)}
</TouchableOpacity>
4. 定义样式
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)',
},
});
重要注意事项
- 兼容性处理:必须使用
isLiquidGlassAvailable()检查设备支持情况 - overflow: 'hidden':GlassView 组件需要设置此属性以保证圆角效果
- 降级样式:为不支持 Liquid Glass 的设备提供视觉上相似的替代方案
- 交互反馈:设置
isInteractive={true}启用原生的触觉反馈 - 色调自定义:通过
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
import { useAuthGuard } from '@/hooks/useAuthGuard';
2. 在组件中获取方法
const { pushIfAuthedElseLogin, ensureLoggedIn } = useAuthGuard();
3. 替换导航操作
// ❌ 原来的写法 - 没有登录验证
<TouchableOpacity
onPress={() => router.push('/food/nutrition-analysis-history')}
activeOpacity={0.7}
>
// ✅ 修改后的写法 - 带登录验证
<TouchableOpacity
onPress={() => pushIfAuthedElseLogin('/food/nutrition-analysis-history')}
activeOpacity={0.7}
>
4. 服务端接口调用的登录验证
对于需要调用服务端接口的功能,使用 ensureLoggedIn 进行登录验证:
// ❌ 原来的写法 - 没有登录验证
<TouchableOpacity
onPress={() => startNewAnalysis(imageUri)}
activeOpacity={0.8}
>
// ✅ 修改后的写法 - 带登录验证
<TouchableOpacity
onPress={async () => {
// 先验证登录状态
const isLoggedIn = await ensureLoggedIn();
if (isLoggedIn) {
startNewAnalysis(imageUri);
}
}}
activeOpacity={0.8}
>
5. 完整示例(包含 Liquid Glass 兼容性处理)
{isLiquidGlassAvailable() ? (
<TouchableOpacity
onPress={() => pushIfAuthedElseLogin('/food/nutrition-analysis-history')}
activeOpacity={0.7}
>
<GlassView
style={styles.historyButton}
glassEffectStyle="clear"
tintColor="rgba(255, 255, 255, 0.2)"
isInteractive={true}
>
<Ionicons name="time-outline" size={24} color="#333" />
</GlassView>
</TouchableOpacity>
) : (
<TouchableOpacity
onPress={() => pushIfAuthedElseLogin('/food/nutrition-analysis-history')}
style={[styles.historyButton, styles.fallbackBackground]}
activeOpacity={0.7}
>
<Ionicons name="time-outline" size={24} color="#333" />
</TouchableOpacity>
)}
重要注意事项
- 统一体验:使用
pushIfAuthedElseLogin可以确保登录后自动跳转到目标页面 - 参数传递:该方法支持传递路由参数,格式为
pushIfAuthedElseLogin('/path', { param: value }) - 登录重定向:登录页面会接收
redirectTo和redirectParams参数用于登录后跳转 - 兼容性:与 Liquid Glass 设计风格完全兼容,可以同时使用
- 服务端接口调用:所有调用服务端接口的功能必须使用
ensureLoggedIn进行登录验证 - 异步处理:
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 文件中添加新的路由常量:
export const ROUTES = {
// 现有路由...
// 新增路由
FOOD_CAMERA: '/food/camera',
} as const;
2. 在组件中使用路由常量
导入并使用路由常量,而不是硬编码路径:
import { ROUTES } from '@/constants/Routes';
// ❌ 错误写法 - 硬编码路径
router.push('/food/camera?mealType=dinner');
// ✅ 正确写法 - 使用路由常量
router.push(`${ROUTES.FOOD_CAMERA}?mealType=dinner`);
3. 结合登录验证使用
对于需要登录验证的路由,结合 pushIfAuthedElseLogin 使用:
import { ROUTES } from '@/constants/Routes';
import { useAuthGuard } from '@/hooks/useAuthGuard';
const { pushIfAuthedElseLogin } = useAuthGuard();
// 在需要登录验证的路由中使用
<TouchableOpacity
onPress={() => pushIfAuthedElseLogin(`${ROUTES.FOOD_CAMERA}?mealType=${currentMealType}`)}
activeOpacity={0.7}
>
重要注意事项
- 统一管理:所有路由路径都必须在
constants/Routes.ts中定义 - 命名规范:使用大写字母和下划线,如
FOOD_CAMERA - 路径一致性:常量名应该清晰表达路由的用途
- 参数处理:查询参数和路径参数在使用时动态拼接
- 类型安全:使用
as const确保类型推导
路由分类
按照功能模块对路由进行分组:
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- 食物拍照页面实现