Files
digital-pilates/hooks/useAuthGuard.ts
richarjiang fbe0c92f0f feat(i18n): 全面实现应用核心功能模块的国际化支持
- 新增 i18n 翻译资源,覆盖睡眠、饮水、体重、锻炼、用药 AI 识别、步数、健身圆环、基础代谢及设置等核心模块
- 重构相关页面及组件(如 SleepDetail, WaterDetail, WorkoutHistory 等)使用 `useI18n` 钩子替换硬编码文本
- 升级 `utils/date` 工具库与 `DateSelector` 组件,支持基于语言环境的日期格式化与显示
- 完善登录页、注销流程及权限申请弹窗的双语提示信息
- 优化部分页面的 UI 细节与字体样式以适配多语言显示
2025-11-27 17:54:36 +08:00

159 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import AsyncStorage from '@/utils/kvStore';
import { usePathname, useRouter } from 'expo-router';
import { useCallback } from 'react';
import { Alert } from 'react-native';
import { ROUTES } from '@/constants/Routes';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useI18n } from '@/hooks/useI18n';
import { STORAGE_KEYS, api } from '@/services/api';
import { logout as logoutAction } from '@/store/userSlice';
type RedirectParams = Record<string, string | number | boolean | undefined>;
type EnsureOptions = {
redirectTo?: string;
redirectParams?: RedirectParams;
shouldBack?: boolean; // 登录成功后是否返回上一页而不是重定向
};
export function useAuthGuard() {
const router = useRouter();
const dispatch = useAppDispatch();
const currentPath = usePathname();
const user = useAppSelector(state => state.user);
const { t } = useI18n();
// 判断登录状态:优先使用 token因为 token 是登录的根本凭证
// profile.id 可能在初始化时还未加载,但 token 已经从 AsyncStorage 恢复
const isLoggedIn = !!user?.token;
const ensureLoggedIn = useCallback(async (options?: EnsureOptions): Promise<boolean> => {
if (isLoggedIn) return true;
const redirectTo = options?.redirectTo ?? currentPath ?? ROUTES.TAB_STATISTICS;
const paramsJson = options?.redirectParams ? JSON.stringify(options.redirectParams) : undefined;
const shouldBack = options?.shouldBack;
router.push({
pathname: '/auth/login',
params: {
redirectTo,
...(paramsJson ? { redirectParams: paramsJson } : {}),
...(shouldBack ? { shouldBack: 'true' } : {}),
},
} as any);
return false;
}, [isLoggedIn, router, currentPath]);
const pushIfAuthedElseLogin = useCallback((pathname: string, params?: RedirectParams) => {
if (isLoggedIn) {
router.push({ pathname, params } as any);
return;
}
const paramsJson = params ? JSON.stringify(params) : undefined;
router.push({ pathname: '/auth/login', params: { redirectTo: pathname, ...(paramsJson ? { redirectParams: paramsJson } : {}) } } as any);
}, [isLoggedIn, router]);
const guardHandler = useCallback(
<T extends any[]>(fn: (...args: T) => any | Promise<any>, options?: EnsureOptions) => {
return async (...args: T) => {
const ok = await ensureLoggedIn(options);
if (!ok) return;
return fn(...args);
};
},
[ensureLoggedIn]
);
// 退出登录功能
const handleLogout = useCallback(async () => {
try {
// 调用 Redux action 清除本地状态和缓存
await dispatch(logoutAction()).unwrap();
// 跳转到登录页面
router.push('/auth/login');
} catch (error) {
console.error('退出登录失败:', error);
Alert.alert(t('authGuard.logout.error'), t('authGuard.logout.errorMessage'));
}
}, [dispatch, router, t]);
// 带确认对话框的退出登录
const confirmLogout = useCallback(() => {
Alert.alert(
t('authGuard.confirmLogout.title'),
t('authGuard.confirmLogout.message'),
[
{
text: t('authGuard.confirmLogout.cancelButton'),
style: 'cancel',
},
{
text: t('authGuard.confirmLogout.confirmButton'),
style: 'default',
onPress: handleLogout,
},
]
);
}, [handleLogout, t]);
// 注销账号功能
const handleDeleteAccount = useCallback(async () => {
try {
// 调用注销账号API
await api.delete('/api/users/delete-account');
// 清除额外的本地数据
await AsyncStorage.multiRemove(['@user_personal_info', STORAGE_KEYS.onboardingCompleted]);
// 执行退出登录逻辑
await dispatch(logoutAction()).unwrap();
Alert.alert(t('authGuard.deleteAccount.successTitle'), t('authGuard.deleteAccount.successMessage'), [
{
text: t('authGuard.deleteAccount.confirmButton'),
onPress: () => router.push('/auth/login'),
},
]);
} catch (error: any) {
console.error('注销账号失败:', error);
const message = error?.message || t('authGuard.deleteAccount.errorMessage');
Alert.alert(t('authGuard.deleteAccount.errorTitle'), message);
}
}, [dispatch, router, t]);
// 带确认对话框的注销账号
const confirmDeleteAccount = useCallback(() => {
Alert.alert(
t('authGuard.confirmDeleteAccount.title'),
t('authGuard.confirmDeleteAccount.message'),
[
{
text: t('authGuard.confirmDeleteAccount.cancelButton'),
style: 'cancel',
},
{
text: t('authGuard.confirmDeleteAccount.confirmButton'),
style: 'destructive',
onPress: handleDeleteAccount,
},
],
{ cancelable: true }
);
}, [handleDeleteAccount, t]);
return {
isLoggedIn,
ensureLoggedIn,
pushIfAuthedElseLogin,
guardHandler,
handleLogout,
confirmLogout,
handleDeleteAccount,
confirmDeleteAccount,
} as const;
}