496 lines
13 KiB
Markdown
496 lines
13 KiB
Markdown
# Duooomi Expo App - Copilot Instructions
|
|
|
|
## 项目概述
|
|
|
|
这是一个基于 Expo + React Native 的跨平台移动应用,使用 TypeScript 开发,集成了蓝牙设备通信、模板生成、用户认证等核心功能。项目采用 Monorepo 架构,使用 Bun 作为包管理器。
|
|
|
|
## 技术栈
|
|
|
|
### 核心框架
|
|
- **Expo SDK 54** - 跨平台开发框架
|
|
- **React 19.1.0** - UI 框架
|
|
- **React Native 0.81.5** - 原生渲染
|
|
- **Expo Router** - 文件系统路由
|
|
- **TypeScript 5.9** - 类型安全
|
|
|
|
### 状态管理与数据
|
|
- **MobX 6** + **mobx-react-lite** - 响应式状态管理
|
|
- **Better Auth** - 身份认证
|
|
- **AsyncStorage** - 本地存储
|
|
|
|
### UI 与样式
|
|
- **NativeWind 4** - Tailwind CSS for React Native
|
|
- **Tailwind CSS 3.4** - 原子化 CSS
|
|
- **tailwind-variants** - 变体管理
|
|
- **class-variance-authority** - CVA 工具
|
|
- **Expo Linear Gradient** - 渐变效果
|
|
|
|
### 蓝牙与设备
|
|
- **react-native-ble-plx** - BLE 中心设备
|
|
- **react-native-multi-ble-peripheral** - BLE 外设
|
|
- **自定义协议层** (`ble/protocol/`)
|
|
|
|
### 媒体与资源
|
|
- **Expo Image** - 图片处理
|
|
- **Expo Image Picker** - 相册选择
|
|
- **react-native-video** - 视频播放
|
|
- **Expo Media Library** - 媒体库访问
|
|
|
|
### 支付与内购
|
|
- **Expo IAP** - 应用内购买
|
|
- **Stripe** - 支付集成
|
|
- **Expo Native Alipay / WeChat** - 第三方支付
|
|
|
|
### 开发工具
|
|
- **ESLint 9** - 代码检查
|
|
- **Prettier** - 代码格式化
|
|
- **Sentry** - 错误监控
|
|
- **EAS Build & Update** - 云构建与 OTA 更新
|
|
|
|
## 项目结构
|
|
|
|
```
|
|
expo-duooomi-app/
|
|
├── app/ # Expo Router 路由页面
|
|
│ ├── (tabs)/ # 主 Tab 导航
|
|
│ │ ├── index.tsx # 首页(模板列表)
|
|
│ │ ├── explore.tsx # 探索页(测试功能)
|
|
│ │ ├── generate.tsx # 生成页(AI 生成)
|
|
│ │ └── sync.tsx # 同步页(设备同步)
|
|
│ ├── _layout.tsx # 根布局
|
|
│ ├── auth.tsx # 登录页
|
|
│ ├── service.tsx # 服务条款
|
|
│ └── pointList.tsx # 积分充值
|
|
├── components/ # 通用 UI 组件
|
|
│ ├── ui/ # UI 基础组件
|
|
│ ├── themed-*/ # 主题化组件
|
|
│ └── BannerSection.tsx # 业务组件
|
|
├── hooks/ # React Hooks
|
|
│ ├── actions/ # 业务操作 hooks
|
|
│ ├── core/ # 核心 hooks
|
|
│ └── data/ # 数据获取 hooks
|
|
├── stores/ # MobX 状态管理
|
|
│ ├── bleStore.ts # 蓝牙状态
|
|
│ ├── userStore.ts # 用户状态
|
|
│ └── userBalanceStore.ts # 用户余额
|
|
├── ble/ # 蓝牙模块
|
|
│ ├── protocol/ # 协议定义
|
|
│ │ ├── types.ts # 类型定义(Zod)
|
|
│ │ └── utils.ts # 协议工具
|
|
│ ├── managers/ # 管理器
|
|
│ │ └── bleManager.ts # BLE 中心管理
|
|
│ └── flows/ # 业务流程
|
|
├── utils/ # 工具函数
|
|
│ ├── cn.ts # className 工具
|
|
│ ├── uploadFile.ts # 文件上传
|
|
│ └── storage.ts # 本地存储
|
|
├── lib/ # 第三方库封装
|
|
│ └── auth.ts # Better Auth 客户端
|
|
├── constants/ # 常量定义
|
|
├── @share/ # Monorepo 共享模块
|
|
│ ├── components/ # 共享组件
|
|
│ ├── hooks/ # 共享 hooks
|
|
│ └── apis/ # API 封装
|
|
├── assets/ # 静态资源
|
|
├── scripts/ # 构建脚本
|
|
│ └── export-icp-source.ts # ICP 备案导出
|
|
├── .env.development # 开发环境变量
|
|
├── .env.test # 测试环境变量
|
|
├── .env.production # 生产环境变量
|
|
├── eas.json # EAS 构建配置
|
|
├── app.config.js # Expo 应用配置
|
|
└── AGENTS.md # AI Agent 协作规范
|
|
```
|
|
|
|
## 代码规范
|
|
|
|
### 命名约定
|
|
|
|
#### 文件命名
|
|
- **路由页面**: `kebab-case.tsx` 或 `camelCase.tsx` (如 `pointList.tsx`, `point-list.tsx`)
|
|
- **组件文件**: `PascalCase.tsx` (如 `BannerSection.tsx`)
|
|
- **Hooks**: `camelCase.ts` 或 `kebab-case.ts` (如 `useTemplates.ts`, `use-template-actions.ts`)
|
|
- **工具函数**: `camelCase.ts` (如 `uploadFile.ts`)
|
|
- **类型文件**: `types.ts`
|
|
- **常量文件**: `camelCase.ts`
|
|
|
|
#### 变量与函数命名
|
|
- **组件**: `PascalCase` (`BannerSection`, `ThemedText`)
|
|
- **Hook 函数**: `camelCase` 以 `use` 开头 (`useTemplates`, `useUserBalance`)
|
|
- **普通函数**: `camelCase` (`uploadFile`, `cn`)
|
|
- **常量**: `SCREAMING_SNAKE_CASE` (`BLE_UUIDS`, `APP_VERSION`)
|
|
- **枚举/对象常量**: `SCREAMING_SNAKE_CASE` (`PREPARE_TRANSFER_STATUS`)
|
|
- **类型/接口**: `PascalCase` (`Template`, `MediaItem`, `SourceLine`)
|
|
- **Store 实例**: `camelCase` (`bleStore`, `userStore`)
|
|
|
|
### TypeScript 规范
|
|
|
|
#### 类型定义
|
|
```typescript
|
|
// 使用 Zod 进行运行时校验
|
|
import { z } from 'zod'
|
|
|
|
export const PrepareTransferRequestZod = z.object({
|
|
type: z.number().describe('命令类型 0x14'),
|
|
key: z.string().describe('资源 key'),
|
|
size: z.number().describe('文件大小(bytes)'),
|
|
})
|
|
|
|
export type PrepareTransferRequest = z.infer<typeof PrepareTransferRequestZod>
|
|
|
|
// 接口定义
|
|
interface SourceLine {
|
|
content: string
|
|
filePath: string
|
|
lineNumber: number
|
|
isFileHeader: boolean
|
|
isFileEnd: boolean
|
|
}
|
|
|
|
// 类型别名
|
|
type ActiveTab = 'gen' | '' | 'new' | 'like'
|
|
```
|
|
|
|
#### 严格模式
|
|
- 启用 `strict: true`
|
|
- 避免使用 `any`,优先使用 `unknown` 或明确类型
|
|
- 使用可选链 (`?.`) 和空值合并 (`??`)
|
|
|
|
### 样式规范
|
|
|
|
#### Tailwind CSS / NativeWind
|
|
```tsx
|
|
// 使用 className 而非内联样式
|
|
<Block className="flex-row items-center justify-between px-4 py-2">
|
|
<Text className="text-sm font-bold text-white">标题</Text>
|
|
</Block>
|
|
|
|
// 使用 cn() 工具合并条件样式
|
|
import { cn } from '@/utils/cn'
|
|
|
|
<Block className={cn(
|
|
"border-2 bg-white p-4",
|
|
isSelected && "border-accent",
|
|
isDisabled && "opacity-50"
|
|
)}>
|
|
```
|
|
|
|
#### 避免内联样式
|
|
- **不推荐**: `style={{ width: 100, height: 100 }}`
|
|
- **推荐**: `className="w-[100px] h-[100px]"`
|
|
- **例外**: 动态计算的值可以使用 style (如 `style={{ width: itemWidth }}`)
|
|
|
|
### React 规范
|
|
|
|
#### 组件定义
|
|
```typescript
|
|
// 函数组件 + TypeScript
|
|
import { observer } from 'mobx-react-lite'
|
|
import React from 'react'
|
|
|
|
type Props = {
|
|
title: string
|
|
onPress?: () => void
|
|
isActive?: boolean
|
|
}
|
|
|
|
export const MyComponent = observer(function MyComponent({
|
|
title,
|
|
onPress,
|
|
isActive = false
|
|
}: Props) {
|
|
return (
|
|
<Block onPress={onPress}>
|
|
<Text>{title}</Text>
|
|
</Block>
|
|
)
|
|
})
|
|
```
|
|
|
|
#### Hooks 使用
|
|
```typescript
|
|
// 自定义 Hook
|
|
export function useTemplates() {
|
|
const [data, setData] = useState<Template[]>([])
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
const execute = useCallback(async () => {
|
|
setLoading(true)
|
|
try {
|
|
const result = await api.getTemplates()
|
|
setData(result)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [])
|
|
|
|
return { data, loading, execute }
|
|
}
|
|
```
|
|
|
|
### MobX 规范
|
|
|
|
```typescript
|
|
// Store 定义
|
|
import { makeAutoObservable, runInAction } from 'mobx'
|
|
|
|
class UserStore {
|
|
user: User | null = null
|
|
isLogin = false
|
|
|
|
constructor() {
|
|
makeAutoObservable(this)
|
|
}
|
|
|
|
async login(credentials: Credentials) {
|
|
const user = await authApi.login(credentials)
|
|
runInAction(() => {
|
|
this.user = user
|
|
this.isLogin = true
|
|
})
|
|
}
|
|
}
|
|
|
|
export const userStore = new UserStore()
|
|
|
|
// 组件使用
|
|
import { observer } from 'mobx-react-lite'
|
|
|
|
export const Profile = observer(function Profile() {
|
|
const { user, isLogin } = userStore
|
|
|
|
if (!isLogin) return <Login />
|
|
|
|
return <Text>{user.name}</Text>
|
|
})
|
|
```
|
|
|
|
### 导入规范
|
|
|
|
```typescript
|
|
// 使用 @ 别名
|
|
import { Block, Text } from '@/@share/components'
|
|
import { useTemplates } from '@/hooks/data'
|
|
import { userStore } from '@/stores'
|
|
import { cn } from '@/utils/cn'
|
|
|
|
// 按类型分组排序(自动由 eslint-plugin-simple-import-sort 处理)
|
|
// 1. React 相关
|
|
// 2. 第三方库
|
|
// 3. 绝对路径导入 (@/)
|
|
// 4. 相对路径导入 (./)
|
|
// 5. 类型导入
|
|
```
|
|
|
|
### 组件库使用
|
|
|
|
```tsx
|
|
// 使用 @share/components 中的组件,而非 react-native 原生组件
|
|
// ✅ 推荐
|
|
import { Block, Text, Img } from '@/@share/components'
|
|
|
|
// ❌ 避免
|
|
import { View, Text, Image } from 'react-native'
|
|
|
|
// Block 替代 View
|
|
<Block className="flex-1 bg-white">
|
|
<Text>内容</Text>
|
|
</Block>
|
|
|
|
// Toast 统一使用项目封装的版本
|
|
import { Toast } from '@/@share/components'
|
|
|
|
Toast.show({ title: '操作成功' })
|
|
Toast.showLoading({ title: '加载中...' })
|
|
Toast.hideLoading()
|
|
Toast.showModal(<CustomModal />)
|
|
Toast.hideModal()
|
|
```
|
|
|
|
### 错误处理
|
|
|
|
```typescript
|
|
// 统一错误处理
|
|
try {
|
|
await api.uploadFile(file)
|
|
Toast.show({ title: '上传成功' })
|
|
} catch (error) {
|
|
console.error('上传失败:', error)
|
|
Toast.show({ title: error instanceof Error ? error.message : '上传失败' })
|
|
}
|
|
|
|
// Sentry 错误上报
|
|
import * as Sentry from '@sentry/react-native'
|
|
|
|
try {
|
|
// ...
|
|
} catch (error) {
|
|
Sentry.captureException(error)
|
|
throw error
|
|
}
|
|
```
|
|
|
|
## 最佳实践
|
|
|
|
### 组件设计
|
|
|
|
1. **单一职责**: 每个组件只做一件事
|
|
2. **Props 设计**: 明确的 Props 类型,合理的默认值
|
|
3. **记忆化**: 使用 `memo`, `useMemo`, `useCallback` 优化性能
|
|
4. **条件渲染**: 提前返回,减少嵌套
|
|
|
|
```typescript
|
|
// 提前返回
|
|
if (!isLogin) return <Login />
|
|
if (loading) return <Loading />
|
|
|
|
return <Content />
|
|
```
|
|
|
|
### 性能优化
|
|
|
|
```typescript
|
|
// 使用 FlashList 代替 FlatList
|
|
import { FlashList } from '@shopify/flash-list'
|
|
|
|
<FlashList
|
|
data={items}
|
|
renderItem={({ item }) => <Item data={item} />}
|
|
estimatedItemSize={100}
|
|
/>
|
|
|
|
// 图片优化
|
|
import { Img } from '@/@share/components'
|
|
|
|
<Img
|
|
src={uri}
|
|
className="w-full h-full"
|
|
isCompression
|
|
/>
|
|
```
|
|
|
|
### 蓝牙通信
|
|
|
|
```typescript
|
|
// 使用 bleManager 管理 BLE 连接
|
|
import { bleManager } from '@/ble/managers/bleManager'
|
|
|
|
// 扫描设备
|
|
await bleManager.startScan()
|
|
|
|
// 连接设备
|
|
await bleManager.connectToDevice(device)
|
|
|
|
// 发送数据
|
|
await bleManager.transferMediaSingle(fileUrl)
|
|
|
|
// 断开连接
|
|
await bleManager.disconnectDevice()
|
|
```
|
|
|
|
### 状态管理
|
|
|
|
- **本地 UI 状态**: 使用 `useState`
|
|
- **跨组件状态**: 使用 MobX Store
|
|
- **服务端状态**: 使用自定义 Hook (如 `useTemplates`)
|
|
- **持久化状态**: 使用 `AsyncStorage` 或 `expo-secure-store`
|
|
|
|
### 环境变量
|
|
|
|
```typescript
|
|
// 访问环境变量
|
|
const apiUrl = process.env.EXPO_PUBLIC_API_URL
|
|
const env = process.env.EXPO_PUBLIC_ENV // 'development' | 'test' | 'production'
|
|
|
|
// 根据环境执行不同逻辑
|
|
if (__DEV__) {
|
|
console.log('开发模式')
|
|
}
|
|
```
|
|
|
|
## 常用命令
|
|
|
|
```bash
|
|
# 安装依赖
|
|
bun install
|
|
|
|
# 启动开发服务器
|
|
bun run start # 开发环境
|
|
bun run start:test # 测试环境
|
|
bun run start:prod # 生产环境
|
|
|
|
# 运行平台
|
|
bun run android # Android
|
|
bun run ios # iOS
|
|
bun run web # Web
|
|
|
|
# 构建
|
|
bun run build:development:android
|
|
bun run build:production:ios
|
|
|
|
# OTA 更新
|
|
bun run update:production
|
|
|
|
# 代码检查
|
|
bun run lint
|
|
|
|
# ICP 备案导出
|
|
bun run export:icp:android
|
|
bun run export:icp:ios
|
|
|
|
# Prebuild
|
|
bun run prebuild:android
|
|
```
|
|
|
|
## 注意事项
|
|
|
|
1. **不要过度设计**: 优先简单直接的解决方案
|
|
2. **代码即文档**: 通过清晰的命名和结构表达意图,避免无用注释
|
|
3. **类型安全**: 充分利用 TypeScript,减少运行时错误
|
|
4. **性能优先**: 注意列表渲染、图片加载、状态更新的性能
|
|
5. **测试覆盖**: 为核心功能编写测试(当前项目暂无测试框架)
|
|
6. **错误监控**: 关键操作使用 Sentry 上报错误
|
|
7. **国际化准备**: 使用 `i18next` 管理多语言(已集成但未完全启用)
|
|
|
|
## 平台差异处理
|
|
|
|
```typescript
|
|
// 使用 Platform 处理平台差异
|
|
import { Platform } from 'react-native'
|
|
|
|
const isIOS = Platform.OS === 'ios'
|
|
const isAndroid = Platform.OS === 'android'
|
|
|
|
// 平台特定样式
|
|
<Block className={cn(
|
|
"p-4",
|
|
isIOS && "pt-8",
|
|
isAndroid && "pt-4"
|
|
)}>
|
|
|
|
// 平台特定文件
|
|
// pointList.native.tsx - 原生平台
|
|
// pointList.tsx - Web 平台
|
|
```
|
|
|
|
## AI Agent 协作
|
|
|
|
项目采用"千门八将"AI Agent 协作模式,详见 [AGENTS.md](./AGENTS.md) 和 [CLAUDE.md](./CLAUDE.md):
|
|
|
|
- **提将(orchestrator)**: 任务规划与调度
|
|
- **正将(code-artisan)**: 代码编写(追求简洁优雅)
|
|
- **反将(architect)**: 架构设计
|
|
- **风将(scout)**: 代码审查
|
|
- **火将(guard)**: 测试编写
|
|
- **除将(fixer)**: Bug 修复
|
|
- **脱将(deploy)**: 部署发布
|
|
- **谣将(researcher)**: 技术调研
|
|
|
|
根据任务类型自动调度合适的 Agent 协作完成。
|
|
|
|
---
|
|
|
|
**Always use Context7 MCP when I need library/API documentation, code generation, setup or configuration steps without me having to explicitly ask.**
|
|
|
|
**记住**: 代码是数字时代的文化遗产,每一行都应该有其存在的理由,追求简洁、优雅、高效。
|