feat: 实现减脂体重管理App完整功能

- 实现拍照识别食物功能(集成大语言模型视觉能力)
- 实现智能对话功能(集成大语言模型流式输出)
- 实现食物记录和卡路里管理功能
- 实现体重记录和统计功能
- 实现健康数据管理页面
- 配置数据库表结构(用户、食物记录、体重记录)
- 实现Express后端API路由
- 配置Tab导航和前端页面
- 采用健康运动配色方案
This commit is contained in:
jaystar
2026-02-02 15:17:50 +08:00
commit 28c4d7b3b4
82 changed files with 21891 additions and 0 deletions

View File

@@ -0,0 +1,152 @@
/**
* 安全路由 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;
}