From b75a8991ac69279552c520907035f82bb20bcb15 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Thu, 16 Oct 2025 17:45:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E6=B7=BB=E5=8A=A0=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=AA=8C=E8=AF=81=E5=88=B0=E9=A3=9F=E7=89=A9=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在食物拍照、语音记录和营养成分分析功能中添加登录验证 - 使用 ensureLoggedIn 方法确保用户已登录后再调用服务端接口 - 使用 pushIfAuthedElseLogin 方法处理需要登录的页面导航 - 添加新的营养图标资源 - 在路由常量中添加 FOOD_CAMERA 路由定义 - 更新 Memory Bank 任务文档,记录登录验证和路由常量管理的实现模式 --- .kilocode/rules/memory-bank/tasks.md | 200 +++++++++++++++++++++++++- app/food/camera.tsx | 15 +- app/food/nutrition-label-analysis.tsx | 14 +- app/voice-record.tsx | 14 ++ assets/images/icons/icon-yingyang.png | Bin 0 -> 28183 bytes components/NutritionRadarCard.tsx | 8 +- constants/Routes.ts | 1 + 7 files changed, 241 insertions(+), 11 deletions(-) create mode 100644 assets/images/icons/icon-yingyang.png diff --git a/.kilocode/rules/memory-bank/tasks.md b/.kilocode/rules/memory-bank/tasks.md index be36518..b523f69 100644 --- a/.kilocode/rules/memory-bank/tasks.md +++ b/.kilocode/rules/memory-bank/tasks.md @@ -145,4 +145,202 @@ const styles = StyleSheet.create({ ### 参考实现 - `app/food/nutrition-analysis-history.tsx` - 删除按钮实现 - `components/glass/button.tsx` - 通用 Glass 按钮组件 -- `app/(tabs)/_layout.tsx` - 标签栏按钮实现 \ No newline at end of file +- `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` - 食物拍照页面实现 \ No newline at end of file diff --git a/app/food/camera.tsx b/app/food/camera.tsx index 68d769a..81d4b3b 100644 --- a/app/food/camera.tsx +++ b/app/food/camera.tsx @@ -1,5 +1,6 @@ import { HeaderBar } from '@/components/ui/HeaderBar'; import { Colors } from '@/constants/Colors'; +import { useAuthGuard } from '@/hooks/useAuthGuard'; import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding'; import { Ionicons } from '@expo/vector-icons'; import { CameraType, CameraView, useCameraPermissions } from 'expo-camera'; @@ -24,6 +25,7 @@ export default function FoodCameraScreen() { const router = useRouter(); const params = useLocalSearchParams<{ mealType?: string }>(); const cameraRef = useRef(null); + const { ensureLoggedIn } = useAuthGuard(); const [currentMealType, setCurrentMealType] = useState( (params.mealType as MealType) || 'dinner' @@ -103,9 +105,12 @@ export default function FoodCameraScreen() { }); if (photo) { - // 跳转到食物识别页面 + // 先验证登录状态,再跳转到食物识别页面 console.log('照片拍摄成功:', photo.uri); - router.replace(`/food/food-recognition?imageUri=${encodeURIComponent(photo.uri)}&mealType=${currentMealType}`); + const isLoggedIn = await ensureLoggedIn(); + if (isLoggedIn) { + router.replace(`/food/food-recognition?imageUri=${encodeURIComponent(photo.uri)}&mealType=${currentMealType}`); + } } } catch (error) { console.error('拍照失败:', error); @@ -127,7 +132,11 @@ export default function FoodCameraScreen() { if (!result.canceled && result.assets[0]) { const imageUri = result.assets[0].uri; console.log('从相册选择的照片:', imageUri); - router.push(`/food/food-recognition?imageUri=${encodeURIComponent(imageUri)}&mealType=${currentMealType}`); + // 先验证登录状态,再跳转到食物识别页面 + const isLoggedIn = await ensureLoggedIn(); + if (isLoggedIn) { + router.push(`/food/food-recognition?imageUri=${encodeURIComponent(imageUri)}&mealType=${currentMealType}`); + } } } catch (error) { console.error('选择照片失败:', error); diff --git a/app/food/nutrition-label-analysis.tsx b/app/food/nutrition-label-analysis.tsx index adcfa77..d833adc 100644 --- a/app/food/nutrition-label-analysis.tsx +++ b/app/food/nutrition-label-analysis.tsx @@ -1,5 +1,6 @@ import { HeaderBar } from '@/components/ui/HeaderBar'; import { Colors } from '@/constants/Colors'; +import { useAuthGuard } from '@/hooks/useAuthGuard'; import { useCosUpload } from '@/hooks/useCosUpload'; import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding'; import { @@ -30,6 +31,7 @@ import ImageViewing from 'react-native-image-viewing'; export default function NutritionLabelAnalysisScreen() { const safeAreaTop = useSafeAreaTop(); const router = useRouter(); + const { pushIfAuthedElseLogin, ensureLoggedIn } = useAuthGuard(); const { upload, uploading: uploadingToCos, progress: uploadProgress } = useCosUpload({ prefix: 'nutrition-labels' }); @@ -186,7 +188,7 @@ export default function NutritionLabelAnalysisScreen() { right={ isLiquidGlassAvailable() ? ( router.push('/food/nutrition-analysis-history')} + onPress={() => pushIfAuthedElseLogin('/food/nutrition-analysis-history')} activeOpacity={0.7} > ) : ( router.push('/food/nutrition-analysis-history')} + onPress={() => pushIfAuthedElseLogin('/food/nutrition-analysis-history')} style={[styles.historyButton, styles.fallbackBackground]} activeOpacity={0.7} > @@ -241,7 +243,13 @@ export default function NutritionLabelAnalysisScreen() { {!isAnalyzing && !isUploading && !newAnalysisResult && ( startNewAnalysis(imageUri)} + onPress={async () => { + // 先验证登录状态 + const isLoggedIn = await ensureLoggedIn(); + if (isLoggedIn) { + startNewAnalysis(imageUri); + } + }} activeOpacity={0.8} > diff --git a/app/voice-record.tsx b/app/voice-record.tsx index 9c8a790..87935bd 100644 --- a/app/voice-record.tsx +++ b/app/voice-record.tsx @@ -1,6 +1,7 @@ import { HeaderBar } from '@/components/ui/HeaderBar'; import { Colors } from '@/constants/Colors'; import { useAppDispatch } from '@/hooks/redux'; +import { useAuthGuard } from '@/hooks/useAuthGuard'; import { useColorScheme } from '@/hooks/useColorScheme'; import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding'; import { analyzeFoodFromText } from '@/services/foodRecognition'; @@ -28,6 +29,7 @@ export default function VoiceRecordScreen() { const colorTokens = Colors[theme]; const { mealType = 'dinner' } = useLocalSearchParams<{ mealType?: string }>(); const dispatch = useAppDispatch(); + const { ensureLoggedIn } = useAuthGuard(); // 状态管理 const [recordState, setRecordState] = useState('idle'); @@ -222,6 +224,12 @@ export default function VoiceRecordScreen() { // 开始录音 const startRecording = async () => { + // 先验证登录状态 + const isLoggedIn = await ensureLoggedIn(); + if (!isLoggedIn) { + return; + } + try { // 重置状态 setRecognizedText(''); @@ -292,6 +300,12 @@ export default function VoiceRecordScreen() { return; } + // 先验证登录状态 + const isLoggedIn = await ensureLoggedIn(); + if (!isLoggedIn) { + return; + } + try { triggerHapticFeedback('impactMedium'); setRecordState('analyzing'); diff --git a/assets/images/icons/icon-yingyang.png b/assets/images/icons/icon-yingyang.png new file mode 100644 index 0000000000000000000000000000000000000000..e2fdb7533fdf0d8bb87181f913664ac5a8a02964 GIT binary patch literal 28183 zcmb@uWmuHm7d|>eDvd~|bP5Af(jrnrH%NDbbPgfig3_T#D9b29V-w8ExO3{UyH~W3M38$?Vml^Ce5FM5Yz1c@#6_DjL~qX~#bexfb$%ZaSi@w$7SY&Lmc;UCa@$X1o^m=SZp~#7((fZ1pW5)I&MQPJE>0M0jT_!#Jztl z6M}{Z$@v=VY+Lomg%VseEtcOIq+O81xD8Hq-d5L9xaPd7gw7{^*4DO1F9>6K_vPSG zxr5;3)^IKDtFqEbENM)op1Qfwf^xmL(24^*CAp76Af+;;V%*%1RFS~197Wosz$lKQ zwDMOAAoij(O&tYl5b#ls15B8vsigDF83cTkael=Q(NyBliKqjs15cGF|NrrDES!J{ z!w(_*UE%TtUM&$5gQNQr_DEUM`P^vnD+aSzIp_pBu^ZlpXr>>@x5l(9hu2DF8fB$e z(s{lpEAb=4f!rv_W67-(K@XEQKN*l9}a(K<^9fjKRsi0z?`<`Qp zFSNgE{N$r%p&o>%S^NZPdE8bKiW|}cHu(T*CerdX4EFgq@vL79syHvxan#yeiBfqJk@XQ1 z?YjNENx(Xmdc^pkHIx3GSQK3?q*qR+SK}{#vpB0B#XU8KMNqigDq;D{;LL=oG)|Ry zVUR2?a=4kL&EvDUn}01TMtd#?>N0!;sTBUF_U&bGUB8~v$Kriz5OV+(cE>i1Ptiei zQQr{MpnlIZgb5_KV^dw1VwTH~bG~UkKYZroAQ4#1 z_(lqg{YCuxNCFa5Lny<2n&{`$$?w3xqA>4!gy;pQ*j6o)}&hCR(RJEU=>FQHzP=ywR8 zx%R)qCa7j$u6H~N2ZyNT+pXUti}x#>1LGpk$88mOO`E_Qn5$QPJ;C|W5!>z5h3U3YTg zrx6+1CLL~Np{@HU?>a+i13Y>HpT%aCB{3{~*iGGg39g6I0@b=Oam zf(H{X>*DrDddRn|1!G*hb#%qxeK*wmxu;*Z?~$v=(-d^!#ykEWUbr4MZx7GKMG1CL zM-hf2nz;^B8U%E>W}lLS{sR)6H@EX=hHg1h{l|aFQwZ^#6Dq!uB%%e2E~fvtAwe5d z+v%6-(VrbxxTBdAHxvrF2T%v8zIc(lFaOD*p$OacuCOq*$rAg)_F&8CYREPRlzH2P)g;8 z#uUEd-(&0m_9aeMj8#DHM@wFBaM2Cd=E~pn3)Y;A#?+UAcx4iS8@-IM#mYjwzj0`-%>G<+=klHN5l^X_or;_s>---AJJ{hB=yv{ z>VTUw4DXK@uC!bfWU_ECF^xx#340N({>fLpoXj5QrfIq7mhbINbS@&lpDln&%_z^9 ze;G7bmX=Td_CE51KMQjk!QIZ=XddlEW6Ha#{6!-C_@5=-Xx2_C4k(4N~2!}A3j&xsG`_5NYZ^f9UAEeXQj_Z<{oq z!Hu9}(ktwhdXh!*okkPZ1CEoDo>ZJLnNC&tgSwg^ z5`|kfErLgN-A*jYb(C`kQI)8-6HDgBNDU7DyJFcUh1S22SQuHyypW*-kFg{$d1Lg% z8CpfVqu%0tyd}kqHAh*C#`+cM;@iJsDN%{YpH`oGeYnr*# z3(+~2dGghd>5xm!`LHT0;_J%W4!6(SzNm0AlmeyN3ASClpv5l}+|^8T*NB92@ZA02 zl}W);qk4CCL{BhNQjpsm6JEFO!3r#+?mC^<87+_@nT@mj zU@v_aMb!J$bKdDv1(cg9<(=4&-3EWbsxQV^zN|@#PV=65{nAofSoWWc-5Bc7gS?&1*Ttjtw-RD z*7v-9Umz75x~8#O2v|^1g3kiX^py;bX7fyPN}V2U8>&3^*&|$9Eh%CyQuq(5NKEzBi0+XglwG8(cAdCInV$$FW&k#Hvu zT$l*t48tw@VG)_dvm>7Kaih7-6-;?&aRCscaqt^T63hLBcKL^OCe}

K^-6oX2|D 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 AmjD0& literal 0 HcmV?d00001 diff --git a/components/NutritionRadarCard.tsx b/components/NutritionRadarCard.tsx index 932ec0d..f7063ef 100644 --- a/components/NutritionRadarCard.tsx +++ b/components/NutritionRadarCard.tsx @@ -250,7 +250,7 @@ export function NutritionRadarCard({ style={styles.foodOptionItem} onPress={() => { triggerLightHaptic(); - pushIfAuthedElseLogin(`/food/camera?mealType=${currentMealType}`); + router.push(`${ROUTES.FOOD_CAMERA}?mealType=${currentMealType}`); }} activeOpacity={0.7} > @@ -284,7 +284,7 @@ export function NutritionRadarCard({ style={styles.foodOptionItem} onPress={() => { triggerLightHaptic(); - pushIfAuthedElseLogin(`${ROUTES.VOICE_RECORD}?mealType=${currentMealType}`); + router.push(`${ROUTES.VOICE_RECORD}?mealType=${currentMealType}`); }} activeOpacity={0.7} > @@ -301,13 +301,13 @@ export function NutritionRadarCard({ style={styles.foodOptionItem} onPress={() => { triggerLightHaptic(); - pushIfAuthedElseLogin(`${ROUTES.NUTRITION_LABEL_ANALYSIS}?mealType=${currentMealType}`); + router.push(`${ROUTES.NUTRITION_LABEL_ANALYSIS}?mealType=${currentMealType}`); }} activeOpacity={0.7} > diff --git a/constants/Routes.ts b/constants/Routes.ts index 37b686a..837b76f 100644 --- a/constants/Routes.ts +++ b/constants/Routes.ts @@ -41,6 +41,7 @@ export const ROUTES = { FOOD_LIBRARY: '/food-library', VOICE_RECORD: '/voice-record', NUTRITION_LABEL_ANALYSIS: '/food/nutrition-label-analysis', + FOOD_CAMERA: '/food/camera', // 体重记录相关路由 WEIGHT_RECORDS: '/weight-records',