# 正念 APP(客户端) 本目录用于存放「正念 APP」客户端工程,定位是面向宝妈人群的情绪价值与正念练习支持应用。 ## 功能概览(一期) - **APP-PUSH 推送**:定时推送情绪文字(固定时间 / 用户自定义时间),推送内容可由后端配置更新 - **iOS 小组件(Widget)**:展示当前情绪文字与背景图/渐变色,支持多尺寸与可配置刷新频率 - **情绪卡片滑动**:卡片列表左右滑动切换(类似 Tinder),收藏/分享可作为后续迭代 ## 技术栈 - **React Native + Expo** - **TypeScript** - **React Navigation**:页面路由 - **状态管理**:Zustand(或 Redux Toolkit) - **网络请求**:Axios(或 Fetch) - **推送**:Expo Notifications - **环境区分**:Expo App Config + `.env.dev` / `.env.prod` - **iOS 打包**:EAS Build ## 开发环境要求 - **Node.js**:建议使用 **20+(LTS)** - **包管理器**:pnpm - **Expo CLI**:推荐通过 `npx expo` 使用(避免全局版本漂移) - **iOS 真机调试**:macOS + Xcode(如需) > 本项目已内置 `postinstall` 补丁脚本用于兼容(安装依赖后会自动修复),但仍建议升级到 Node 20+。 ## 快速开始 > 说明:本仓库已包含 Expo 工程文件;如果你只是想把客户端跑起来,从「安装依赖」开始即可。 ### 1)初始化工程(若尚未创建) 在 `client/` 目录下创建 Expo 工程(示例): ```bash cd client npx create-expo-app@latest . ``` 执行后按提示选择模板即可;如果你希望固定使用 TypeScript 模板,也可以在创建时选择对应模板(以实际 CLI 提示为准)。 ### 2)安装依赖 ```bash cd client pnpm install ``` ### 3)配置环境变量(dev / prod) 按根目录约定,客户端使用 `.env.dev` 与 `.env.prod` 区分环境;`.env` 文件不提交到 Git,仓库内应提供 `.env.example` 作为模板。 #### 本地开发如何注入环境变量(推荐) Expo 在本地开发时会自动读取当前目录下的 `.env` / `.env.local`(仅以 `EXPO_PUBLIC_` 前缀变量注入到客户端运行时)。推荐做法: ```bash cd client cp .env.example .env.local # 然后按需修改 .env.local pnpm start ``` > 修改 `.env*` 后需要重启 `pnpm start` 才会生效。 #### 本地开发如何区分 dev / prod(可选) 如果你希望坚持使用 `.env.dev` / `.env.prod` 文件名,可以在启动前导出环境变量(zsh 示例): ```bash cd client set -a source .env.dev set +a pnpm start ``` > 注意:`source` 方式要求 `.env.dev` 内容是合法的 shell 格式(例如 `KEY=value`),不要带 `export` 以外的复杂语法;值包含空格时需要加引号。 #### 必填环境变量 - **`EXPO_PUBLIC_API_BASE_URL`**:后端 API 基地址 - 本地:`http://localhost:8000` - 真机联调:改为电脑局域网 IP(例如 `http://192.168.1.10:8000`) - **`EXPO_PUBLIC_ENV`**:环境标识(建议 `dev` / `prod`) #### 可选环境变量 - **`EXPO_PUBLIC_DEFAULT_LANGUAGE`**:默认语言策略 - `auto`:优先设备语言(默认) - `zh-CN/en/es/pt/zh-TW`:固定默认语言(仍允许用户在设置中切换并持久化) 建议字段(示例): ```env EXPO_PUBLIC_API_BASE_URL=http://localhost:8000 EXPO_PUBLIC_ENV=dev EXPO_PUBLIC_DEFAULT_LANGUAGE=auto ``` > 提示:在 Expo 中建议使用 `EXPO_PUBLIC_` 前缀,方便在运行时读取并支持 EAS 构建注入。 ### 4)启动开发服务器 ```bash cd client pnpm start ``` 启动后你会看到终端输出的二维码(QR Code)与可用命令提示。 ### 5)运行到 iOS / Android / 真机 / Web - **iOS 模拟器(macOS + Xcode)**: - 方式一:在 `pnpm start` 的终端里按 `i` - 方式二:直接运行: ```bash cd client pnpm ios ``` - **Android 模拟器(Android Studio)**: - 方式一:在 `pnpm start` 的终端里按 `a` - 方式二:直接运行: ```bash cd client pnpm android ``` - **真机(推荐使用 Expo Go)**: - 手机安装 **Expo Go** - 确保手机与电脑在**同一局域网** - 运行 `pnpm start` 后,用 Expo Go 扫描终端二维码即可打开 App - **Web(可选)**: ```bash cd client pnpm web ``` 常用命令(脚本): ```bash pnpm ios pnpm android pnpm web ``` ### 6)常见问题 - **真机打不开 / 扫码后卡住**: - 检查手机与电脑是否同一 Wi-Fi - 尝试重启 `pnpm start -- --clear` 清缓存 - **启动时提示 `Networking has been disabled` / `Network connection is unreliable`**: - 这是 Expo CLI 的网络探测/请求失败导致的,按下面方式跳过网络请求即可: ```bash cd client EXPO_OFFLINE=1 pnpm start ``` - 如果你本机设置了代理(例如 `HTTP_PROXY/ALL_PROXY`),也可以临时关闭代理后再启动: ```bash cd client unset HTTP_PROXY HTTPS_PROXY ALL_PROXY pnpm start ``` - **Watchman 提示 Recrawl(不影响运行,但建议处理)**: - 按提示执行一次即可清除警告: ```bash watchman watch-del '/Users/jojo/Desktop/lxy/gitea/mindfulness/client' ; watchman watch-project '/Users/jojo/Desktop/lxy/gitea/mindfulness/client' ``` - **联调时请求打到 localhost**: - 真机上 `localhost` 指向手机自身,请把 `EXPO_PUBLIC_API_BASE_URL` 改成电脑的局域网 IP(例如 `http://192.168.1.10:8000`),或使用内网穿透方案 ## 与后端联调 - **后端基座**:FastAPI(详见 `server/README.md`) - **API Base URL**:通过 `EXPO_PUBLIC_API_BASE_URL` 配置 - **本地真机注意**:如果在手机上调试,请将 `localhost` 替换为电脑局域网 IP,或使用内网穿透方案 ## 多语言(i18n):CN / EN / ES / PT / TC ### 语言码约定 - **CN(简体中文)**:`zh-CN` - **EN(英语)**:`en` - **ES(西班牙语)**:`es` - **PT(葡萄牙语)**:`pt` - **TC(繁体中文)**:`zh-TW` ### 推荐选型(Expo/RN 常用组合) - **文案管理**:`i18next` + `react-i18next` - **系统语言读取**:`expo-localization` ### 建议目录结构 ```text src/ └── i18n/ ├── index.ts # i18n 初始化(默认语言、回退语言、资源注册) ├── locales/ │ ├── zh-CN.json │ ├── en.json │ ├── es.json │ ├── pt.json │ └── zh-TW.json └── types.ts #(可选)语言码与 key 的类型定义 ``` ### 约定与最佳实践 - **key 规则**:使用稳定的点分层 key,例如 `common.ok`、`push.permissionTitle`、`cards.swipeHint` - **默认与回退**:默认优先跟随用户当前设备语言(在支持列表内时生效);设备语言不支持时回退到 `zh-CN`(或团队指定默认) - **插值与复数**:优先使用 i18next 的插值(例如 `{{name}}`),避免在代码里拼字符串 - **动态切换**:提供“语言设置”入口允许手动切换;切换后持久化(例如 AsyncStorage),后续启动优先生效,并立即刷新文案 - **语言优先级**:用户设置(若存在)> 设备语言(在支持列表内)> 默认回退(`zh-CN` 或团队指定默认) - **设置入口建议**:设置页 -> 语言(或 设置页 -> 通用 -> 语言) - **与服务端一致性**:若后端也需要多语言(推送文案等),建议统一语言码(`zh-CN/en/es/pt/zh-TW`)并在接口里明确 `lang` ## 推送(Expo Notifications) 客户端侧通常需要: - **申请通知权限** - **获取 Expo Push Token** - **将 Token 上报后端**(用于定时推送任务) 后端侧通常需要: - **保存用户 Push Token** - **按策略触发推送**(固定时间 / 用户自定义时间) - **支持后台配置推送文案并更新** > 具体实现以客户端工程代码为准;当代码接入后,建议在 README 补充「Token 上报接口」与「字段定义」。 ## iOS 小组件(Widget) 小组件通常需要: - **数据源**:来自本地缓存或后端拉取(需要设计刷新策略与缓存) - **展示内容**:当前情绪文字 + 背景图/渐变 - **尺寸**:小 / 中 / 大 - **刷新频率**:如每小时/每天(iOS 对频率有系统限制,以实际效果为准) > 若你计划使用 Expo 的相关能力,请在工程中明确选型与实现路径,并补充到本 README。 ## EAS Build(iOS 打包) 建议按根目录约定区分 dev/prod: - **dev**:`com.damer.mindfulness.dev` - **prod**:`com.damer.mindfulness` 常见流程(示例): ```bash cd client eas login eas build --platform ios --profile development ``` > 实际 profile、证书、bundle id、环境变量注入方式以工程内 `eas.json` / app config 为准。 ## 目录结构(建议) 当客户端工程落地后,建议使用更标准、可扩展的目录结构(Expo + TypeScript 常见组织方式): ```text client/ ├── app/ #(推荐)expo-router 路由目录(若使用 expo-router) │ ├── (tabs)/ # Tabs 分组(可选) │ ├── _layout.tsx # 根布局 │ └── index.tsx # 首页 ├── src/ # 业务源码(与路由/平台代码解耦) │ ├── components/ # 通用 UI 组件 │ ├── features/ # 按功能域拆分(推荐) │ │ ├── push/ # 推送相关(权限、token、上报等) │ │ ├── widget/ # 小组件数据与样式相关 │ │ └── cards/ # 情绪卡片滑动相关 │ ├── hooks/ # 自定义 hooks │ ├── navigation/ #(若不用 expo-router)React Navigation 配置 │ ├── screens/ #(若不用 expo-router)页面 │ ├── services/ # API 封装(request、接口定义) │ ├── store/ # 状态管理(Zustand/RTK) │ ├── utils/ # 工具函数(时间、格式化、校验等) │ ├── constants/ # 常量与配置(主题、枚举等) │ └── types/ # 全局类型声明 ├── assets/ # 静态资源(图片/字体/音频等) ├── app.json / app.config.ts # Expo 配置(环境区分可在此处理) ├── eas.json # EAS Build 配置(如使用) ├── package.json └── README.md ``` > 说明:如果你不使用 `expo-router`,可以删除 `app/`,并以 `src/navigation/` + `src/screens/` 作为主路由结构;其余目录保持不变即可。 ## iOS 小组件开发(V1:写死文案) > 重要:**Expo Go 不支持 WidgetKit**。要做真正的小组件,必须预构建 iOS 工程并用 Xcode 跑。 ### 1)生成 iOS 工程 ```bash cd client npx expo prebuild -p ios --no-install ``` 生成后会得到 `client/ios/`(Xcode 工程文件都在里面)。 ### 2)在 Xcode 创建 Widget Extension - 用 Xcode 打开:`client/ios/client.xcworkspace` - 菜单:`File -> New -> Target... -> Widget Extension` - Target 名称示例:`MindfulnessWidget` ### 3)写死文案与三尺寸布局 仓库里已提供 SwiftUI 代码骨架(写死文案 + Small/Medium/Large + 点击跳转): - `client/ios/情绪小组件/EmotionWidget.swift` 创建 Widget target 后,把该文件加入到 Widget target 中即可(Target Membership 勾选:`情绪小组件`)。 ### 4)点击跳转到 Home(Deep Link) Widget 点击跳转使用: - `client:///(app)/home` 代码里已通过 `widgetURL` 设置(见 `MindfulnessWidget.swift`)。