# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. > 个人开发项目。优先帮我把活干完、踩过的坑别再踩,**不要**纠结协作规范、提交模板、验证清单、影响面汇报这些东西。 ## 项目概览 - `Loan Assistant` / `借贷助手` 客户端 - 技术栈:Expo 55、React Native 0.83、React 19、TypeScript(strict)、`expo-router`(typedRoutes + reactCompiler)、NativeWind 4、`@ant-design/react-native` 5 - 目标平台:iOS、Android、Web(部署在 `/h5` 子路径下) - 包管理器:**pnpm**(仓库以 `pnpm-lock.yaml` 为准) ## 命令 - 使用 pnpm - `package.json` 已经列出常规命令。 - 验证以 `pnpm lint` + 真机/模拟器为准。 ## 大图架构 ### 启动流程([src/app/_layout.tsx](src/app/_layout.tsx)) `RootLayout` 渲染 `` 之前按顺序: 1. 加载 antd 图标字体(`useFonts({ antoutline })`) 2. `POST common/check_version` 检查原生版本,必要时弹 Modal 引导跳商店/下载链接(`type: 'apk' | 'url'`,`force` 控制是否可取消)。最近一次检查时间存在 MMKV 的 `last_update_app`,15 分钟内不再重复 3. `expo-updates` 拉热更新(`__DEV__` 下完全跳过) 4. Provider 嵌套顺序固定:`ThemeProvider`(react-navigation) → antd `Provider`(theme=`antdTheme`) → `AuthProvider` → `Stack` 新增全局 Provider 一律加在这里,不要散落到子页面。`Stack.screenOptions` 已设置了 iOS 风格透明模糊 header,新页面默认继承,特殊页(如 `sign-in`)通过 `Stack.Screen` 单独覆盖。 ### 路由([src/app/](src/app/)) - `expo-router` + `experiments.typedRoutes`,新增页面文件即新增路由,不需要中央路由表 - 底部 Tab 在 [src/app/(tabs)/](src/app/(tabs)/)(`index/customer/analytics/reports/profile`),改 Tab 结构同时改 `(tabs)/_layout.tsx` - 业务子流程目录:`credit/`、`customer/`;登录注册:`sign-in.tsx`、`sign-up.tsx`;Web 专属入口 `web.tsx` 和 `+html.tsx` - `app.json -> experiments.baseUrl: "/h5"`,Web 部署到 `/h5`,本地 dev server 也带这个前缀 ### 网络层([src/utils/api.ts](src/utils/api.ts)) 唯一的 axios client: - `baseURL` 来自 [src/config.json](src/config.json)(`api.url`),不要绕开它硬编码 - 自动注入 header `x-app-name`、`x-app-version`(拼了 `app.json` version + 平台 versionCode/buildNumber + `config.json` 的 `jsVersion`)、`x-app-platform` - access token 是模块级单例(`setAccessToken` / `getAccessToken`),由 `AuthProvider` 在登录/启动时注入;过期判断带 30 秒余量,过期会自动清空 - 响应约定:HTTP 200 + `data.code === "1"` 才算成功,其它一律抛 `ApiError`(`HttpError` 是父类,提供 `is4xx/is5xx` 等静态判断) - `validateStatus` 放行 200–499,5xx 才走 axios reject 路径 - 文件上传走 `uploadFile`(用 `expo/fetch`,避开 RN 的 multipart 兼容问题) - `__DEV__` 下挂了请求/响应拦截器打 console;生产构建由 `metro.config.js` 的 `drop_console: true` 整体剔除 ### 鉴权([src/utils/auth.tsx](src/utils/auth.tsx)) - `AuthProvider` 启动时从 MMKV `access_token` 读 token,调 `setAccessToken` 注入 api client - `signIn` / `smsSignIn` / `signUp` / `signOut` 调完接口后由调用方 `setToken(...)` 写回,Provider 同步落 MMKV 并切 `authStatus` - token 落盘用 `getGlobalStorage().setObject(key, value, ttl)`,TTL 用接口返回的 `expires_in`(秒) ### 持久化([src/utils/storage.ts](src/utils/storage.ts)) 三个独立 MMKV 实例,路径都落在 `expo-file-system` 的 `cacheDirectory`: - `getGlobalStorage()` — 长期 KV(token、上次版本检查时间) - `getCaches()` — 业务通用缓存 - `getApiCache()` — 接口结果缓存 扩展方法 `setObject(key, value, ttlSeconds)` / `getObject(key)` 把 TTL 编进 JSON;后台每 15 分钟扫一遍清理过期项。Web 端有 [storage.web.ts](src/utils/storage.web.ts) 同名导出,靠 Metro 平台后缀分流。 ### 主题与样式 - Tailwind 配置在 [tailwind.config.js](tailwind.config.js),扩展 token 来自仓库根的 [theme.js](theme.js)(注意是 `.js`,不在 `src/`) - antd 主题在 [src/constants/antd-theme.ts](src/constants/antd-theme.ts),react-navigation 的 `DefaultTheme` 在 `_layout.tsx` 基于它构造,三套主题共用同一组色板 - [babel.config.js](babel.config.js) 启用 `babel-plugin-import` 给 antd-rn 做按需引入;`jsxImportSource: "nativewind"` 让 `className` 直接生效 - 全局 CSS:[src/global.css](src/global.css)(NativeWind 入口,由 metro 注入)+ [src/global.web.css](src/global.web.css)(仅 Web) ### 平台分流 文件级用平台后缀:`*.web.tsx` 与 `*.tsx` 共存即可(如 `animated-icon.tsx` / `animated-icon.web.tsx`、`storage.ts` / `storage.web.ts`)。避免在单文件里堆过多 `Platform.OS` 分支。 ## 目录约定 - `src/app/` — `expo-router` 路由 / layout / 页面级 screen - `src/components/` — 跨页面复用组件;`src/components/ui/` 放更基础的 UI 积木 - `src/hooks/` — 自定义 hooks - `src/utils/` — `api`、`auth`、`storage`、`cache` 等基础能力 - `src/constants/` — 主题与常量 - `assets/` — 运行时资源 - `docs/antd/` — 本地 antd-rn 参考文档(`llms-semantic.md` / `llms-full.txt` / `llms.txt`),新增/调整 antd 组件优先查阅 - `design/` — 设计稿,**不**作为运行时代码来源 - `android/`、`ios/` — 原生工程,JS 改动默认不动 - `dist/` — 构建产物,不要手工编辑 路径别名([tsconfig.json](tsconfig.json)):`@/*` → `src/*`,`@/assets/*` → `assets/*`。 ## 踩坑提醒 - React 19 + `experiments.reactCompiler: true` 已开启,**别**为了"优化"到处补 `useMemo` / `useCallback`,编译器自己会处理 - TypeScript strict,新代码别用 `any` 糊过去 - 复用现有基础组件 / Tailwind token,别在页面里重复造按钮/输入框/弹窗/状态标签或散落魔法颜色值 - `airpush.sh ` 发热更新时,`runtimeVersion` 必须等于已安装客户端的 `app.json -> expo.version`(policy 是 `appVersion`),否则设备拉不到 - `pnpm reset-project` 会把 `app/` 移走,正常开发**绝对**别跑 - 不要引入重量级新依赖,除非现有方案明显不够用 - `app.json` / `babel.config.js` / `metro.config.js` / `tailwind.config.js` 改前先想清楚影响面 ## 仓库与外部上下文 - 上一级目录 `Loan/` 下还有 `server/`(后端)、`expo-updates-server/`(热更新服务)、`ant-design-mobile-rn/`(被 fork 的 antd-rn)等同胞仓库;改接口/热更新协议可能需要联动它们,但默认不跨仓改 - 接口 base URL `https://loan.ewaga.com/api/v1/`,热更新服务 `https://updates-loan.ewaga.com/` - iOS bundleId / Android package:`com.cdloan.assistant`,应用显示名"借贷助手" / "Loan Assistant" - 代码签名证书在 [code-signing/](code-signing/),`expo-updates` 用它校验热更新包,**别**提交私钥