Files
height_manager/client/hooks/useSafeRouter.ts
jaystar 28c4d7b3b4 feat: 实现减脂体重管理App完整功能
- 实现拍照识别食物功能(集成大语言模型视觉能力)
- 实现智能对话功能(集成大语言模型流式输出)
- 实现食物记录和卡路里管理功能
- 实现体重记录和统计功能
- 实现健康数据管理页面
- 配置数据库表结构(用户、食物记录、体重记录)
- 实现Express后端API路由
- 配置Tab导航和前端页面
- 采用健康运动配色方案
2026-02-02 15:17:50 +08:00

153 lines
5.2 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.
/**
* 安全路由 Hook - 完全代替原生的 useRouter 和 useLocalSearchParams
*
* 提供的 Hook
* - useSafeRouter: 代替 useRouter包含所有路由方法并对 push/replace/navigate/setParams 进行安全编码
* - useSafeSearchParams: 代替 useLocalSearchParams获取路由参数
*
* 解决的问题:
* 1. URI 编解码不对称 - useLocalSearchParams 会自动解码,但 router.push 不会自动编码,
* 当参数包含 % 等特殊字符时会拿到错误的值
* 2. 类型丢失 - URL 参数全是 stringNumber/Boolean 类型会丢失
* 3. 嵌套对象无法传递 - URL search params 不支持嵌套结构
*
* 解决方案:
* 采用 Payload 模式,将所有参数打包成 JSON 并 Base64 编码后传递,
* 接收时再解码还原,确保数据完整性和类型安全。
*
* 优点:
* 1. 自动处理所有特殊字符(如 %、&、=、中文、Emoji 等)
* 2. 保留数据类型Number、Boolean 不会变成 String
* 3. 支持嵌套对象和数组传递
* 4. 三端兼容iOS、Android、Web
*
* 使用方式:
* ```tsx
* // 发送端 - 使用 useSafeRouter 代替 useRouter
* const router = useSafeRouter();
* router.push('/detail', { id: 123, uri: 'file:///path/%40test.mp3' });
* router.replace('/home', { tab: 'settings' });
* router.navigate('/profile', { userId: 456 });
* router.back();
* if (router.canGoBack()) { ... }
* router.setParams({ updated: true });
*
* // 接收端 - 使用 useSafeSearchParams 代替 useLocalSearchParams
* const { id, uri } = useSafeSearchParams<{ id: number; uri: string }>();
* ```
*/
import { useMemo } from 'react';
import { useRouter as useExpoRouter, useLocalSearchParams as useExpoParams } from 'expo-router';
import { Base64 } from 'js-base64';
const PAYLOAD_KEY = '__safeRouterPayload__';
const LOG_PREFIX = '[SafeRouter]';
const getCurrentParams = (rawParams: Record<string, string | string[]>): Record<string, unknown> => {
const payload = rawParams[PAYLOAD_KEY];
if (payload && typeof payload === 'string') {
const decoded = deserializeParams<Record<string, unknown>>(payload);
if (decoded && Object.keys(decoded).length > 0) {
return decoded;
}
}
const { [PAYLOAD_KEY]: _, ...rest } = rawParams;
return rest as Record<string, unknown>;
};
const serializeParams = (params: Record<string, unknown>): string => {
try {
const jsonStr = JSON.stringify(params);
return Base64.encode(jsonStr);
} catch (error) {
console.error(LOG_PREFIX, 'serialize failed:', error instanceof Error ? error.message : 'Unknown error');
return '';
}
};
const deserializeParams = <T = Record<string, unknown>>(
payload: string | string[] | undefined
): T | null => {
if (!payload || typeof payload !== 'string') {
return null;
}
try {
const jsonStr = Base64.decode(payload);
return JSON.parse(jsonStr) as T;
} catch (error) {
console.error(LOG_PREFIX, 'deserialize failed:', error instanceof Error ? error.message : 'Unknown error');
return null;
}
};
/**
* 安全路由 Hook用于页面跳转代替 useRouter
* @returns 路由方法(继承 useRouter 所有方法,并对以下方法进行安全编码)
* - push(pathname, params) - 入栈新页面
* - replace(pathname, params) - 替换当前页面
* - navigate(pathname, params) - 智能跳转(已存在则返回,否则 push
* - setParams(params) - 更新当前页面参数(合并现有参数)
*/
export function useSafeRouter() {
const router = useExpoRouter();
const rawParams = useExpoParams<Record<string, string | string[]>>();
const push = (pathname: string, params: Record<string, unknown> = {}) => {
const encodedPayload = serializeParams(params);
router.push({
pathname: pathname as `/${string}`,
params: { [PAYLOAD_KEY]: encodedPayload },
});
};
const replace = (pathname: string, params: Record<string, unknown> = {}) => {
const encodedPayload = serializeParams(params);
router.replace({
pathname: pathname as `/${string}`,
params: { [PAYLOAD_KEY]: encodedPayload },
});
};
const navigate = (pathname: string, params: Record<string, unknown> = {}) => {
const encodedPayload = serializeParams(params);
router.navigate({
pathname: pathname as `/${string}`,
params: { [PAYLOAD_KEY]: encodedPayload },
});
};
const setParams = (params: Record<string, unknown>) => {
const currentParams = getCurrentParams(rawParams);
const mergedParams = { ...currentParams, ...params };
const encodedPayload = serializeParams(mergedParams);
router.setParams({ [PAYLOAD_KEY]: encodedPayload });
};
return {
...router,
push,
replace,
navigate,
setParams,
};
}
/**
* 安全获取路由参数 Hook用于接收方代替 useLocalSearchParams
* 兼容两种跳转方式:
* 1. useSafeRouter 跳转 - 自动解码 Payload
* 2. 外部跳转(深链接、浏览器直接访问等)- 回退到原始参数
* @returns 解码后的参数对象,类型安全
*/
export function useSafeSearchParams<T = Record<string, unknown>>(): T {
const rawParams = useExpoParams<Record<string, string | string[]>>();
const decodedParams = useMemo(() => {
return getCurrentParams(rawParams) as T;
}, [rawParams]);
return decodedParams;
}