feat: update
This commit is contained in:
4
.env.development
Normal file
4
.env.development
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# 开发环境配置
|
||||||
|
EXPO_PUBLIC_API_URL=/
|
||||||
|
EXPO_PUBLIC_API_TIMEOUT=10000
|
||||||
|
|
||||||
29
.env.example
29
.env.example
@@ -1,11 +1,34 @@
|
|||||||
# API 配置
|
# 环境变量示例文件
|
||||||
EXPO_PUBLIC_API_URL=https://api.example.com
|
# 复制此文件为 .env 并填入实际值
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# API 配置
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# API 基础 URL
|
||||||
|
# 开发环境推荐使用相对路径 "/" 配合代理服务器
|
||||||
|
# 生产环境根据实际情况配置:
|
||||||
|
# - 如果前后端同域:使用 "/"
|
||||||
|
# - 如果前后端分离:使用完整 URL "https://api.yourdomain.com/api"
|
||||||
|
EXPO_PUBLIC_API_URL=/
|
||||||
|
|
||||||
|
# API 请求超时时间(毫秒)
|
||||||
|
EXPO_PUBLIC_API_TIMEOUT=10000
|
||||||
|
|
||||||
|
# ============================================
|
||||||
# 应用信息
|
# 应用信息
|
||||||
|
# ============================================
|
||||||
|
|
||||||
EXPO_PUBLIC_APP_NAME=RN Demo
|
EXPO_PUBLIC_APP_NAME=RN Demo
|
||||||
EXPO_PUBLIC_APP_VERSION=1.0.0
|
EXPO_PUBLIC_APP_VERSION=1.0.0
|
||||||
|
|
||||||
# 其他配置
|
# ============================================
|
||||||
|
# 其他配置(可选)
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Sentry 错误追踪
|
||||||
# EXPO_PUBLIC_SENTRY_DSN=
|
# EXPO_PUBLIC_SENTRY_DSN=
|
||||||
|
|
||||||
|
# 分析工具 ID
|
||||||
# EXPO_PUBLIC_ANALYTICS_ID=
|
# EXPO_PUBLIC_ANALYTICS_ID=
|
||||||
|
|
||||||
|
|||||||
4
.env.production
Normal file
4
.env.production
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# 生产环境配置
|
||||||
|
EXPO_PUBLIC_API_URL=/
|
||||||
|
EXPO_PUBLIC_API_TIMEOUT=10000
|
||||||
|
|
||||||
55
.prettierignore
Normal file
55
.prettierignore
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
.expo
|
||||||
|
.expo-shared
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
.next
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Lock files
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
pnpm-lock.yaml
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
*.min.js
|
||||||
|
*.min.css
|
||||||
|
public
|
||||||
|
|
||||||
15
.prettierrc.json
Normal file
15
.prettierrc.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"quoteProps": "as-needed",
|
||||||
|
"proseWrap": "preserve"
|
||||||
|
}
|
||||||
91
README.md
91
README.md
@@ -69,16 +69,19 @@ pnpm start
|
|||||||
#### 方法 2:使用模拟器
|
#### 方法 2:使用模拟器
|
||||||
|
|
||||||
**Android 模拟器:**
|
**Android 模拟器:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm android
|
pnpm android
|
||||||
```
|
```
|
||||||
|
|
||||||
**iOS 模拟器(仅 macOS):**
|
**iOS 模拟器(仅 macOS):**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm ios
|
pnpm ios
|
||||||
```
|
```
|
||||||
|
|
||||||
**Web 浏览器:**
|
**Web 浏览器:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm web
|
pnpm web
|
||||||
```
|
```
|
||||||
@@ -156,12 +159,15 @@ rn-demo/
|
|||||||
### 关键目录说明
|
### 关键目录说明
|
||||||
|
|
||||||
#### 📱 `app/` - 路由和页面
|
#### 📱 `app/` - 路由和页面
|
||||||
|
|
||||||
- **`app/_layout.tsx`** - 应用启动时自动检查更新
|
- **`app/_layout.tsx`** - 应用启动时自动检查更新
|
||||||
- **`app/(tabs)/index.tsx`** - 热更新演示页面
|
- **`app/(tabs)/index.tsx`** - 热更新演示页面
|
||||||
- **`app/(tabs)/demo.tsx`** - 完整示例页面,展示所有工具的使用 🎯
|
- **`app/(tabs)/demo.tsx`** - 完整示例页面,展示所有工具的使用 🎯
|
||||||
|
|
||||||
#### 💻 `src/` - 源代码目录 🎯
|
#### 💻 `src/` - 源代码目录 🎯
|
||||||
|
|
||||||
项目的核心业务逻辑代码,包含:
|
项目的核心业务逻辑代码,包含:
|
||||||
|
|
||||||
- **`utils/`** - 工具函数(API、存储、日期)
|
- **`utils/`** - 工具函数(API、存储、日期)
|
||||||
- **`stores/`** - 状态管理(用户、设置)
|
- **`stores/`** - 状态管理(用户、设置)
|
||||||
- **`schemas/`** - 数据验证(认证、用户)
|
- **`schemas/`** - 数据验证(认证、用户)
|
||||||
@@ -171,7 +177,9 @@ rn-demo/
|
|||||||
- **`index.ts`** - 统一导出所有模块
|
- **`index.ts`** - 统一导出所有模块
|
||||||
|
|
||||||
#### 📚 `docs/` - 项目文档 🎯
|
#### 📚 `docs/` - 项目文档 🎯
|
||||||
|
|
||||||
完善的项目文档,包含:
|
完善的项目文档,包含:
|
||||||
|
|
||||||
- **使用指南** - 如何使用各个工具
|
- **使用指南** - 如何使用各个工具
|
||||||
- **代码示例** - 实际的代码示例
|
- **代码示例** - 实际的代码示例
|
||||||
- **配置说明** - 项目配置详解
|
- **配置说明** - 项目配置详解
|
||||||
@@ -179,12 +187,14 @@ rn-demo/
|
|||||||
### 核心文件说明
|
### 核心文件说明
|
||||||
|
|
||||||
#### 热更新相关 ⭐
|
#### 热更新相关 ⭐
|
||||||
|
|
||||||
- **`app.json`** - Expo 配置,包含热更新设置
|
- **`app.json`** - Expo 配置,包含热更新设置
|
||||||
- **`eas.json`** - EAS 构建和更新通道配置
|
- **`eas.json`** - EAS 构建和更新通道配置
|
||||||
- **`app/_layout.tsx`** - 自动检查更新逻辑
|
- **`app/_layout.tsx`** - 自动检查更新逻辑
|
||||||
- **`app/(tabs)/index.tsx`** - 手动检查更新功能
|
- **`app/(tabs)/index.tsx`** - 手动检查更新功能
|
||||||
|
|
||||||
#### 工具配置 🎯
|
#### 工具配置 🎯
|
||||||
|
|
||||||
- **`src/index.ts`** - 统一导出,从这里导入所有工具
|
- **`src/index.ts`** - 统一导出,从这里导入所有工具
|
||||||
- **`.env.example`** - 环境变量配置示例
|
- **`.env.example`** - 环境变量配置示例
|
||||||
- **`tsconfig.json`** - 配置了路径别名 `@/*`
|
- **`tsconfig.json`** - 配置了路径别名 `@/*`
|
||||||
@@ -193,17 +203,17 @@ rn-demo/
|
|||||||
|
|
||||||
项目已安装并配置好以下工具库:
|
项目已安装并配置好以下工具库:
|
||||||
|
|
||||||
| 类别 | 工具库 | 用途 |
|
| 类别 | 工具库 | 用途 |
|
||||||
|------|--------|------|
|
| ------------ | ----------------------------------------- | ------------------- |
|
||||||
| **工具类** | lodash-es | JavaScript 工具函数 |
|
| **工具类** | lodash-es | JavaScript 工具函数 |
|
||||||
| | dayjs | 日期处理 |
|
| | dayjs | 日期处理 |
|
||||||
| | axios | HTTP 请求 |
|
| | axios | HTTP 请求 |
|
||||||
| **状态管理** | zustand | 轻量级状态管理 |
|
| **状态管理** | zustand | 轻量级状态管理 |
|
||||||
| **表单处理** | react-hook-form | 表单管理 |
|
| **表单处理** | react-hook-form | 表单管理 |
|
||||||
| | zod | 数据验证 |
|
| | zod | 数据验证 |
|
||||||
| **原生功能** | @react-native-async-storage/async-storage | 本地存储 |
|
| **原生功能** | @react-native-async-storage/async-storage | 本地存储 |
|
||||||
| | expo-image | 优化的图片组件 |
|
| | expo-image | 优化的图片组件 |
|
||||||
| | expo-haptics | 触觉反馈 |
|
| | expo-haptics | 触觉反馈 |
|
||||||
|
|
||||||
### 快速开始
|
### 快速开始
|
||||||
|
|
||||||
@@ -243,22 +253,26 @@ eas init
|
|||||||
```
|
```
|
||||||
|
|
||||||
这会:
|
这会:
|
||||||
|
|
||||||
- 创建一个唯一的项目 ID
|
- 创建一个唯一的项目 ID
|
||||||
- 自动更新 `app.json` 中的 `extra.eas.projectId`
|
- 自动更新 `app.json` 中的 `extra.eas.projectId`
|
||||||
|
|
||||||
### 步骤 3:构建开发版本
|
### 步骤 3:构建开发版本
|
||||||
|
|
||||||
**Android 开发构建:**
|
**Android 开发构建:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
eas build --profile development --platform android
|
eas build --profile development --platform android
|
||||||
```
|
```
|
||||||
|
|
||||||
**iOS 开发构建(需要 macOS 和 Apple 开发者账号):**
|
**iOS 开发构建(需要 macOS 和 Apple 开发者账号):**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
eas build --profile development --platform ios
|
eas build --profile development --platform ios
|
||||||
```
|
```
|
||||||
|
|
||||||
构建过程需要 **10-20 分钟**。完成后:
|
构建过程需要 **10-20 分钟**。完成后:
|
||||||
|
|
||||||
1. 在 [expo.dev](https://expo.dev) 控制台下载构建的 APK/IPA 文件
|
1. 在 [expo.dev](https://expo.dev) 控制台下载构建的 APK/IPA 文件
|
||||||
2. 安装到你的设备上
|
2. 安装到你的设备上
|
||||||
|
|
||||||
@@ -291,11 +305,11 @@ eas update --channel production --message "v1.0.1: 添加了新功能"
|
|||||||
|
|
||||||
项目配置了三个更新通道(在 `eas.json` 中定义):
|
项目配置了三个更新通道(在 `eas.json` 中定义):
|
||||||
|
|
||||||
| 通道 | 用途 | 适用场景 |
|
| 通道 | 用途 | 适用场景 |
|
||||||
|------|------|----------|
|
| --------------- | -------- | -------------- |
|
||||||
| **development** | 开发环境 | 日常开发和测试 |
|
| **development** | 开发环境 | 日常开发和测试 |
|
||||||
| **preview** | 预览环境 | 内部测试和 QA |
|
| **preview** | 预览环境 | 内部测试和 QA |
|
||||||
| **production** | 生产环境 | 正式发布给用户 |
|
| **production** | 生产环境 | 正式发布给用户 |
|
||||||
|
|
||||||
不同的构建配置会订阅不同的更新通道:
|
不同的构建配置会订阅不同的更新通道:
|
||||||
|
|
||||||
@@ -404,12 +418,9 @@ button: {
|
|||||||
const [count, setCount] = useState(0);
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
// 在 JSX 中添加
|
// 在 JSX 中添加
|
||||||
<TouchableOpacity
|
<TouchableOpacity style={styles.button} onPress={() => setCount(count + 1)}>
|
||||||
style={styles.button}
|
|
||||||
onPress={() => setCount(count + 1)}
|
|
||||||
>
|
|
||||||
<Text style={styles.buttonText}>点击次数: {count}</Text>
|
<Text style={styles.buttonText}>点击次数: {count}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>;
|
||||||
```
|
```
|
||||||
|
|
||||||
修改后,运行 `eas update` 发布更新,然后在应用中检查更新即可看到变化。
|
修改后,运行 `eas update` 发布更新,然后在应用中检查更新即可看到变化。
|
||||||
@@ -459,16 +470,16 @@ Alert.alert(
|
|||||||
"name": "rn-demo",
|
"name": "rn-demo",
|
||||||
"slug": "rn-demo",
|
"slug": "rn-demo",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"newArchEnabled": true, // 启用 React Native 新架构
|
"newArchEnabled": true, // 启用 React Native 新架构
|
||||||
"updates": {
|
"updates": {
|
||||||
"url": "https://u.expo.dev/your-project-id" // EAS Update 服务器
|
"url": "https://u.expo.dev/your-project-id" // EAS Update 服务器
|
||||||
},
|
},
|
||||||
"runtimeVersion": {
|
"runtimeVersion": {
|
||||||
"policy": "appVersion" // 运行时版本策略
|
"policy": "appVersion" // 运行时版本策略
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"expo-router", // Expo Router 插件
|
"expo-router", // Expo Router 插件
|
||||||
"expo-updates" // 热更新插件
|
"expo-updates" // 热更新插件
|
||||||
],
|
],
|
||||||
"ios": {
|
"ios": {
|
||||||
"bundleIdentifier": "com.rndemo.app"
|
"bundleIdentifier": "com.rndemo.app"
|
||||||
@@ -481,6 +492,7 @@ Alert.alert(
|
|||||||
```
|
```
|
||||||
|
|
||||||
**配置说明:**
|
**配置说明:**
|
||||||
|
|
||||||
- `updates.url` - EAS Update 服务器地址(运行 `eas init` 后自动生成)
|
- `updates.url` - EAS Update 服务器地址(运行 `eas init` 后自动生成)
|
||||||
- `runtimeVersion.policy` - 运行时版本策略,确保更新兼容性
|
- `runtimeVersion.policy` - 运行时版本策略,确保更新兼容性
|
||||||
- `appVersion` - 基于 `version` 字段(当前使用)
|
- `appVersion` - 基于 `version` 字段(当前使用)
|
||||||
@@ -496,22 +508,23 @@ Alert.alert(
|
|||||||
{
|
{
|
||||||
"build": {
|
"build": {
|
||||||
"development": {
|
"development": {
|
||||||
"developmentClient": true, // 开发客户端
|
"developmentClient": true, // 开发客户端
|
||||||
"distribution": "internal", // 内部分发
|
"distribution": "internal", // 内部分发
|
||||||
"channel": "development" // 订阅 development 更新通道
|
"channel": "development" // 订阅 development 更新通道
|
||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"distribution": "internal",
|
"distribution": "internal",
|
||||||
"channel": "preview" // 订阅 preview 更新通道
|
"channel": "preview" // 订阅 preview 更新通道
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"channel": "production" // 订阅 production 更新通道
|
"channel": "production" // 订阅 production 更新通道
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**配置说明:**
|
**配置说明:**
|
||||||
|
|
||||||
- `developmentClient` - 是否为开发客户端(包含开发工具)
|
- `developmentClient` - 是否为开发客户端(包含开发工具)
|
||||||
- `distribution` - 分发方式(`internal` 或 `store`)
|
- `distribution` - 分发方式(`internal` 或 `store`)
|
||||||
- `channel` - 更新通道,决定应用接收哪个通道的更新
|
- `channel` - 更新通道,决定应用接收哪个通道的更新
|
||||||
@@ -521,6 +534,7 @@ Alert.alert(
|
|||||||
### 何时使用热更新
|
### 何时使用热更新
|
||||||
|
|
||||||
✅ **适合热更新的场景:**
|
✅ **适合热更新的场景:**
|
||||||
|
|
||||||
- ✅ 修复 JavaScript/TypeScript 代码 bug
|
- ✅ 修复 JavaScript/TypeScript 代码 bug
|
||||||
- ✅ 更新 UI 样式和布局
|
- ✅ 更新 UI 样式和布局
|
||||||
- ✅ 修改业务逻辑
|
- ✅ 修改业务逻辑
|
||||||
@@ -529,6 +543,7 @@ Alert.alert(
|
|||||||
- ✅ 添加新的 JS 功能
|
- ✅ 添加新的 JS 功能
|
||||||
|
|
||||||
❌ **不适合热更新的场景(需要重新构建):**
|
❌ **不适合热更新的场景(需要重新构建):**
|
||||||
|
|
||||||
- ❌ 添加/删除原生依赖(如 `react-native-camera`)
|
- ❌ 添加/删除原生依赖(如 `react-native-camera`)
|
||||||
- ❌ 修改原生代码(iOS/Android)
|
- ❌ 修改原生代码(iOS/Android)
|
||||||
- ❌ 更改应用权限(如相机、位置权限)
|
- ❌ 更改应用权限(如相机、位置权限)
|
||||||
@@ -539,11 +554,13 @@ Alert.alert(
|
|||||||
### 版本管理策略
|
### 版本管理策略
|
||||||
|
|
||||||
**appVersion 策略**(当前使用):
|
**appVersion 策略**(当前使用):
|
||||||
|
|
||||||
- 基于 `app.json` 中的 `version` 字段
|
- 基于 `app.json` 中的 `version` 字段
|
||||||
- ✅ 优点:简单直观,易于理解
|
- ✅ 优点:简单直观,易于理解
|
||||||
- ⚠️ 注意:每次原生构建需要手动更新版本号
|
- ⚠️ 注意:每次原生构建需要手动更新版本号
|
||||||
|
|
||||||
**nativeVersion 策略**:
|
**nativeVersion 策略**:
|
||||||
|
|
||||||
- 基于原生构建号(iOS 的 `buildNumber`,Android 的 `versionCode`)
|
- 基于原生构建号(iOS 的 `buildNumber`,Android 的 `versionCode`)
|
||||||
- ✅ 优点:自动管理,无需手动更新
|
- ✅ 优点:自动管理,无需手动更新
|
||||||
- ⚠️ 注意:需要配置原生构建号
|
- ⚠️ 注意:需要配置原生构建号
|
||||||
@@ -564,6 +581,7 @@ Alert.alert(
|
|||||||
### Q: 更新后没有生效?
|
### Q: 更新后没有生效?
|
||||||
|
|
||||||
**A:** 检查以下几点:
|
**A:** 检查以下几点:
|
||||||
|
|
||||||
1. 确保应用完全关闭后重新打开(不是后台切换)
|
1. 确保应用完全关闭后重新打开(不是后台切换)
|
||||||
2. 检查更新通道是否匹配(development/preview/production)
|
2. 检查更新通道是否匹配(development/preview/production)
|
||||||
3. 确保 `runtimeVersion` 匹配
|
3. 确保 `runtimeVersion` 匹配
|
||||||
@@ -573,6 +591,7 @@ Alert.alert(
|
|||||||
### Q: 如何回滚到之前的版本?
|
### Q: 如何回滚到之前的版本?
|
||||||
|
|
||||||
**A:**
|
**A:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 查看更新历史
|
# 查看更新历史
|
||||||
eas update:list --channel production
|
eas update:list --channel production
|
||||||
@@ -584,6 +603,7 @@ eas update --channel production --branch main --message "回滚到稳定版本"
|
|||||||
### Q: 热更新的大小限制是多少?
|
### Q: 热更新的大小限制是多少?
|
||||||
|
|
||||||
**A:** EAS Update 没有严格的大小限制,但建议:
|
**A:** EAS Update 没有严格的大小限制,但建议:
|
||||||
|
|
||||||
- 保持更新包 < 10MB 以确保良好的用户体验
|
- 保持更新包 < 10MB 以确保良好的用户体验
|
||||||
- 避免在更新中包含大量图片或资源文件
|
- 避免在更新中包含大量图片或资源文件
|
||||||
- 使用 CDN 托管大型资源
|
- 使用 CDN 托管大型资源
|
||||||
@@ -599,7 +619,7 @@ if (update.isAvailable) {
|
|||||||
'发现新版本',
|
'发现新版本',
|
||||||
'请更新到最新版本',
|
'请更新到最新版本',
|
||||||
[{ text: '立即更新', onPress: async () => await Updates.reloadAsync() }],
|
[{ text: '立即更新', onPress: async () => await Updates.reloadAsync() }],
|
||||||
{ cancelable: false } // 不可取消
|
{ cancelable: false } // 不可取消
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -607,6 +627,7 @@ if (update.isAvailable) {
|
|||||||
### Q: 更新检查的频率是多少?
|
### Q: 更新检查的频率是多少?
|
||||||
|
|
||||||
**A:**
|
**A:**
|
||||||
|
|
||||||
- **应用启动时**:自动检查(在 `app/_layout.tsx` 中配置)
|
- **应用启动时**:自动检查(在 `app/_layout.tsx` 中配置)
|
||||||
- **手动检查**:用户点击"检查更新"按钮
|
- **手动检查**:用户点击"检查更新"按钮
|
||||||
- **后台检查**:可以配置定时检查(需要自己实现)
|
- **后台检查**:可以配置定时检查(需要自己实现)
|
||||||
@@ -614,6 +635,7 @@ if (update.isAvailable) {
|
|||||||
### Q: 如何在开发时测试热更新?
|
### Q: 如何在开发时测试热更新?
|
||||||
|
|
||||||
**A:**
|
**A:**
|
||||||
|
|
||||||
1. 构建 Development Build:`eas build --profile development --platform android`
|
1. 构建 Development Build:`eas build --profile development --platform android`
|
||||||
2. 安装到设备
|
2. 安装到设备
|
||||||
3. 修改代码
|
3. 修改代码
|
||||||
@@ -635,6 +657,7 @@ if (update.isAvailable) {
|
|||||||
## 📚 相关资源
|
## 📚 相关资源
|
||||||
|
|
||||||
### 官方文档
|
### 官方文档
|
||||||
|
|
||||||
- [Expo 官方文档](https://docs.expo.dev/)
|
- [Expo 官方文档](https://docs.expo.dev/)
|
||||||
- [Expo Router 文档](https://docs.expo.dev/router/introduction/)
|
- [Expo Router 文档](https://docs.expo.dev/router/introduction/)
|
||||||
- [EAS Update 文档](https://docs.expo.dev/eas-update/introduction/)
|
- [EAS Update 文档](https://docs.expo.dev/eas-update/introduction/)
|
||||||
@@ -642,11 +665,13 @@ if (update.isAvailable) {
|
|||||||
- [React Native 文档](https://reactnative.dev/)
|
- [React Native 文档](https://reactnative.dev/)
|
||||||
|
|
||||||
### 学习资源
|
### 学习资源
|
||||||
|
|
||||||
- [Expo Router 最佳实践](https://docs.expo.dev/router/best-practices/)
|
- [Expo Router 最佳实践](https://docs.expo.dev/router/best-practices/)
|
||||||
- [EAS Update 最佳实践](https://docs.expo.dev/eas-update/best-practices/)
|
- [EAS Update 最佳实践](https://docs.expo.dev/eas-update/best-practices/)
|
||||||
- [React Native 新架构](https://reactnative.dev/docs/the-new-architecture/landing-page)
|
- [React Native 新架构](https://reactnative.dev/docs/the-new-architecture/landing-page)
|
||||||
|
|
||||||
### 社区
|
### 社区
|
||||||
|
|
||||||
- [Expo Discord](https://chat.expo.dev/)
|
- [Expo Discord](https://chat.expo.dev/)
|
||||||
- [Expo Forums](https://forums.expo.dev/)
|
- [Expo Forums](https://forums.expo.dev/)
|
||||||
- [React Native Community](https://reactnative.dev/community/overview)
|
- [React Native Community](https://reactnative.dev/community/overview)
|
||||||
@@ -658,10 +683,8 @@ if (update.isAvailable) {
|
|||||||
- **[使用示例](./docs/USAGE_EXAMPLES.md)** - 实际代码示例
|
- **[使用示例](./docs/USAGE_EXAMPLES.md)** - 实际代码示例
|
||||||
- **[工具库使用指南](./docs/LIBRARIES.md)** - 详细的工具库使用方法和示例
|
- **[工具库使用指南](./docs/LIBRARIES.md)** - 详细的工具库使用方法和示例
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**祝你开发愉快!** 🎉
|
**祝你开发愉快!** 🎉
|
||||||
|
|
||||||
如有问题,请查看 [常见问题](#-常见问题) 或访问 [Expo 官方文档](https://docs.expo.dev/)。
|
如有问题,请查看 [常见问题](#-常见问题) 或访问 [Expo 官方文档](https://docs.expo.dev/)。
|
||||||
|
|
||||||
|
|||||||
5
app.json
5
app.json
@@ -31,10 +31,7 @@
|
|||||||
"output": "static",
|
"output": "static",
|
||||||
"favicon": "./assets/images/favicon.png"
|
"favicon": "./assets/images/favicon.png"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": ["expo-router", "expo-updates"],
|
||||||
"expo-router",
|
|
||||||
"expo-updates"
|
|
||||||
],
|
|
||||||
"experiments": {
|
"experiments": {
|
||||||
"typedRoutes": true
|
"typedRoutes": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export default function TabLayout() {
|
|||||||
// Disable the static render of the header on web
|
// Disable the static render of the header on web
|
||||||
// to prevent a hydration error in React Navigation v6.
|
// to prevent a hydration error in React Navigation v6.
|
||||||
headerShown: useClientOnlyValue(false, true),
|
headerShown: useClientOnlyValue(false, true),
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="index"
|
name="index"
|
||||||
options={{
|
options={{
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
formatDate,
|
formatDate,
|
||||||
formatRelativeTime,
|
formatRelativeTime,
|
||||||
formatChatTime,
|
formatChatTime,
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
useUserStore,
|
useUserStore,
|
||||||
useUser,
|
useUser,
|
||||||
@@ -36,14 +36,15 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
useLanguage,
|
useLanguage,
|
||||||
useHapticsEnabled,
|
useHapticsEnabled,
|
||||||
|
|
||||||
// 验证规则
|
// 验证规则
|
||||||
loginSchema,
|
loginSchema,
|
||||||
type LoginFormData,
|
type LoginFormData,
|
||||||
|
|
||||||
// API 服务
|
// API 服务
|
||||||
authService,
|
authService,
|
||||||
|
appService,
|
||||||
|
|
||||||
// 自定义 Hooks
|
// 自定义 Hooks
|
||||||
useDebounce,
|
useDebounce,
|
||||||
useThrottle,
|
useThrottle,
|
||||||
@@ -51,14 +52,15 @@ import {
|
|||||||
} from '@/src';
|
} from '@/src';
|
||||||
|
|
||||||
export default function DemoScreen() {
|
export default function DemoScreen() {
|
||||||
|
console.log('=== DemoScreen 组件已渲染 ===');
|
||||||
const haptics = useHaptics();
|
const haptics = useHaptics();
|
||||||
|
|
||||||
// 状态管理示例
|
// 状态管理示例
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const isLoggedIn = useIsLoggedIn();
|
const isLoggedIn = useIsLoggedIn();
|
||||||
const login = useUserStore((state) => state.login);
|
const login = useUserStore((state) => state.login);
|
||||||
const logout = useUserStore((state) => state.logout);
|
const logout = useUserStore((state) => state.logout);
|
||||||
|
|
||||||
// 设置状态
|
// 设置状态
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const language = useLanguage();
|
const language = useLanguage();
|
||||||
@@ -66,7 +68,7 @@ export default function DemoScreen() {
|
|||||||
const setTheme = useSettingsStore((state) => state.setTheme);
|
const setTheme = useSettingsStore((state) => state.setTheme);
|
||||||
const setLanguage = useSettingsStore((state) => state.setLanguage);
|
const setLanguage = useSettingsStore((state) => state.setLanguage);
|
||||||
const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled);
|
const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled);
|
||||||
|
|
||||||
// 本地状态
|
// 本地状态
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
const [searchResults, setSearchResults] = useState<string[]>([]);
|
const [searchResults, setSearchResults] = useState<string[]>([]);
|
||||||
@@ -89,6 +91,7 @@ export default function DemoScreen() {
|
|||||||
|
|
||||||
// 防抖搜索示例
|
// 防抖搜索示例
|
||||||
const debouncedSearch = useDebounce(async (text: string) => {
|
const debouncedSearch = useDebounce(async (text: string) => {
|
||||||
|
console.log('防抖搜索:', text);
|
||||||
if (!text.trim()) {
|
if (!text.trim()) {
|
||||||
setSearchResults([]);
|
setSearchResults([]);
|
||||||
return;
|
return;
|
||||||
@@ -97,13 +100,24 @@ export default function DemoScreen() {
|
|||||||
console.log('执行搜索:', text);
|
console.log('执行搜索:', text);
|
||||||
// 模拟 API 调用
|
// 模拟 API 调用
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
setSearchResults([
|
setSearchResults([`结果 1: ${text}`, `结果 2: ${text}`, `结果 3: ${text}`]);
|
||||||
`结果 1: ${text}`,
|
|
||||||
`结果 2: ${text}`,
|
|
||||||
`结果 3: ${text}`,
|
|
||||||
]);
|
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('=== useEffect 开始执行 ===');
|
||||||
|
console.log('appService:', appService);
|
||||||
|
console.log('getPlatformData 方法:', appService.getPlatformData);
|
||||||
|
|
||||||
|
appService
|
||||||
|
.getPlatformData()
|
||||||
|
.then((res: any) => {
|
||||||
|
console.log('getPlatformData 成功:', res);
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.error('getPlatformData 失败:', err);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 监听搜索文本变化
|
// 监听搜索文本变化
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
debouncedSearch(searchText);
|
debouncedSearch(searchText);
|
||||||
@@ -124,11 +138,11 @@ export default function DemoScreen() {
|
|||||||
|
|
||||||
// 模拟登录 API 调用
|
// 模拟登录 API 调用
|
||||||
console.log('登录数据:', data);
|
console.log('登录数据:', data);
|
||||||
|
|
||||||
// 实际项目中使用:
|
// 实际项目中使用:
|
||||||
// const { user, token } = await authService.login(data);
|
// const { user, token } = await authService.login(data);
|
||||||
// login(user, token);
|
// login(user, token);
|
||||||
|
|
||||||
// 模拟登录成功
|
// 模拟登录成功
|
||||||
const mockUser = {
|
const mockUser = {
|
||||||
id: '1',
|
id: '1',
|
||||||
@@ -138,7 +152,7 @@ export default function DemoScreen() {
|
|||||||
nickname: '演示用户',
|
nickname: '演示用户',
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
login(mockUser, 'mock-token-123456');
|
login(mockUser, 'mock-token-123456');
|
||||||
haptics.success();
|
haptics.success();
|
||||||
Alert.alert('成功', '登录成功!');
|
Alert.alert('成功', '登录成功!');
|
||||||
@@ -153,20 +167,16 @@ export default function DemoScreen() {
|
|||||||
// 登出处理
|
// 登出处理
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
haptics.warning();
|
haptics.warning();
|
||||||
Alert.alert(
|
Alert.alert('确认', '确定要退出登录吗?', [
|
||||||
'确认',
|
{ text: '取消', style: 'cancel' },
|
||||||
'确定要退出登录吗?',
|
{
|
||||||
[
|
text: '确定',
|
||||||
{ text: '取消', style: 'cancel' },
|
onPress: () => {
|
||||||
{
|
logout();
|
||||||
text: '确定',
|
haptics.success();
|
||||||
onPress: () => {
|
|
||||||
logout();
|
|
||||||
haptics.success();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]
|
},
|
||||||
);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 存储示例
|
// 存储示例
|
||||||
@@ -178,7 +188,7 @@ export default function DemoScreen() {
|
|||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
counter,
|
counter,
|
||||||
};
|
};
|
||||||
|
|
||||||
await Storage.setObject(STORAGE_KEYS.USER_PREFERENCES, testData);
|
await Storage.setObject(STORAGE_KEYS.USER_PREFERENCES, testData);
|
||||||
haptics.success();
|
haptics.success();
|
||||||
Alert.alert('成功', '数据已保存到本地存储');
|
Alert.alert('成功', '数据已保存到本地存储');
|
||||||
@@ -192,7 +202,7 @@ export default function DemoScreen() {
|
|||||||
try {
|
try {
|
||||||
haptics.light();
|
haptics.light();
|
||||||
const data = await Storage.getObject<any>(STORAGE_KEYS.USER_PREFERENCES);
|
const data = await Storage.getObject<any>(STORAGE_KEYS.USER_PREFERENCES);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
setStorageValue(JSON.stringify(data, null, 2));
|
setStorageValue(JSON.stringify(data, null, 2));
|
||||||
haptics.success();
|
haptics.success();
|
||||||
@@ -234,11 +244,7 @@ export default function DemoScreen() {
|
|||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
<View style={styles.userInfo}>
|
<View style={styles.userInfo}>
|
||||||
{user?.avatar && (
|
{user?.avatar && (
|
||||||
<Image
|
<Image source={{ uri: user.avatar }} style={styles.avatar} contentFit="cover" />
|
||||||
source={{ uri: user.avatar }}
|
|
||||||
style={styles.avatar}
|
|
||||||
contentFit="cover"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<View style={styles.userDetails}>
|
<View style={styles.userDetails}>
|
||||||
<Text style={styles.userName}>{user?.nickname}</Text>
|
<Text style={styles.userName}>{user?.nickname}</Text>
|
||||||
@@ -247,10 +253,7 @@ export default function DemoScreen() {
|
|||||||
注册时间: {formatRelativeTime(user?.createdAt || '')}
|
注册时间: {formatRelativeTime(user?.createdAt || '')}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity
|
<TouchableOpacity style={[styles.button, styles.logoutButton]} onPress={handleLogout}>
|
||||||
style={[styles.button, styles.logoutButton]}
|
|
||||||
onPress={handleLogout}
|
|
||||||
>
|
|
||||||
<Text style={styles.buttonText}>退出</Text>
|
<Text style={styles.buttonText}>退出</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
@@ -263,7 +266,7 @@ export default function DemoScreen() {
|
|||||||
{!isLoggedIn && (
|
{!isLoggedIn && (
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>🔐 登录表单 (React Hook Form + Zod)</Text>
|
<Text style={styles.sectionTitle}>🔐 登录表单 (React Hook Form + Zod)</Text>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="email"
|
name="email"
|
||||||
@@ -276,14 +279,9 @@ export default function DemoScreen() {
|
|||||||
placeholder="请输入邮箱"
|
placeholder="请输入邮箱"
|
||||||
keyboardType="email-address"
|
keyboardType="email-address"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
style={[
|
style={[styles.input, errors.email && styles.inputError]}
|
||||||
styles.input,
|
|
||||||
errors.email && styles.inputError,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
{errors.email && (
|
{errors.email && <Text style={styles.errorText}>{errors.email.message}</Text>}
|
||||||
<Text style={styles.errorText}>{errors.email.message}</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -299,10 +297,7 @@ export default function DemoScreen() {
|
|||||||
onChangeText={onChange}
|
onChangeText={onChange}
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
style={[
|
style={[styles.input, errors.password && styles.inputError]}
|
||||||
styles.input,
|
|
||||||
errors.password && styles.inputError,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
{errors.password && (
|
{errors.password && (
|
||||||
<Text style={styles.errorText}>{errors.password.message}</Text>
|
<Text style={styles.errorText}>{errors.password.message}</Text>
|
||||||
@@ -349,10 +344,7 @@ export default function DemoScreen() {
|
|||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>⏱️ 节流点击 (useThrottle)</Text>
|
<Text style={styles.sectionTitle}>⏱️ 节流点击 (useThrottle)</Text>
|
||||||
<Text style={styles.infoText}>点击次数: {counter}</Text>
|
<Text style={styles.infoText}>点击次数: {counter}</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity style={[styles.button, styles.primaryButton]} onPress={throttledClick}>
|
||||||
style={[styles.button, styles.primaryButton]}
|
|
||||||
onPress={throttledClick}
|
|
||||||
>
|
|
||||||
<Text style={styles.buttonText}>快速点击测试(1秒节流)</Text>
|
<Text style={styles.buttonText}>快速点击测试(1秒节流)</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
@@ -387,18 +379,14 @@ export default function DemoScreen() {
|
|||||||
<Text style={styles.infoText}>
|
<Text style={styles.infoText}>
|
||||||
当前时间: {formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')}
|
当前时间: {formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.infoText}>
|
<Text style={styles.infoText}>相对时间: {formatRelativeTime(new Date())}</Text>
|
||||||
相对时间: {formatRelativeTime(new Date())}
|
<Text style={styles.infoText}>聊天时间: {formatChatTime(Date.now())}</Text>
|
||||||
</Text>
|
|
||||||
<Text style={styles.infoText}>
|
|
||||||
聊天时间: {formatChatTime(Date.now())}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 设置示例 */}
|
{/* 设置示例 */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>⚙️ 应用设置</Text>
|
<Text style={styles.sectionTitle}>⚙️ 应用设置</Text>
|
||||||
|
|
||||||
<View style={styles.settingRow}>
|
<View style={styles.settingRow}>
|
||||||
<Text style={styles.settingLabel}>主题: {theme}</Text>
|
<Text style={styles.settingLabel}>主题: {theme}</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -475,9 +463,7 @@ export default function DemoScreen() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
<Text style={styles.footerText}>
|
<Text style={styles.footerText}>查看代码了解更多使用方法 📖</Text>
|
||||||
查看代码了解更多使用方法 📖
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -607,7 +593,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
buttonRow: {
|
buttonRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
gap: 12,
|
justifyContent: 'space-between',
|
||||||
},
|
},
|
||||||
halfButton: {
|
halfButton: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -651,27 +637,31 @@ const styles = StyleSheet.create({
|
|||||||
buttonGrid: {
|
buttonGrid: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
gap: 8,
|
marginHorizontal: -4,
|
||||||
},
|
},
|
||||||
hapticsButton: {
|
hapticsButton: {
|
||||||
backgroundColor: '#5856D6',
|
backgroundColor: '#5856D6',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: '30%',
|
minWidth: '30%',
|
||||||
|
margin: 4,
|
||||||
},
|
},
|
||||||
successButton: {
|
successButton: {
|
||||||
backgroundColor: '#34C759',
|
backgroundColor: '#34C759',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
margin: 4,
|
||||||
minWidth: '30%',
|
minWidth: '30%',
|
||||||
},
|
},
|
||||||
warningButton: {
|
warningButton: {
|
||||||
backgroundColor: '#FF9500',
|
backgroundColor: '#FF9500',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: '30%',
|
minWidth: '30%',
|
||||||
|
margin: 4,
|
||||||
},
|
},
|
||||||
errorButton: {
|
errorButton: {
|
||||||
backgroundColor: '#FF3B30',
|
backgroundColor: '#FF3B30',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: '30%',
|
minWidth: '30%',
|
||||||
|
margin: 4,
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
marginTop: 24,
|
marginTop: 24,
|
||||||
@@ -683,4 +673,3 @@ const styles = StyleSheet.create({
|
|||||||
color: '#999',
|
color: '#999',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { StyleSheet, TouchableOpacity, Alert, ActivityIndicator } from 'react-native';
|
import { StyleSheet, TouchableOpacity, Alert, ActivityIndicator } from 'react-native';
|
||||||
import * as Updates from 'expo-updates';
|
import * as Updates from 'expo-updates';
|
||||||
|
|
||||||
@@ -24,23 +24,19 @@ export default function TabOneScreen() {
|
|||||||
setUpdateInfo('发现新版本,正在下载...');
|
setUpdateInfo('发现新版本,正在下载...');
|
||||||
await Updates.fetchUpdateAsync();
|
await Updates.fetchUpdateAsync();
|
||||||
|
|
||||||
Alert.alert(
|
Alert.alert('更新完成', '新版本已下载完成,是否立即重启应用?', [
|
||||||
'更新完成',
|
{
|
||||||
'新版本已下载完成,是否立即重启应用?',
|
text: '稍后',
|
||||||
[
|
style: 'cancel',
|
||||||
{
|
onPress: () => setUpdateInfo('更新已下载,稍后重启应用即可应用'),
|
||||||
text: '稍后',
|
},
|
||||||
style: 'cancel',
|
{
|
||||||
onPress: () => setUpdateInfo('更新已下载,稍后重启应用即可应用'),
|
text: '立即重启',
|
||||||
|
onPress: async () => {
|
||||||
|
await Updates.reloadAsync();
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
text: '立即重启',
|
]);
|
||||||
onPress: async () => {
|
|
||||||
await Updates.reloadAsync();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
setUpdateInfo('当前已是最新版本');
|
setUpdateInfo('当前已是最新版本');
|
||||||
}
|
}
|
||||||
@@ -53,13 +49,8 @@ export default function TabOneScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getUpdateInfo = () => {
|
const getUpdateInfo = () => {
|
||||||
const {
|
const { isEmbeddedLaunch, isEmergencyLaunch, updateId, channel, runtimeVersion } =
|
||||||
isEmbeddedLaunch,
|
Updates.useUpdates();
|
||||||
isEmergencyLaunch,
|
|
||||||
updateId,
|
|
||||||
channel,
|
|
||||||
runtimeVersion,
|
|
||||||
} = Updates.useUpdates();
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
运行模式: ${__DEV__ ? '开发模式' : '生产模式'}
|
运行模式: ${__DEV__ ? '开发模式' : '生产模式'}
|
||||||
@@ -71,6 +62,10 @@ export default function TabOneScreen() {
|
|||||||
`.trim();
|
`.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('=== TabOneScreen 组件已渲染 ===');
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text style={styles.title}>🚀 热更新演示</Text>
|
<Text style={styles.title}>🚀 热更新演示</Text>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export default function PaperDemo() {
|
|||||||
🔘 按钮
|
🔘 按钮
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.buttonRow}>
|
<View style={styles.buttonRow}>
|
||||||
<Button mode="contained" onPress={() => setSnackbarVisible(true)}>
|
<Button mode="contained" onPress={() => setSnackbarVisible(true)} style={{ marginRight: 8 }}>
|
||||||
Contained
|
Contained
|
||||||
</Button>
|
</Button>
|
||||||
<Button mode="outlined" onPress={() => setSnackbarVisible(true)}>
|
<Button mode="outlined" onPress={() => setSnackbarVisible(true)}>
|
||||||
@@ -90,14 +90,10 @@ export default function PaperDemo() {
|
|||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.buttonRow}>
|
<View style={styles.buttonRow}>
|
||||||
<Button mode="text" onPress={() => setSnackbarVisible(true)}>
|
<Button mode="text" onPress={() => setSnackbarVisible(true)} style={{ marginRight: 8 }}>
|
||||||
Text
|
Text
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button mode="elevated" onPress={() => setSnackbarVisible(true)} icon="camera">
|
||||||
mode="elevated"
|
|
||||||
onPress={() => setSnackbarVisible(true)}
|
|
||||||
icon="camera"
|
|
||||||
>
|
|
||||||
With Icon
|
With Icon
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
@@ -141,18 +137,13 @@ export default function PaperDemo() {
|
|||||||
<Switch value={switchValue} onValueChange={setSwitchValue} />
|
<Switch value={switchValue} onValueChange={setSwitchValue} />
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.chipContainer}>
|
<View style={styles.chipContainer}>
|
||||||
<Chip icon="star" onPress={() => {}}>
|
<Chip icon="star" onPress={() => {}} style={{ marginRight: 8, marginBottom: 8 }}>
|
||||||
收藏
|
收藏
|
||||||
</Chip>
|
</Chip>
|
||||||
<Chip icon="heart" mode="outlined" onPress={() => {}}>
|
<Chip icon="heart" mode="outlined" onPress={() => {}} style={{ marginRight: 8, marginBottom: 8 }}>
|
||||||
喜欢
|
喜欢
|
||||||
</Chip>
|
</Chip>
|
||||||
<Chip
|
<Chip icon="close" onPress={() => {}} onClose={() => {}} closeIcon="close-circle" style={{ marginBottom: 8 }}>
|
||||||
icon="close"
|
|
||||||
onPress={() => {}}
|
|
||||||
onClose={() => {}}
|
|
||||||
closeIcon="close-circle"
|
|
||||||
>
|
|
||||||
可关闭
|
可关闭
|
||||||
</Chip>
|
</Chip>
|
||||||
</View>
|
</View>
|
||||||
@@ -221,12 +212,7 @@ export default function PaperDemo() {
|
|||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
{/* 浮动操作按钮 */}
|
{/* 浮动操作按钮 */}
|
||||||
<FAB
|
<FAB icon="plus" style={styles.fab} onPress={() => setSnackbarVisible(true)} label="添加" />
|
||||||
icon="plus"
|
|
||||||
style={styles.fab}
|
|
||||||
onPress={() => setSnackbarVisible(true)}
|
|
||||||
label="添加"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 提示条 */}
|
{/* 提示条 */}
|
||||||
<Snackbar
|
<Snackbar
|
||||||
@@ -279,7 +265,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
buttonRow: {
|
buttonRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
gap: 8,
|
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
row: {
|
row: {
|
||||||
@@ -291,7 +276,6 @@ const styles = StyleSheet.create({
|
|||||||
chipContainer: {
|
chipContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
gap: 8,
|
|
||||||
},
|
},
|
||||||
progressBar: {
|
progressBar: {
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
@@ -306,4 +290,3 @@ const styles = StyleSheet.create({
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -56,22 +56,18 @@ export default function RootLayout() {
|
|||||||
await Updates.fetchUpdateAsync();
|
await Updates.fetchUpdateAsync();
|
||||||
|
|
||||||
// 提示用户重启应用以应用更新
|
// 提示用户重启应用以应用更新
|
||||||
Alert.alert(
|
Alert.alert('更新可用', '发现新版本,是否立即重启应用?', [
|
||||||
'更新可用',
|
{
|
||||||
'发现新版本,是否立即重启应用?',
|
text: '稍后',
|
||||||
[
|
style: 'cancel',
|
||||||
{
|
},
|
||||||
text: '稍后',
|
{
|
||||||
style: 'cancel',
|
text: '立即重启',
|
||||||
|
onPress: async () => {
|
||||||
|
await Updates.reloadAsync();
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
text: '立即重启',
|
]);
|
||||||
onPress: async () => {
|
|
||||||
await Updates.reloadAsync();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 处理更新检查错误
|
// 处理更新检查错误
|
||||||
|
|||||||
@@ -14,21 +14,24 @@ export default function EditScreenInfo({ path }: { path: string }) {
|
|||||||
<Text
|
<Text
|
||||||
style={styles.getStartedText}
|
style={styles.getStartedText}
|
||||||
lightColor="rgba(0,0,0,0.8)"
|
lightColor="rgba(0,0,0,0.8)"
|
||||||
darkColor="rgba(255,255,255,0.8)">
|
darkColor="rgba(255,255,255,0.8)"
|
||||||
|
>
|
||||||
Open up the code for this screen:
|
Open up the code for this screen:
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={[styles.codeHighlightContainer, styles.homeScreenFilename]}
|
style={[styles.codeHighlightContainer, styles.homeScreenFilename]}
|
||||||
darkColor="rgba(255,255,255,0.05)"
|
darkColor="rgba(255,255,255,0.05)"
|
||||||
lightColor="rgba(0,0,0,0.05)">
|
lightColor="rgba(0,0,0,0.05)"
|
||||||
|
>
|
||||||
<MonoText>{path}</MonoText>
|
<MonoText>{path}</MonoText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
style={styles.getStartedText}
|
style={styles.getStartedText}
|
||||||
lightColor="rgba(0,0,0,0.8)"
|
lightColor="rgba(0,0,0,0.8)"
|
||||||
darkColor="rgba(255,255,255,0.8)">
|
darkColor="rgba(255,255,255,0.8)"
|
||||||
|
>
|
||||||
Change any of the text, save the file, and your app will automatically update.
|
Change any of the text, save the file, and your app will automatically update.
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -36,7 +39,8 @@ export default function EditScreenInfo({ path }: { path: string }) {
|
|||||||
<View style={styles.helpContainer}>
|
<View style={styles.helpContainer}>
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
style={styles.helpLink}
|
style={styles.helpLink}
|
||||||
href="https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet">
|
href="https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet"
|
||||||
|
>
|
||||||
<Text style={styles.helpLinkText} lightColor={Colors.light.tint}>
|
<Text style={styles.helpLinkText} lightColor={Colors.light.tint}>
|
||||||
Tap here if your app doesn't automatically update after making changes
|
Tap here if your app doesn't automatically update after making changes
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
38
constants/network.ts
Normal file
38
constants/network.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// 请求相关
|
||||||
|
|
||||||
|
export enum NetworkTypeEnum {
|
||||||
|
ERROR = 'error',
|
||||||
|
SUCCESS = 'success',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 冻结账号相关接口
|
||||||
|
export const FREEZE_CMDID = [
|
||||||
|
'314501',
|
||||||
|
'7242031',
|
||||||
|
'621116',
|
||||||
|
'396101',
|
||||||
|
'420029',
|
||||||
|
'724209',
|
||||||
|
'621112',
|
||||||
|
'377003',
|
||||||
|
'7242026',
|
||||||
|
'390004',
|
||||||
|
'3740012',
|
||||||
|
'321543',
|
||||||
|
'310400',
|
||||||
|
'325308',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const WITHDRAWAL_CMDID = ['325308'];
|
||||||
|
|
||||||
|
export const NO_CANCEL_CMDID = ['370730']; // 不需要取消的请求集合
|
||||||
|
|
||||||
|
export const TIPS_CON = [
|
||||||
|
'请完成短信验证之后再参与',
|
||||||
|
'请填写真实姓名之后再参与',
|
||||||
|
'请完成绑定银行卡之后再参与',
|
||||||
|
'请完成生日设置之后再参与',
|
||||||
|
'请绑定虚拟货币之后再参与',
|
||||||
|
'请绑定收款方式之后再参与',
|
||||||
|
'同登录IP仅可领取一次,不可重复领取',
|
||||||
|
];
|
||||||
@@ -5,25 +5,28 @@
|
|||||||
## 📦 已安装的库列表
|
## 📦 已安装的库列表
|
||||||
|
|
||||||
### 工具类库
|
### 工具类库
|
||||||
| 库名 | 版本 | 用途 |
|
|
||||||
|------|------|------|
|
| 库名 | 版本 | 用途 |
|
||||||
| **lodash-es** | ^4.17.21 | JavaScript 工具函数库(ES modules 版本) |
|
| ------------------- | -------- | ---------------------------------------- |
|
||||||
| **dayjs** | ^1.11.19 | 轻量级日期处理库 |
|
| **lodash-es** | ^4.17.21 | JavaScript 工具函数库(ES modules 版本) |
|
||||||
| **axios** | ^1.13.1 | HTTP 请求库 |
|
| **dayjs** | ^1.11.19 | 轻量级日期处理库 |
|
||||||
| **zustand** | ^5.0.8 | 轻量级状态管理 |
|
| **axios** | ^1.13.1 | HTTP 请求库 |
|
||||||
| **react-hook-form** | ^7.66.0 | 表单处理库 |
|
| **zustand** | ^5.0.8 | 轻量级状态管理 |
|
||||||
| **zod** | ^4.1.12 | TypeScript 数据验证库 |
|
| **react-hook-form** | ^7.66.0 | 表单处理库 |
|
||||||
|
| **zod** | ^4.1.12 | TypeScript 数据验证库 |
|
||||||
|
|
||||||
### Expo 原生模块
|
### Expo 原生模块
|
||||||
| 库名 | 版本 | 用途 |
|
|
||||||
|------|------|------|
|
| 库名 | 版本 | 用途 |
|
||||||
| **@react-native-async-storage/async-storage** | ^2.2.0 | 本地存储 |
|
| --------------------------------------------- | ------- | -------------- |
|
||||||
| **expo-image** | ^3.0.10 | 优化的图片组件 |
|
| **@react-native-async-storage/async-storage** | ^2.2.0 | 本地存储 |
|
||||||
| **expo-haptics** | ^15.0.7 | 触觉反馈 |
|
| **expo-image** | ^3.0.10 | 优化的图片组件 |
|
||||||
|
| **expo-haptics** | ^15.0.7 | 触觉反馈 |
|
||||||
|
|
||||||
### 开发工具
|
### 开发工具
|
||||||
| 库名 | 版本 | 用途 |
|
|
||||||
|------|------|------|
|
| 库名 | 版本 | 用途 |
|
||||||
|
| -------------------- | -------- | ----------------------------- |
|
||||||
| **@types/lodash-es** | ^4.17.12 | Lodash-ES TypeScript 类型定义 |
|
| **@types/lodash-es** | ^4.17.12 | Lodash-ES TypeScript 类型定义 |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -39,13 +42,13 @@
|
|||||||
import { map, filter, uniq, pick, cloneDeep, debounce } from 'lodash-es';
|
import { map, filter, uniq, pick, cloneDeep, debounce } from 'lodash-es';
|
||||||
|
|
||||||
// 数组操作
|
// 数组操作
|
||||||
map([1, 2, 3], n => n * 2); // [2, 4, 6]
|
map([1, 2, 3], (n) => n * 2); // [2, 4, 6]
|
||||||
filter([1, 2, 3, 4], n => n % 2 === 0); // [2, 4]
|
filter([1, 2, 3, 4], (n) => n % 2 === 0); // [2, 4]
|
||||||
uniq([1, 2, 2, 3]); // [1, 2, 3]
|
uniq([1, 2, 2, 3]); // [1, 2, 3]
|
||||||
|
|
||||||
// 对象操作
|
// 对象操作
|
||||||
pick({ a: 1, b: 2, c: 3 }, ['a', 'b']); // { a: 1, b: 2 }
|
pick({ a: 1, b: 2, c: 3 }, ['a', 'b']); // { a: 1, b: 2 }
|
||||||
cloneDeep(obj); // 深拷贝
|
cloneDeep(obj); // 深拷贝
|
||||||
|
|
||||||
// 防抖和节流
|
// 防抖和节流
|
||||||
const handleSearch = debounce((text) => {
|
const handleSearch = debounce((text) => {
|
||||||
@@ -54,7 +57,7 @@ const handleSearch = debounce((text) => {
|
|||||||
|
|
||||||
// 也可以全量导入(不推荐,会增加包体积)
|
// 也可以全量导入(不推荐,会增加包体积)
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
_.map([1, 2, 3], n => n * 2);
|
_.map([1, 2, 3], (n) => n * 2);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Day.js - 日期处理
|
### 2. Day.js - 日期处理
|
||||||
@@ -71,12 +74,12 @@ dayjs.locale('zh-cn');
|
|||||||
dayjs().format('YYYY-MM-DD HH:mm:ss');
|
dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
||||||
// 相对时间
|
// 相对时间
|
||||||
dayjs().fromNow(); // '几秒前'
|
dayjs().fromNow(); // '几秒前'
|
||||||
dayjs().subtract(1, 'day').fromNow(); // '1天前'
|
dayjs().subtract(1, 'day').fromNow(); // '1天前'
|
||||||
|
|
||||||
// 日期操作
|
// 日期操作
|
||||||
dayjs().add(7, 'day'); // 7天后
|
dayjs().add(7, 'day'); // 7天后
|
||||||
dayjs().startOf('month'); // 本月第一天
|
dayjs().startOf('month'); // 本月第一天
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Axios - HTTP 请求
|
### 3. Axios - HTTP 请求
|
||||||
@@ -329,7 +332,7 @@ export const formatRelativeTime = (date: Date | string | number) => {
|
|||||||
export const formatChatTime = (timestamp: number) => {
|
export const formatChatTime = (timestamp: number) => {
|
||||||
const date = dayjs(timestamp);
|
const date = dayjs(timestamp);
|
||||||
const now = dayjs();
|
const now = dayjs();
|
||||||
|
|
||||||
if (date.isSame(now, 'day')) {
|
if (date.isSame(now, 'day')) {
|
||||||
return date.format('HH:mm');
|
return date.format('HH:mm');
|
||||||
} else if (date.isSame(now.subtract(1, 'day'), 'day')) {
|
} else if (date.isSame(now.subtract(1, 'day'), 'day')) {
|
||||||
@@ -384,4 +387,3 @@ export const formatChatTime = (timestamp: number) => {
|
|||||||
---
|
---
|
||||||
|
|
||||||
**提示**:所有库都已安装并配置好,可以直接在项目中使用!🎉
|
**提示**:所有库都已安装并配置好,可以直接在项目中使用!🎉
|
||||||
|
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ export default function SettingsScreen() {
|
|||||||
const theme = useSettingsStore((state) => state.theme);
|
const theme = useSettingsStore((state) => state.theme);
|
||||||
const notificationsEnabled = useSettingsStore((state) => state.notificationsEnabled);
|
const notificationsEnabled = useSettingsStore((state) => state.notificationsEnabled);
|
||||||
const hapticsEnabled = useSettingsStore((state) => state.hapticsEnabled);
|
const hapticsEnabled = useSettingsStore((state) => state.hapticsEnabled);
|
||||||
|
|
||||||
const setTheme = useSettingsStore((state) => state.setTheme);
|
const setTheme = useSettingsStore((state) => state.setTheme);
|
||||||
const setNotificationsEnabled = useSettingsStore((state) => state.setNotificationsEnabled);
|
const setNotificationsEnabled = useSettingsStore((state) => state.setNotificationsEnabled);
|
||||||
const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled);
|
const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled);
|
||||||
@@ -426,4 +426,3 @@ export default function SettingsScreen() {
|
|||||||
---
|
---
|
||||||
|
|
||||||
**提示**:这些示例都是可以直接使用的代码,复制到你的项目中即可!
|
**提示**:这些示例都是可以直接使用的代码,复制到你的项目中即可!
|
||||||
|
|
||||||
|
|||||||
19
metro.config.js
Normal file
19
metro.config.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const { getDefaultConfig } = require('expo/metro-config');
|
||||||
|
|
||||||
|
/** @type {import('expo/metro-config').MetroConfig} */
|
||||||
|
const config = getDefaultConfig(__dirname);
|
||||||
|
|
||||||
|
// 自定义 Metro 配置
|
||||||
|
config.resolver = {
|
||||||
|
...config.resolver,
|
||||||
|
// 可以在这里添加自定义解析规则
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开发服务器配置
|
||||||
|
config.server = {
|
||||||
|
...config.server,
|
||||||
|
// Metro 服务器端口(默认 8081)
|
||||||
|
port: 8081,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
14
package.json
14
package.json
@@ -6,7 +6,11 @@
|
|||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"android": "expo start --android",
|
"android": "expo start --android",
|
||||||
"ios": "expo start --ios",
|
"ios": "expo start --ios",
|
||||||
"web": "expo start --web"
|
"web": "expo start --web",
|
||||||
|
"proxy": "node scripts/proxy-server.js",
|
||||||
|
"dev": "concurrently \"npm run proxy\" \"npm run start\"",
|
||||||
|
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\"",
|
||||||
|
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
@@ -14,6 +18,7 @@
|
|||||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||||
"@react-navigation/native": "^7.1.8",
|
"@react-navigation/native": "^7.1.8",
|
||||||
"axios": "^1.13.1",
|
"axios": "^1.13.1",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"expo": "~54.0.22",
|
"expo": "~54.0.22",
|
||||||
"expo-constants": "~18.0.10",
|
"expo-constants": "~18.0.10",
|
||||||
@@ -27,6 +32,7 @@
|
|||||||
"expo-updates": "^29.0.12",
|
"expo-updates": "^29.0.12",
|
||||||
"expo-web-browser": "~15.0.9",
|
"expo-web-browser": "~15.0.9",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"md5": "^2.3.0",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-hook-form": "^7.66.0",
|
"react-hook-form": "^7.66.0",
|
||||||
@@ -41,8 +47,14 @@
|
|||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/md5": "^2.3.6",
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
|
"concurrently": "^9.2.1",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"http-proxy-middleware": "^3.0.5",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
"react-test-renderer": "19.1.0",
|
"react-test-renderer": "19.1.0",
|
||||||
"typescript": "~5.9.2"
|
"typescript": "~5.9.2"
|
||||||
},
|
},
|
||||||
|
|||||||
490
pnpm-lock.yaml
generated
490
pnpm-lock.yaml
generated
@@ -23,6 +23,9 @@ importers:
|
|||||||
axios:
|
axios:
|
||||||
specifier: ^1.13.1
|
specifier: ^1.13.1
|
||||||
version: 1.13.1
|
version: 1.13.1
|
||||||
|
crypto-js:
|
||||||
|
specifier: ^4.2.0
|
||||||
|
version: 4.2.0
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.19
|
specifier: ^1.11.19
|
||||||
version: 1.11.19
|
version: 1.11.19
|
||||||
@@ -62,6 +65,9 @@ importers:
|
|||||||
lodash-es:
|
lodash-es:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
|
md5:
|
||||||
|
specifier: ^2.3.0
|
||||||
|
version: 2.3.0
|
||||||
react:
|
react:
|
||||||
specifier: 19.1.0
|
specifier: 19.1.0
|
||||||
version: 19.1.0
|
version: 19.1.0
|
||||||
@@ -99,12 +105,30 @@ importers:
|
|||||||
specifier: ^5.0.8
|
specifier: ^5.0.8
|
||||||
version: 5.0.8(@types/react@19.1.17)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0))
|
version: 5.0.8(@types/react@19.1.17)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0))
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@types/crypto-js':
|
||||||
|
specifier: ^4.2.2
|
||||||
|
version: 4.2.2
|
||||||
'@types/lodash-es':
|
'@types/lodash-es':
|
||||||
specifier: ^4.17.12
|
specifier: ^4.17.12
|
||||||
version: 4.17.12
|
version: 4.17.12
|
||||||
|
'@types/md5':
|
||||||
|
specifier: ^2.3.6
|
||||||
|
version: 2.3.6
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ~19.1.0
|
specifier: ~19.1.0
|
||||||
version: 19.1.17
|
version: 19.1.17
|
||||||
|
concurrently:
|
||||||
|
specifier: ^9.2.1
|
||||||
|
version: 9.2.1
|
||||||
|
express:
|
||||||
|
specifier: ^5.1.0
|
||||||
|
version: 5.1.0
|
||||||
|
http-proxy-middleware:
|
||||||
|
specifier: ^3.0.5
|
||||||
|
version: 3.0.5
|
||||||
|
prettier:
|
||||||
|
specifier: ^3.6.2
|
||||||
|
version: 3.6.2
|
||||||
react-test-renderer:
|
react-test-renderer:
|
||||||
specifier: 19.1.0
|
specifier: 19.1.0
|
||||||
version: 19.1.0(react@19.1.0)
|
version: 19.1.0(react@19.1.0)
|
||||||
@@ -1192,9 +1216,15 @@ packages:
|
|||||||
'@types/babel__traverse@7.28.0':
|
'@types/babel__traverse@7.28.0':
|
||||||
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
||||||
|
|
||||||
|
'@types/crypto-js@4.2.2':
|
||||||
|
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
|
||||||
|
|
||||||
'@types/graceful-fs@4.1.9':
|
'@types/graceful-fs@4.1.9':
|
||||||
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
|
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
|
||||||
|
|
||||||
|
'@types/http-proxy@1.17.17':
|
||||||
|
resolution: {integrity: sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==}
|
||||||
|
|
||||||
'@types/istanbul-lib-coverage@2.0.6':
|
'@types/istanbul-lib-coverage@2.0.6':
|
||||||
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
|
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
|
||||||
|
|
||||||
@@ -1210,6 +1240,9 @@ packages:
|
|||||||
'@types/lodash@4.17.20':
|
'@types/lodash@4.17.20':
|
||||||
resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==}
|
resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==}
|
||||||
|
|
||||||
|
'@types/md5@2.3.6':
|
||||||
|
resolution: {integrity: sha512-WD69gNXtRBnpknfZcb4TRQ0XJQbUPZcai/Qdhmka3sxUR3Et8NrXoeAoknG/LghYHTf4ve795rInVYHBTQdNVA==}
|
||||||
|
|
||||||
'@types/node@24.10.0':
|
'@types/node@24.10.0':
|
||||||
resolution: {integrity: sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==}
|
resolution: {integrity: sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==}
|
||||||
|
|
||||||
@@ -1248,6 +1281,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
accepts@2.0.0:
|
||||||
|
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
acorn@8.15.0:
|
acorn@8.15.0:
|
||||||
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
@@ -1409,6 +1446,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
|
resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
|
body-parser@2.2.0:
|
||||||
|
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
bplist-creator@0.1.0:
|
bplist-creator@0.1.0:
|
||||||
resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==}
|
resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==}
|
||||||
|
|
||||||
@@ -1452,6 +1493,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
call-bound@1.0.4:
|
||||||
|
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
camelcase@5.3.1:
|
camelcase@5.3.1:
|
||||||
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
|
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -1471,6 +1516,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
charenc@0.0.2:
|
||||||
|
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
|
||||||
|
|
||||||
chownr@3.0.0:
|
chownr@3.0.0:
|
||||||
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
|
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -1562,13 +1610,34 @@ packages:
|
|||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||||
|
|
||||||
|
concurrently@9.2.1:
|
||||||
|
resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
connect@3.7.0:
|
connect@3.7.0:
|
||||||
resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}
|
resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}
|
||||||
engines: {node: '>= 0.10.0'}
|
engines: {node: '>= 0.10.0'}
|
||||||
|
|
||||||
|
content-disposition@1.0.0:
|
||||||
|
resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
content-type@1.0.5:
|
||||||
|
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
|
cookie-signature@1.2.2:
|
||||||
|
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
|
||||||
|
engines: {node: '>=6.6.0'}
|
||||||
|
|
||||||
|
cookie@0.7.2:
|
||||||
|
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
core-js-compat@3.46.0:
|
core-js-compat@3.46.0:
|
||||||
resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==}
|
resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==}
|
||||||
|
|
||||||
@@ -1579,6 +1648,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
crypt@0.0.2:
|
||||||
|
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
|
||||||
|
|
||||||
|
crypto-js@4.2.0:
|
||||||
|
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||||
|
|
||||||
crypto-random-string@2.0.0:
|
crypto-random-string@2.0.0:
|
||||||
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
|
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1749,6 +1824,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
eventemitter3@4.0.7:
|
||||||
|
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||||
|
|
||||||
exec-async@2.2.0:
|
exec-async@2.2.0:
|
||||||
resolution: {integrity: sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==}
|
resolution: {integrity: sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==}
|
||||||
|
|
||||||
@@ -1918,6 +1996,10 @@ packages:
|
|||||||
exponential-backoff@3.1.3:
|
exponential-backoff@3.1.3:
|
||||||
resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==}
|
resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==}
|
||||||
|
|
||||||
|
express@5.1.0:
|
||||||
|
resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
fast-deep-equal@3.1.3:
|
fast-deep-equal@3.1.3:
|
||||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||||
|
|
||||||
@@ -1945,6 +2027,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
|
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
finalhandler@2.1.0:
|
||||||
|
resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
find-up@4.1.0:
|
find-up@4.1.0:
|
||||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1976,6 +2062,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
|
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
forwarded@0.2.0:
|
||||||
|
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
freeport-async@2.0.0:
|
freeport-async@2.0.0:
|
||||||
resolution: {integrity: sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==}
|
resolution: {integrity: sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1984,6 +2074,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
fresh@2.0.0:
|
||||||
|
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
fs.realpath@1.0.0:
|
fs.realpath@1.0.0:
|
||||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||||
|
|
||||||
@@ -2085,6 +2179,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
http-proxy-middleware@3.0.5:
|
||||||
|
resolution: {integrity: sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==}
|
||||||
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
|
|
||||||
|
http-proxy@1.18.1:
|
||||||
|
resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
https-proxy-agent@7.0.6:
|
https-proxy-agent@7.0.6:
|
||||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
@@ -2092,6 +2194,14 @@ packages:
|
|||||||
hyphenate-style-name@1.1.0:
|
hyphenate-style-name@1.1.0:
|
||||||
resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==}
|
resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==}
|
||||||
|
|
||||||
|
iconv-lite@0.6.3:
|
||||||
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
iconv-lite@0.7.0:
|
||||||
|
resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
ieee754@1.2.1:
|
ieee754@1.2.1:
|
||||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
|
|
||||||
@@ -2124,9 +2234,16 @@ packages:
|
|||||||
invariant@2.2.4:
|
invariant@2.2.4:
|
||||||
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
|
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
|
||||||
|
|
||||||
|
ipaddr.js@1.9.1:
|
||||||
|
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
is-arrayish@0.3.4:
|
is-arrayish@0.3.4:
|
||||||
resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==}
|
resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==}
|
||||||
|
|
||||||
|
is-buffer@1.1.6:
|
||||||
|
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
|
||||||
|
|
||||||
is-core-module@2.16.1:
|
is-core-module@2.16.1:
|
||||||
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2136,10 +2253,18 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
is-extglob@2.1.1:
|
||||||
|
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
is-fullwidth-code-point@3.0.0:
|
is-fullwidth-code-point@3.0.0:
|
||||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
is-glob@4.0.3:
|
||||||
|
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
is-number@7.0.0:
|
is-number@7.0.0:
|
||||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||||
engines: {node: '>=0.12.0'}
|
engines: {node: '>=0.12.0'}
|
||||||
@@ -2148,6 +2273,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
|
resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
is-plain-object@5.0.0:
|
||||||
|
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
is-promise@4.0.0:
|
||||||
|
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
||||||
|
|
||||||
is-wsl@2.2.0:
|
is-wsl@2.2.0:
|
||||||
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -2358,12 +2490,23 @@ packages:
|
|||||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
md5@2.3.0:
|
||||||
|
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
|
||||||
|
|
||||||
|
media-typer@1.1.0:
|
||||||
|
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
memoize-one@5.2.1:
|
memoize-one@5.2.1:
|
||||||
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
|
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
|
||||||
|
|
||||||
memoize-one@6.0.0:
|
memoize-one@6.0.0:
|
||||||
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
|
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
|
||||||
|
|
||||||
|
merge-descriptors@2.0.0:
|
||||||
|
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
merge-options@3.0.4:
|
merge-options@3.0.4:
|
||||||
resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==}
|
resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -2503,6 +2646,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-types@3.0.1:
|
||||||
|
resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
mime@1.6.0:
|
mime@1.6.0:
|
||||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -2557,6 +2704,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==}
|
resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
negotiator@1.0.0:
|
||||||
|
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
nested-error-stacks@2.0.1:
|
nested-error-stacks@2.0.1:
|
||||||
resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==}
|
resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==}
|
||||||
|
|
||||||
@@ -2602,6 +2753,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
object-inspect@1.13.4:
|
||||||
|
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
on-finished@2.3.0:
|
on-finished@2.3.0:
|
||||||
resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
|
resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -2683,6 +2838,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||||
engines: {node: '>=16 || 14 >=14.18'}
|
engines: {node: '>=16 || 14 >=14.18'}
|
||||||
|
|
||||||
|
path-to-regexp@8.3.0:
|
||||||
|
resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
|
||||||
|
|
||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
@@ -2713,6 +2871,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
|
resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
prettier@3.6.2:
|
||||||
|
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
pretty-bytes@5.6.0:
|
pretty-bytes@5.6.0:
|
||||||
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
|
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -2739,6 +2902,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
proxy-addr@2.0.7:
|
||||||
|
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
proxy-from-env@1.1.0:
|
proxy-from-env@1.1.0:
|
||||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||||
|
|
||||||
@@ -2750,6 +2917,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==}
|
resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
qs@6.14.0:
|
||||||
|
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||||
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
query-string@7.1.3:
|
query-string@7.1.3:
|
||||||
resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==}
|
resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -2761,6 +2932,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
raw-body@3.0.1:
|
||||||
|
resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
rc@1.2.8:
|
rc@1.2.8:
|
||||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -2930,6 +3105,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==}
|
resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
|
|
||||||
|
requires-port@1.0.0:
|
||||||
|
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||||
|
|
||||||
resolve-from@5.0.0:
|
resolve-from@5.0.0:
|
||||||
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
|
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -2962,9 +3140,19 @@ packages:
|
|||||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
router@2.2.0:
|
||||||
|
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
|
rxjs@7.8.2:
|
||||||
|
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
|
||||||
|
|
||||||
safe-buffer@5.2.1:
|
safe-buffer@5.2.1:
|
||||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||||
|
|
||||||
|
safer-buffer@2.1.2:
|
||||||
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
sax@1.4.2:
|
sax@1.4.2:
|
||||||
resolution: {integrity: sha512-FySGAa0RGcFiN6zfrO9JvK1r7TB59xuzCcTHOBXBNoKgDejlOQCR2KL/FGk3/iDlsqyYg1ELZpOmlg09B01Czw==}
|
resolution: {integrity: sha512-FySGAa0RGcFiN6zfrO9JvK1r7TB59xuzCcTHOBXBNoKgDejlOQCR2KL/FGk3/iDlsqyYg1ELZpOmlg09B01Czw==}
|
||||||
|
|
||||||
@@ -2998,6 +3186,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==}
|
resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
send@1.2.0:
|
||||||
|
resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
serialize-error@2.1.0:
|
serialize-error@2.1.0:
|
||||||
resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==}
|
resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -3006,6 +3198,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
|
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
serve-static@2.2.0:
|
||||||
|
resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
server-only@0.0.1:
|
server-only@0.0.1:
|
||||||
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||||
|
|
||||||
@@ -3034,6 +3230,22 @@ packages:
|
|||||||
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
|
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
side-channel-list@1.0.0:
|
||||||
|
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
side-channel-map@1.0.1:
|
||||||
|
resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
side-channel-weakmap@1.0.2:
|
||||||
|
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
side-channel@1.1.0:
|
||||||
|
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
signal-exit@3.0.7:
|
signal-exit@3.0.7:
|
||||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||||
|
|
||||||
@@ -3207,6 +3419,10 @@ packages:
|
|||||||
tr46@0.0.3:
|
tr46@0.0.3:
|
||||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||||
|
|
||||||
|
tree-kill@1.2.2:
|
||||||
|
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
ts-interface-checker@0.1.13:
|
ts-interface-checker@0.1.13:
|
||||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||||
|
|
||||||
@@ -3225,6 +3441,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==}
|
resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
type-is@2.0.1:
|
||||||
|
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
typescript@5.9.3:
|
typescript@5.9.3:
|
||||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
@@ -4938,10 +5158,16 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.28.5
|
'@babel/types': 7.28.5
|
||||||
|
|
||||||
|
'@types/crypto-js@4.2.2': {}
|
||||||
|
|
||||||
'@types/graceful-fs@4.1.9':
|
'@types/graceful-fs@4.1.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.0
|
'@types/node': 24.10.0
|
||||||
|
|
||||||
|
'@types/http-proxy@1.17.17':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 24.10.0
|
||||||
|
|
||||||
'@types/istanbul-lib-coverage@2.0.6': {}
|
'@types/istanbul-lib-coverage@2.0.6': {}
|
||||||
|
|
||||||
'@types/istanbul-lib-report@3.0.3':
|
'@types/istanbul-lib-report@3.0.3':
|
||||||
@@ -4958,6 +5184,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/lodash@4.17.20': {}
|
'@types/lodash@4.17.20': {}
|
||||||
|
|
||||||
|
'@types/md5@2.3.6': {}
|
||||||
|
|
||||||
'@types/node@24.10.0':
|
'@types/node@24.10.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.16.0
|
undici-types: 7.16.0
|
||||||
@@ -4999,6 +5227,11 @@ snapshots:
|
|||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
negotiator: 0.6.3
|
negotiator: 0.6.3
|
||||||
|
|
||||||
|
accepts@2.0.0:
|
||||||
|
dependencies:
|
||||||
|
mime-types: 3.0.1
|
||||||
|
negotiator: 1.0.0
|
||||||
|
|
||||||
acorn@8.15.0: {}
|
acorn@8.15.0: {}
|
||||||
|
|
||||||
agent-base@7.1.4: {}
|
agent-base@7.1.4: {}
|
||||||
@@ -5056,7 +5289,7 @@ snapshots:
|
|||||||
|
|
||||||
axios@1.13.1:
|
axios@1.13.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.11
|
follow-redirects: 1.15.11(debug@4.4.3)
|
||||||
form-data: 4.0.4
|
form-data: 4.0.4
|
||||||
proxy-from-env: 1.1.0
|
proxy-from-env: 1.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -5201,6 +5434,20 @@ snapshots:
|
|||||||
|
|
||||||
big-integer@1.6.52: {}
|
big-integer@1.6.52: {}
|
||||||
|
|
||||||
|
body-parser@2.2.0:
|
||||||
|
dependencies:
|
||||||
|
bytes: 3.1.2
|
||||||
|
content-type: 1.0.5
|
||||||
|
debug: 4.4.3
|
||||||
|
http-errors: 2.0.0
|
||||||
|
iconv-lite: 0.6.3
|
||||||
|
on-finished: 2.4.1
|
||||||
|
qs: 6.14.0
|
||||||
|
raw-body: 3.0.1
|
||||||
|
type-is: 2.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
bplist-creator@0.1.0:
|
bplist-creator@0.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
stream-buffers: 2.2.0
|
stream-buffers: 2.2.0
|
||||||
@@ -5252,6 +5499,11 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
call-bound@1.0.4:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
|
||||||
camelcase@5.3.1: {}
|
camelcase@5.3.1: {}
|
||||||
|
|
||||||
camelcase@6.3.0: {}
|
camelcase@6.3.0: {}
|
||||||
@@ -5269,6 +5521,8 @@ snapshots:
|
|||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
supports-color: 7.2.0
|
supports-color: 7.2.0
|
||||||
|
|
||||||
|
charenc@0.0.2: {}
|
||||||
|
|
||||||
chownr@3.0.0: {}
|
chownr@3.0.0: {}
|
||||||
|
|
||||||
chrome-launcher@0.15.2:
|
chrome-launcher@0.15.2:
|
||||||
@@ -5368,6 +5622,15 @@ snapshots:
|
|||||||
|
|
||||||
concat-map@0.0.1: {}
|
concat-map@0.0.1: {}
|
||||||
|
|
||||||
|
concurrently@9.2.1:
|
||||||
|
dependencies:
|
||||||
|
chalk: 4.1.2
|
||||||
|
rxjs: 7.8.2
|
||||||
|
shell-quote: 1.8.3
|
||||||
|
supports-color: 8.1.1
|
||||||
|
tree-kill: 1.2.2
|
||||||
|
yargs: 17.7.2
|
||||||
|
|
||||||
connect@3.7.0:
|
connect@3.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 2.6.9
|
debug: 2.6.9
|
||||||
@@ -5377,8 +5640,18 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
content-disposition@1.0.0:
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
|
||||||
|
content-type@1.0.5: {}
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
|
cookie-signature@1.2.2: {}
|
||||||
|
|
||||||
|
cookie@0.7.2: {}
|
||||||
|
|
||||||
core-js-compat@3.46.0:
|
core-js-compat@3.46.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.27.0
|
browserslist: 4.27.0
|
||||||
@@ -5395,6 +5668,10 @@ snapshots:
|
|||||||
shebang-command: 2.0.0
|
shebang-command: 2.0.0
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
|
|
||||||
|
crypt@0.0.2: {}
|
||||||
|
|
||||||
|
crypto-js@4.2.0: {}
|
||||||
|
|
||||||
crypto-random-string@2.0.0: {}
|
crypto-random-string@2.0.0: {}
|
||||||
|
|
||||||
css-in-js-utils@3.1.0:
|
css-in-js-utils@3.1.0:
|
||||||
@@ -5504,6 +5781,8 @@ snapshots:
|
|||||||
|
|
||||||
event-target-shim@5.0.1: {}
|
event-target-shim@5.0.1: {}
|
||||||
|
|
||||||
|
eventemitter3@4.0.7: {}
|
||||||
|
|
||||||
exec-async@2.2.0: {}
|
exec-async@2.2.0: {}
|
||||||
|
|
||||||
expo-asset@12.0.9(expo@54.0.22)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
expo-asset@12.0.9(expo@54.0.22)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||||
@@ -5718,6 +5997,38 @@ snapshots:
|
|||||||
|
|
||||||
exponential-backoff@3.1.3: {}
|
exponential-backoff@3.1.3: {}
|
||||||
|
|
||||||
|
express@5.1.0:
|
||||||
|
dependencies:
|
||||||
|
accepts: 2.0.0
|
||||||
|
body-parser: 2.2.0
|
||||||
|
content-disposition: 1.0.0
|
||||||
|
content-type: 1.0.5
|
||||||
|
cookie: 0.7.2
|
||||||
|
cookie-signature: 1.2.2
|
||||||
|
debug: 4.4.3
|
||||||
|
encodeurl: 2.0.0
|
||||||
|
escape-html: 1.0.3
|
||||||
|
etag: 1.8.1
|
||||||
|
finalhandler: 2.1.0
|
||||||
|
fresh: 2.0.0
|
||||||
|
http-errors: 2.0.0
|
||||||
|
merge-descriptors: 2.0.0
|
||||||
|
mime-types: 3.0.1
|
||||||
|
on-finished: 2.4.1
|
||||||
|
once: 1.4.0
|
||||||
|
parseurl: 1.3.3
|
||||||
|
proxy-addr: 2.0.7
|
||||||
|
qs: 6.14.0
|
||||||
|
range-parser: 1.2.1
|
||||||
|
router: 2.2.0
|
||||||
|
send: 1.2.0
|
||||||
|
serve-static: 2.2.0
|
||||||
|
statuses: 2.0.1
|
||||||
|
type-is: 2.0.1
|
||||||
|
vary: 1.1.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
fast-deep-equal@3.1.3: {}
|
fast-deep-equal@3.1.3: {}
|
||||||
|
|
||||||
fast-json-stable-stringify@2.1.0: {}
|
fast-json-stable-stringify@2.1.0: {}
|
||||||
@@ -5758,6 +6069,17 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
finalhandler@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
encodeurl: 2.0.0
|
||||||
|
escape-html: 1.0.3
|
||||||
|
on-finished: 2.4.1
|
||||||
|
parseurl: 1.3.3
|
||||||
|
statuses: 2.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
find-up@4.1.0:
|
find-up@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
locate-path: 5.0.0
|
locate-path: 5.0.0
|
||||||
@@ -5770,7 +6092,9 @@ snapshots:
|
|||||||
|
|
||||||
flow-enums-runtime@0.0.6: {}
|
flow-enums-runtime@0.0.6: {}
|
||||||
|
|
||||||
follow-redirects@1.15.11: {}
|
follow-redirects@1.15.11(debug@4.4.3):
|
||||||
|
optionalDependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
|
||||||
fontfaceobserver@2.3.0: {}
|
fontfaceobserver@2.3.0: {}
|
||||||
|
|
||||||
@@ -5787,10 +6111,14 @@ snapshots:
|
|||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
|
|
||||||
|
forwarded@0.2.0: {}
|
||||||
|
|
||||||
freeport-async@2.0.0: {}
|
freeport-async@2.0.0: {}
|
||||||
|
|
||||||
fresh@0.5.2: {}
|
fresh@0.5.2: {}
|
||||||
|
|
||||||
|
fresh@2.0.0: {}
|
||||||
|
|
||||||
fs.realpath@1.0.0: {}
|
fs.realpath@1.0.0: {}
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
@@ -5894,6 +6222,25 @@ snapshots:
|
|||||||
statuses: 2.0.1
|
statuses: 2.0.1
|
||||||
toidentifier: 1.0.1
|
toidentifier: 1.0.1
|
||||||
|
|
||||||
|
http-proxy-middleware@3.0.5:
|
||||||
|
dependencies:
|
||||||
|
'@types/http-proxy': 1.17.17
|
||||||
|
debug: 4.4.3
|
||||||
|
http-proxy: 1.18.1(debug@4.4.3)
|
||||||
|
is-glob: 4.0.3
|
||||||
|
is-plain-object: 5.0.0
|
||||||
|
micromatch: 4.0.8
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
http-proxy@1.18.1(debug@4.4.3):
|
||||||
|
dependencies:
|
||||||
|
eventemitter3: 4.0.7
|
||||||
|
follow-redirects: 1.15.11(debug@4.4.3)
|
||||||
|
requires-port: 1.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
https-proxy-agent@7.0.6:
|
https-proxy-agent@7.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.4
|
agent-base: 7.1.4
|
||||||
@@ -5903,6 +6250,14 @@ snapshots:
|
|||||||
|
|
||||||
hyphenate-style-name@1.1.0: {}
|
hyphenate-style-name@1.1.0: {}
|
||||||
|
|
||||||
|
iconv-lite@0.6.3:
|
||||||
|
dependencies:
|
||||||
|
safer-buffer: 2.1.2
|
||||||
|
|
||||||
|
iconv-lite@0.7.0:
|
||||||
|
dependencies:
|
||||||
|
safer-buffer: 2.1.2
|
||||||
|
|
||||||
ieee754@1.2.1: {}
|
ieee754@1.2.1: {}
|
||||||
|
|
||||||
ignore@5.3.2: {}
|
ignore@5.3.2: {}
|
||||||
@@ -5930,20 +6285,34 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
|
|
||||||
|
ipaddr.js@1.9.1: {}
|
||||||
|
|
||||||
is-arrayish@0.3.4: {}
|
is-arrayish@0.3.4: {}
|
||||||
|
|
||||||
|
is-buffer@1.1.6: {}
|
||||||
|
|
||||||
is-core-module@2.16.1:
|
is-core-module@2.16.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
|
|
||||||
is-docker@2.2.1: {}
|
is-docker@2.2.1: {}
|
||||||
|
|
||||||
|
is-extglob@2.1.1: {}
|
||||||
|
|
||||||
is-fullwidth-code-point@3.0.0: {}
|
is-fullwidth-code-point@3.0.0: {}
|
||||||
|
|
||||||
|
is-glob@4.0.3:
|
||||||
|
dependencies:
|
||||||
|
is-extglob: 2.1.1
|
||||||
|
|
||||||
is-number@7.0.0: {}
|
is-number@7.0.0: {}
|
||||||
|
|
||||||
is-plain-obj@2.1.0: {}
|
is-plain-obj@2.1.0: {}
|
||||||
|
|
||||||
|
is-plain-object@5.0.0: {}
|
||||||
|
|
||||||
|
is-promise@4.0.0: {}
|
||||||
|
|
||||||
is-wsl@2.2.0:
|
is-wsl@2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-docker: 2.2.1
|
is-docker: 2.2.1
|
||||||
@@ -6159,10 +6528,20 @@ snapshots:
|
|||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
|
md5@2.3.0:
|
||||||
|
dependencies:
|
||||||
|
charenc: 0.0.2
|
||||||
|
crypt: 0.0.2
|
||||||
|
is-buffer: 1.1.6
|
||||||
|
|
||||||
|
media-typer@1.1.0: {}
|
||||||
|
|
||||||
memoize-one@5.2.1: {}
|
memoize-one@5.2.1: {}
|
||||||
|
|
||||||
memoize-one@6.0.0: {}
|
memoize-one@6.0.0: {}
|
||||||
|
|
||||||
|
merge-descriptors@2.0.0: {}
|
||||||
|
|
||||||
merge-options@3.0.4:
|
merge-options@3.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-plain-obj: 2.1.0
|
is-plain-obj: 2.1.0
|
||||||
@@ -6532,6 +6911,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mime-db: 1.52.0
|
mime-db: 1.52.0
|
||||||
|
|
||||||
|
mime-types@3.0.1:
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.54.0
|
||||||
|
|
||||||
mime@1.6.0: {}
|
mime@1.6.0: {}
|
||||||
|
|
||||||
mimic-fn@1.2.0: {}
|
mimic-fn@1.2.0: {}
|
||||||
@@ -6570,6 +6953,8 @@ snapshots:
|
|||||||
|
|
||||||
negotiator@0.6.4: {}
|
negotiator@0.6.4: {}
|
||||||
|
|
||||||
|
negotiator@1.0.0: {}
|
||||||
|
|
||||||
nested-error-stacks@2.0.1: {}
|
nested-error-stacks@2.0.1: {}
|
||||||
|
|
||||||
node-fetch@2.7.0:
|
node-fetch@2.7.0:
|
||||||
@@ -6603,6 +6988,8 @@ snapshots:
|
|||||||
|
|
||||||
object-assign@4.1.1: {}
|
object-assign@4.1.1: {}
|
||||||
|
|
||||||
|
object-inspect@1.13.4: {}
|
||||||
|
|
||||||
on-finished@2.3.0:
|
on-finished@2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ee-first: 1.1.1
|
ee-first: 1.1.1
|
||||||
@@ -6680,6 +7067,8 @@ snapshots:
|
|||||||
lru-cache: 10.4.3
|
lru-cache: 10.4.3
|
||||||
minipass: 7.1.2
|
minipass: 7.1.2
|
||||||
|
|
||||||
|
path-to-regexp@8.3.0: {}
|
||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
picomatch@2.3.1: {}
|
picomatch@2.3.1: {}
|
||||||
@@ -6704,6 +7093,8 @@ snapshots:
|
|||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
prettier@3.6.2: {}
|
||||||
|
|
||||||
pretty-bytes@5.6.0: {}
|
pretty-bytes@5.6.0: {}
|
||||||
|
|
||||||
pretty-format@29.7.0:
|
pretty-format@29.7.0:
|
||||||
@@ -6729,12 +7120,21 @@ snapshots:
|
|||||||
kleur: 3.0.3
|
kleur: 3.0.3
|
||||||
sisteransi: 1.0.5
|
sisteransi: 1.0.5
|
||||||
|
|
||||||
|
proxy-addr@2.0.7:
|
||||||
|
dependencies:
|
||||||
|
forwarded: 0.2.0
|
||||||
|
ipaddr.js: 1.9.1
|
||||||
|
|
||||||
proxy-from-env@1.1.0: {}
|
proxy-from-env@1.1.0: {}
|
||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
qrcode-terminal@0.11.0: {}
|
qrcode-terminal@0.11.0: {}
|
||||||
|
|
||||||
|
qs@6.14.0:
|
||||||
|
dependencies:
|
||||||
|
side-channel: 1.1.0
|
||||||
|
|
||||||
query-string@7.1.3:
|
query-string@7.1.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
decode-uri-component: 0.2.2
|
decode-uri-component: 0.2.2
|
||||||
@@ -6748,6 +7148,13 @@ snapshots:
|
|||||||
|
|
||||||
range-parser@1.2.1: {}
|
range-parser@1.2.1: {}
|
||||||
|
|
||||||
|
raw-body@3.0.1:
|
||||||
|
dependencies:
|
||||||
|
bytes: 3.1.2
|
||||||
|
http-errors: 2.0.0
|
||||||
|
iconv-lite: 0.7.0
|
||||||
|
unpipe: 1.0.0
|
||||||
|
|
||||||
rc@1.2.8:
|
rc@1.2.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-extend: 0.6.0
|
deep-extend: 0.6.0
|
||||||
@@ -6971,6 +7378,8 @@ snapshots:
|
|||||||
rc: 1.2.8
|
rc: 1.2.8
|
||||||
resolve: 1.7.1
|
resolve: 1.7.1
|
||||||
|
|
||||||
|
requires-port@1.0.0: {}
|
||||||
|
|
||||||
resolve-from@5.0.0: {}
|
resolve-from@5.0.0: {}
|
||||||
|
|
||||||
resolve-global@1.0.0:
|
resolve-global@1.0.0:
|
||||||
@@ -7000,8 +7409,24 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob: 7.2.3
|
glob: 7.2.3
|
||||||
|
|
||||||
|
router@2.2.0:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
depd: 2.0.0
|
||||||
|
is-promise: 4.0.0
|
||||||
|
parseurl: 1.3.3
|
||||||
|
path-to-regexp: 8.3.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
rxjs@7.8.2:
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
safe-buffer@5.2.1: {}
|
safe-buffer@5.2.1: {}
|
||||||
|
|
||||||
|
safer-buffer@2.1.2: {}
|
||||||
|
|
||||||
sax@1.4.2: {}
|
sax@1.4.2: {}
|
||||||
|
|
||||||
scheduler@0.26.0: {}
|
scheduler@0.26.0: {}
|
||||||
@@ -7050,6 +7475,22 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
send@1.2.0:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
encodeurl: 2.0.0
|
||||||
|
escape-html: 1.0.3
|
||||||
|
etag: 1.8.1
|
||||||
|
fresh: 2.0.0
|
||||||
|
http-errors: 2.0.0
|
||||||
|
mime-types: 3.0.1
|
||||||
|
ms: 2.1.3
|
||||||
|
on-finished: 2.4.1
|
||||||
|
range-parser: 1.2.1
|
||||||
|
statuses: 2.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
serialize-error@2.1.0: {}
|
serialize-error@2.1.0: {}
|
||||||
|
|
||||||
serve-static@1.16.2:
|
serve-static@1.16.2:
|
||||||
@@ -7061,6 +7502,15 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
serve-static@2.2.0:
|
||||||
|
dependencies:
|
||||||
|
encodeurl: 2.0.0
|
||||||
|
escape-html: 1.0.3
|
||||||
|
parseurl: 1.3.3
|
||||||
|
send: 1.2.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
server-only@0.0.1: {}
|
server-only@0.0.1: {}
|
||||||
|
|
||||||
setimmediate@1.0.5: {}
|
setimmediate@1.0.5: {}
|
||||||
@@ -7079,6 +7529,34 @@ snapshots:
|
|||||||
|
|
||||||
shell-quote@1.8.3: {}
|
shell-quote@1.8.3: {}
|
||||||
|
|
||||||
|
side-channel-list@1.0.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
object-inspect: 1.13.4
|
||||||
|
|
||||||
|
side-channel-map@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
call-bound: 1.0.4
|
||||||
|
es-errors: 1.3.0
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
object-inspect: 1.13.4
|
||||||
|
|
||||||
|
side-channel-weakmap@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
call-bound: 1.0.4
|
||||||
|
es-errors: 1.3.0
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
object-inspect: 1.13.4
|
||||||
|
side-channel-map: 1.0.1
|
||||||
|
|
||||||
|
side-channel@1.1.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
object-inspect: 1.13.4
|
||||||
|
side-channel-list: 1.0.0
|
||||||
|
side-channel-map: 1.0.1
|
||||||
|
side-channel-weakmap: 1.0.2
|
||||||
|
|
||||||
signal-exit@3.0.7: {}
|
signal-exit@3.0.7: {}
|
||||||
|
|
||||||
signal-exit@4.1.0: {}
|
signal-exit@4.1.0: {}
|
||||||
@@ -7239,6 +7717,8 @@ snapshots:
|
|||||||
|
|
||||||
tr46@0.0.3: {}
|
tr46@0.0.3: {}
|
||||||
|
|
||||||
|
tree-kill@1.2.2: {}
|
||||||
|
|
||||||
ts-interface-checker@0.1.13: {}
|
ts-interface-checker@0.1.13: {}
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
@@ -7249,6 +7729,12 @@ snapshots:
|
|||||||
|
|
||||||
type-fest@0.7.1: {}
|
type-fest@0.7.1: {}
|
||||||
|
|
||||||
|
type-is@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
content-type: 1.0.5
|
||||||
|
media-typer: 1.1.0
|
||||||
|
mime-types: 3.0.1
|
||||||
|
|
||||||
typescript@5.9.3: {}
|
typescript@5.9.3: {}
|
||||||
|
|
||||||
ua-parser-js@1.0.41: {}
|
ua-parser-js@1.0.41: {}
|
||||||
|
|||||||
59
scripts/proxy-server.js
Normal file
59
scripts/proxy-server.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* 开发环境代理服务器
|
||||||
|
* 用于解决跨域问题和统一 API 请求
|
||||||
|
*/
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = 8080;
|
||||||
|
|
||||||
|
// 目标 API 服务器地址
|
||||||
|
const API_TARGET = process.env.API_TARGET || 'http://localhost:3000';
|
||||||
|
|
||||||
|
// 配置代理
|
||||||
|
app.use(
|
||||||
|
'/api',
|
||||||
|
createProxyMiddleware({
|
||||||
|
target: API_TARGET,
|
||||||
|
changeOrigin: true,
|
||||||
|
pathRewrite: {
|
||||||
|
'^/api': '/api', // 保持路径不变,或者根据需要重写
|
||||||
|
},
|
||||||
|
onProxyReq: (proxyReq, req, res) => {
|
||||||
|
console.log(`[Proxy] ${req.method} ${req.url} → ${API_TARGET}${req.url}`);
|
||||||
|
},
|
||||||
|
onProxyRes: (proxyRes, req, res) => {
|
||||||
|
console.log(`[Proxy] ${req.method} ${req.url} ← ${proxyRes.statusCode}`);
|
||||||
|
},
|
||||||
|
onError: (err, req, res) => {
|
||||||
|
console.error('[Proxy Error]', err.message);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Proxy Error',
|
||||||
|
message: err.message,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 健康检查
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
status: 'ok',
|
||||||
|
proxy: API_TARGET,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`
|
||||||
|
╔════════════════════════════════════════════════════════╗
|
||||||
|
║ 🚀 Proxy Server Running ║
|
||||||
|
╠════════════════════════════════════════════════════════╣
|
||||||
|
║ Local: http://localhost:${PORT} ║
|
||||||
|
║ Target: ${API_TARGET.padEnd(40)} ║
|
||||||
|
║ Health: http://localhost:${PORT}/health ║
|
||||||
|
╚════════════════════════════════════════════════════════╝
|
||||||
|
`);
|
||||||
|
});
|
||||||
@@ -80,5 +80,3 @@ export function useDebounce<T extends (...args: any[]) => any>(
|
|||||||
* console.log('Searching:', text);
|
* console.log('Searching:', text);
|
||||||
* }, 500);
|
* }, 500);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -90,20 +90,20 @@ export function useHaptics() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用示例:
|
* 使用示例:
|
||||||
*
|
*
|
||||||
* function MyComponent() {
|
* function MyComponent() {
|
||||||
* const haptics = useHaptics();
|
* const haptics = useHaptics();
|
||||||
*
|
*
|
||||||
* const handlePress = () => {
|
* const handlePress = () => {
|
||||||
* haptics.light();
|
* haptics.light();
|
||||||
* // 执行其他操作
|
* // 执行其他操作
|
||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
* const handleSuccess = () => {
|
* const handleSuccess = () => {
|
||||||
* haptics.success();
|
* haptics.success();
|
||||||
* // 显示成功消息
|
* // 显示成功消息
|
||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
* return (
|
* return (
|
||||||
* <TouchableOpacity onPress={handlePress}>
|
* <TouchableOpacity onPress={handlePress}>
|
||||||
* <Text>Press me</Text>
|
* <Text>Press me</Text>
|
||||||
@@ -111,4 +111,3 @@ export function useHaptics() {
|
|||||||
* );
|
* );
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
291
src/hooks/useRequest.ts
Normal file
291
src/hooks/useRequest.ts
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
/**
|
||||||
|
* 请求 Hook
|
||||||
|
* 提供统一的请求状态管理
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import type { RequestConfig } from '@/src/utils/network/api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求状态
|
||||||
|
*/
|
||||||
|
export interface RequestState<T> {
|
||||||
|
data: T | null;
|
||||||
|
loading: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求选项
|
||||||
|
*/
|
||||||
|
export interface UseRequestOptions<T> extends RequestConfig {
|
||||||
|
/** 是否立即执行 */
|
||||||
|
immediate?: boolean;
|
||||||
|
/** 成功回调 */
|
||||||
|
onSuccess?: (data: T) => void;
|
||||||
|
/** 失败回调 */
|
||||||
|
onError?: (error: Error) => void;
|
||||||
|
/** 完成回调(无论成功失败) */
|
||||||
|
onFinally?: () => void;
|
||||||
|
/** 默认数据 */
|
||||||
|
defaultData?: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求 Hook
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data, loading, error, run, refresh } = useRequest(
|
||||||
|
* () => request.get('/api/users'),
|
||||||
|
* { immediate: true }
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useRequest<T = any>(
|
||||||
|
requestFn: () => Promise<T>,
|
||||||
|
options: UseRequestOptions<T> = {}
|
||||||
|
) {
|
||||||
|
const { immediate = false, onSuccess, onError, onFinally, defaultData = null } = options;
|
||||||
|
|
||||||
|
const [state, setState] = useState<RequestState<T>>({
|
||||||
|
data: defaultData,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const requestRef = useRef(requestFn);
|
||||||
|
requestRef.current = requestFn;
|
||||||
|
|
||||||
|
const abortControllerRef = useRef<AbortController | null>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行请求
|
||||||
|
*/
|
||||||
|
const run = useCallback(
|
||||||
|
async (...args: any[]) => {
|
||||||
|
// 取消之前的请求
|
||||||
|
if (abortControllerRef.current) {
|
||||||
|
abortControllerRef.current.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的 AbortController
|
||||||
|
abortControllerRef.current = new AbortController();
|
||||||
|
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
loading: true,
|
||||||
|
error: null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await requestRef.current();
|
||||||
|
|
||||||
|
setState({
|
||||||
|
data,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
onSuccess?.(data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
const err = error as Error;
|
||||||
|
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
loading: false,
|
||||||
|
error: err,
|
||||||
|
}));
|
||||||
|
|
||||||
|
onError?.(err);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
onFinally?.();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onSuccess, onError, onFinally]
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新(重新执行请求)
|
||||||
|
*/
|
||||||
|
const refresh = useCallback(() => {
|
||||||
|
return run();
|
||||||
|
}, [run]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置状态
|
||||||
|
*/
|
||||||
|
const reset = useCallback(() => {
|
||||||
|
setState({
|
||||||
|
data: defaultData,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
}, [defaultData]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消请求
|
||||||
|
*/
|
||||||
|
const cancel = useCallback(() => {
|
||||||
|
if (abortControllerRef.current) {
|
||||||
|
abortControllerRef.current.abort();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 立即执行
|
||||||
|
useEffect(() => {
|
||||||
|
if (immediate) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件卸载时取消请求
|
||||||
|
return () => {
|
||||||
|
cancel();
|
||||||
|
};
|
||||||
|
}, [immediate]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
run,
|
||||||
|
refresh,
|
||||||
|
reset,
|
||||||
|
cancel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页请求 Hook
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data, loading, loadMore, refresh, hasMore } = usePagination(
|
||||||
|
* (page, pageSize) => request.get('/api/users', { params: { page, pageSize } })
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function usePagination<T = any>(
|
||||||
|
requestFn: (
|
||||||
|
page: number,
|
||||||
|
pageSize: number
|
||||||
|
) => Promise<{
|
||||||
|
list: T[];
|
||||||
|
total: number;
|
||||||
|
hasMore: boolean;
|
||||||
|
}>,
|
||||||
|
options: {
|
||||||
|
pageSize?: number;
|
||||||
|
immediate?: boolean;
|
||||||
|
onSuccess?: (data: T[]) => void;
|
||||||
|
onError?: (error: Error) => void;
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
const { pageSize = 20, immediate = false, onSuccess, onError } = options;
|
||||||
|
|
||||||
|
const [state, setState] = useState({
|
||||||
|
data: [] as T[],
|
||||||
|
loading: false,
|
||||||
|
loadingMore: false,
|
||||||
|
error: null as Error | null,
|
||||||
|
page: 1,
|
||||||
|
total: 0,
|
||||||
|
hasMore: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载数据
|
||||||
|
*/
|
||||||
|
const load = useCallback(
|
||||||
|
async (page: number, append = false) => {
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
loading: !append,
|
||||||
|
loadingMore: append,
|
||||||
|
error: null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await requestFn(page, pageSize);
|
||||||
|
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
data: append ? [...prev.data, ...result.list] : result.list,
|
||||||
|
loading: false,
|
||||||
|
loadingMore: false,
|
||||||
|
page,
|
||||||
|
total: result.total,
|
||||||
|
hasMore: result.hasMore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
onSuccess?.(result.list);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
const err = error as Error;
|
||||||
|
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
loading: false,
|
||||||
|
loadingMore: false,
|
||||||
|
error: err,
|
||||||
|
}));
|
||||||
|
|
||||||
|
onError?.(err);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[requestFn, pageSize, onSuccess, onError]
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载更多
|
||||||
|
*/
|
||||||
|
const loadMore = useCallback(async () => {
|
||||||
|
if (state.loadingMore || !state.hasMore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return load(state.page + 1, true);
|
||||||
|
}, [state.loadingMore, state.hasMore, state.page, load]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新(重新加载第一页)
|
||||||
|
*/
|
||||||
|
const refresh = useCallback(async () => {
|
||||||
|
return load(1, false);
|
||||||
|
}, [load]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置
|
||||||
|
*/
|
||||||
|
const reset = useCallback(() => {
|
||||||
|
setState({
|
||||||
|
data: [],
|
||||||
|
loading: false,
|
||||||
|
loadingMore: false,
|
||||||
|
error: null,
|
||||||
|
page: 1,
|
||||||
|
total: 0,
|
||||||
|
hasMore: true,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 立即执行
|
||||||
|
useEffect(() => {
|
||||||
|
if (immediate) {
|
||||||
|
load(1, false);
|
||||||
|
}
|
||||||
|
}, [immediate]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loadMore,
|
||||||
|
refresh,
|
||||||
|
reset,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -49,13 +49,12 @@ export function useThrottle<T extends (...args: any[]) => any>(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用示例:
|
* 使用示例:
|
||||||
*
|
*
|
||||||
* const handleScroll = useThrottle((event) => {
|
* const handleScroll = useThrottle((event) => {
|
||||||
* console.log('Scrolling:', event);
|
* console.log('Scrolling:', event);
|
||||||
* }, 200);
|
* }, 200);
|
||||||
*
|
*
|
||||||
* <ScrollView onScroll={handleScroll}>
|
* <ScrollView onScroll={handleScroll}>
|
||||||
* ...
|
* ...
|
||||||
* </ScrollView>
|
* </ScrollView>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
14
src/index.ts
14
src/index.ts
@@ -3,8 +3,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
export { default as api, request } from './utils/api';
|
export {
|
||||||
|
default as api,
|
||||||
|
request,
|
||||||
|
cancelAllRequests,
|
||||||
|
cancelRequest,
|
||||||
|
createRetryRequest,
|
||||||
|
} from './utils/network/api';
|
||||||
|
export type { ApiResponse, ApiError, RequestConfig } from './utils/network/api';
|
||||||
export { default as Storage, STORAGE_KEYS } from './utils/storage';
|
export { default as Storage, STORAGE_KEYS } from './utils/storage';
|
||||||
|
export { default as config, printConfig } from './utils/config';
|
||||||
export * from './utils/date';
|
export * from './utils/date';
|
||||||
|
|
||||||
// Stores
|
// Stores
|
||||||
@@ -18,12 +26,14 @@ export * from './schemas/user';
|
|||||||
// Services
|
// Services
|
||||||
export { default as authService } from './services/authService';
|
export { default as authService } from './services/authService';
|
||||||
export { default as userService } from './services/userService';
|
export { default as userService } from './services/userService';
|
||||||
|
export { default as appService } from './services/appService';
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
export * from './hooks/useDebounce';
|
export * from './hooks/useDebounce';
|
||||||
export * from './hooks/useThrottle';
|
export * from './hooks/useThrottle';
|
||||||
export * from './hooks/useHaptics';
|
export * from './hooks/useHaptics';
|
||||||
|
export * from './hooks/useRequest';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
export * from './types/api';
|
||||||
|
|||||||
@@ -8,14 +8,8 @@ import { z } from 'zod';
|
|||||||
* 登录表单 Schema
|
* 登录表单 Schema
|
||||||
*/
|
*/
|
||||||
export const loginSchema = z.object({
|
export const loginSchema = z.object({
|
||||||
email: z
|
email: z.string().min(1, '请输入邮箱').email('请输入有效的邮箱地址'),
|
||||||
.string()
|
password: z.string().min(6, '密码至少6个字符').max(20, '密码最多20个字符'),
|
||||||
.min(1, '请输入邮箱')
|
|
||||||
.email('请输入有效的邮箱地址'),
|
|
||||||
password: z
|
|
||||||
.string()
|
|
||||||
.min(6, '密码至少6个字符')
|
|
||||||
.max(20, '密码最多20个字符'),
|
|
||||||
rememberMe: z.boolean().optional(),
|
rememberMe: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -29,10 +23,7 @@ export const registerSchema = z
|
|||||||
.min(3, '用户名至少3个字符')
|
.min(3, '用户名至少3个字符')
|
||||||
.max(20, '用户名最多20个字符')
|
.max(20, '用户名最多20个字符')
|
||||||
.regex(/^[a-zA-Z0-9_]+$/, '用户名只能包含字母、数字和下划线'),
|
.regex(/^[a-zA-Z0-9_]+$/, '用户名只能包含字母、数字和下划线'),
|
||||||
email: z
|
email: z.string().min(1, '请输入邮箱').email('请输入有效的邮箱地址'),
|
||||||
.string()
|
|
||||||
.min(1, '请输入邮箱')
|
|
||||||
.email('请输入有效的邮箱地址'),
|
|
||||||
password: z
|
password: z
|
||||||
.string()
|
.string()
|
||||||
.min(6, '密码至少6个字符')
|
.min(6, '密码至少6个字符')
|
||||||
@@ -54,10 +45,7 @@ export const registerSchema = z
|
|||||||
* 忘记密码 Schema
|
* 忘记密码 Schema
|
||||||
*/
|
*/
|
||||||
export const forgotPasswordSchema = z.object({
|
export const forgotPasswordSchema = z.object({
|
||||||
email: z
|
email: z.string().min(1, '请输入邮箱').email('请输入有效的邮箱地址'),
|
||||||
.string()
|
|
||||||
.min(1, '请输入邮箱')
|
|
||||||
.email('请输入有效的邮箱地址'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,10 +58,7 @@ export const resetPasswordSchema = z
|
|||||||
.min(6, '验证码为6位')
|
.min(6, '验证码为6位')
|
||||||
.max(6, '验证码为6位')
|
.max(6, '验证码为6位')
|
||||||
.regex(/^\d{6}$/, '验证码必须是6位数字'),
|
.regex(/^\d{6}$/, '验证码必须是6位数字'),
|
||||||
password: z
|
password: z.string().min(6, '密码至少6个字符').max(20, '密码最多20个字符'),
|
||||||
.string()
|
|
||||||
.min(6, '密码至少6个字符')
|
|
||||||
.max(20, '密码最多20个字符'),
|
|
||||||
confirmPassword: z.string().min(1, '请确认密码'),
|
confirmPassword: z.string().min(1, '请确认密码'),
|
||||||
})
|
})
|
||||||
.refine((data) => data.password === data.confirmPassword, {
|
.refine((data) => data.password === data.confirmPassword, {
|
||||||
@@ -87,10 +72,7 @@ export const resetPasswordSchema = z
|
|||||||
export const changePasswordSchema = z
|
export const changePasswordSchema = z
|
||||||
.object({
|
.object({
|
||||||
oldPassword: z.string().min(1, '请输入当前密码'),
|
oldPassword: z.string().min(1, '请输入当前密码'),
|
||||||
newPassword: z
|
newPassword: z.string().min(6, '新密码至少6个字符').max(20, '新密码最多20个字符'),
|
||||||
.string()
|
|
||||||
.min(6, '新密码至少6个字符')
|
|
||||||
.max(20, '新密码最多20个字符'),
|
|
||||||
confirmPassword: z.string().min(1, '请确认新密码'),
|
confirmPassword: z.string().min(1, '请确认新密码'),
|
||||||
})
|
})
|
||||||
.refine((data) => data.newPassword === data.confirmPassword, {
|
.refine((data) => data.newPassword === data.confirmPassword, {
|
||||||
@@ -127,4 +109,3 @@ export type ForgotPasswordFormData = z.infer<typeof forgotPasswordSchema>;
|
|||||||
export type ResetPasswordFormData = z.infer<typeof resetPasswordSchema>;
|
export type ResetPasswordFormData = z.infer<typeof resetPasswordSchema>;
|
||||||
export type ChangePasswordFormData = z.infer<typeof changePasswordSchema>;
|
export type ChangePasswordFormData = z.infer<typeof changePasswordSchema>;
|
||||||
export type PhoneLoginFormData = z.infer<typeof phoneLoginSchema>;
|
export type PhoneLoginFormData = z.infer<typeof phoneLoginSchema>;
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,7 @@ export const userSchema = z.object({
|
|||||||
* 更新用户资料 Schema
|
* 更新用户资料 Schema
|
||||||
*/
|
*/
|
||||||
export const updateProfileSchema = z.object({
|
export const updateProfileSchema = z.object({
|
||||||
nickname: z
|
nickname: z.string().min(2, '昵称至少2个字符').max(20, '昵称最多20个字符').optional(),
|
||||||
.string()
|
|
||||||
.min(2, '昵称至少2个字符')
|
|
||||||
.max(20, '昵称最多20个字符')
|
|
||||||
.optional(),
|
|
||||||
avatar: z.string().url('请输入有效的头像URL').optional(),
|
avatar: z.string().url('请输入有效的头像URL').optional(),
|
||||||
phone: z
|
phone: z
|
||||||
.string()
|
.string()
|
||||||
@@ -55,10 +51,7 @@ export const bindPhoneSchema = z.object({
|
|||||||
* 绑定邮箱 Schema
|
* 绑定邮箱 Schema
|
||||||
*/
|
*/
|
||||||
export const bindEmailSchema = z.object({
|
export const bindEmailSchema = z.object({
|
||||||
email: z
|
email: z.string().min(1, '请输入邮箱').email('请输入有效的邮箱地址'),
|
||||||
.string()
|
|
||||||
.min(1, '请输入邮箱')
|
|
||||||
.email('请输入有效的邮箱地址'),
|
|
||||||
code: z
|
code: z
|
||||||
.string()
|
.string()
|
||||||
.min(6, '验证码为6位')
|
.min(6, '验证码为6位')
|
||||||
@@ -73,4 +66,3 @@ export type User = z.infer<typeof userSchema>;
|
|||||||
export type UpdateProfileFormData = z.infer<typeof updateProfileSchema>;
|
export type UpdateProfileFormData = z.infer<typeof updateProfileSchema>;
|
||||||
export type BindPhoneFormData = z.infer<typeof bindPhoneSchema>;
|
export type BindPhoneFormData = z.infer<typeof bindPhoneSchema>;
|
||||||
export type BindEmailFormData = z.infer<typeof bindEmailSchema>;
|
export type BindEmailFormData = z.infer<typeof bindEmailSchema>;
|
||||||
|
|
||||||
|
|||||||
39
src/services/appService.ts
Normal file
39
src/services/appService.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 基础服务
|
||||||
|
* 处理应用相关的 API 请求
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { request } from '@/src/utils/network/api';
|
||||||
|
import type { User, UpdateProfileFormData } from '@/src/schemas/user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 响应接口
|
||||||
|
*/
|
||||||
|
interface ApiResponse<T = any> {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户服务类
|
||||||
|
*/
|
||||||
|
class AppService {
|
||||||
|
/**
|
||||||
|
* 获取当前用户信息
|
||||||
|
*/
|
||||||
|
getPlatformData(data?: Record<string, any>): Promise<any> {
|
||||||
|
return request.post('/v2', data, {
|
||||||
|
headers: {
|
||||||
|
cmdId: 371130,
|
||||||
|
headerType: 1,
|
||||||
|
apiName: 'getPlatformData',
|
||||||
|
tid: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出单例
|
||||||
|
export const appService = new AppService();
|
||||||
|
export default appService;
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
* 处理登录、注册等认证相关的 API 请求
|
* 处理登录、注册等认证相关的 API 请求
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { request } from '@/src/utils/api';
|
import { request } from '@/src/utils/network/api';
|
||||||
import type {
|
import type {
|
||||||
LoginFormData,
|
LoginFormData,
|
||||||
RegisterFormData,
|
RegisterFormData,
|
||||||
@@ -40,10 +40,7 @@ class AuthService {
|
|||||||
* 邮箱登录
|
* 邮箱登录
|
||||||
*/
|
*/
|
||||||
async login(data: LoginFormData): Promise<LoginResponse> {
|
async login(data: LoginFormData): Promise<LoginResponse> {
|
||||||
const response = await request.post<ApiResponse<LoginResponse>>(
|
const response = await request.post<ApiResponse<LoginResponse>>('/auth/login', data);
|
||||||
'/auth/login',
|
|
||||||
data
|
|
||||||
);
|
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,10 +48,7 @@ class AuthService {
|
|||||||
* 手机号登录
|
* 手机号登录
|
||||||
*/
|
*/
|
||||||
async phoneLogin(data: PhoneLoginFormData): Promise<LoginResponse> {
|
async phoneLogin(data: PhoneLoginFormData): Promise<LoginResponse> {
|
||||||
const response = await request.post<ApiResponse<LoginResponse>>(
|
const response = await request.post<ApiResponse<LoginResponse>>('/auth/phone-login', data);
|
||||||
'/auth/phone-login',
|
|
||||||
data
|
|
||||||
);
|
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,10 +56,7 @@ class AuthService {
|
|||||||
* 注册
|
* 注册
|
||||||
*/
|
*/
|
||||||
async register(data: RegisterFormData): Promise<LoginResponse> {
|
async register(data: RegisterFormData): Promise<LoginResponse> {
|
||||||
const response = await request.post<ApiResponse<LoginResponse>>(
|
const response = await request.post<ApiResponse<LoginResponse>>('/auth/register', data);
|
||||||
'/auth/register',
|
|
||||||
data
|
|
||||||
);
|
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,10 +99,9 @@ class AuthService {
|
|||||||
* 刷新 token
|
* 刷新 token
|
||||||
*/
|
*/
|
||||||
async refreshToken(refreshToken: string): Promise<{ token: string }> {
|
async refreshToken(refreshToken: string): Promise<{ token: string }> {
|
||||||
const response = await request.post<ApiResponse<{ token: string }>>(
|
const response = await request.post<ApiResponse<{ token: string }>>('/auth/refresh-token', {
|
||||||
'/auth/refresh-token',
|
refreshToken,
|
||||||
{ refreshToken }
|
});
|
||||||
);
|
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,4 +121,3 @@ class AuthService {
|
|||||||
// 导出单例
|
// 导出单例
|
||||||
export const authService = new AuthService();
|
export const authService = new AuthService();
|
||||||
export default authService;
|
export default authService;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* 处理用户信息相关的 API 请求
|
* 处理用户信息相关的 API 请求
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { request } from '@/src/utils/api';
|
import { request } from '@/src/utils/network/api';
|
||||||
import type { User, UpdateProfileFormData } from '@/src/schemas/user';
|
import type { User, UpdateProfileFormData } from '@/src/schemas/user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,15 +50,11 @@ class UserService {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('avatar', file);
|
formData.append('avatar', file);
|
||||||
|
|
||||||
const response = await request.post<ApiResponse<{ url: string }>>(
|
const response = await request.post<ApiResponse<{ url: string }>>('/user/avatar', formData, {
|
||||||
'/user/avatar',
|
headers: {
|
||||||
formData,
|
'Content-Type': 'multipart/form-data',
|
||||||
{
|
},
|
||||||
headers: {
|
});
|
||||||
'Content-Type': 'multipart/form-data',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,4 +83,3 @@ class UserService {
|
|||||||
// 导出单例
|
// 导出单例
|
||||||
export const userService = new UserService();
|
export const userService = new UserService();
|
||||||
export default userService;
|
export default userService;
|
||||||
|
|
||||||
|
|||||||
@@ -127,12 +127,10 @@ export const useNotificationsEnabled = () =>
|
|||||||
useSettingsStore((state) => state.notificationsEnabled);
|
useSettingsStore((state) => state.notificationsEnabled);
|
||||||
|
|
||||||
// 获取声音状态
|
// 获取声音状态
|
||||||
export const useSoundEnabled = () =>
|
export const useSoundEnabled = () => useSettingsStore((state) => state.soundEnabled);
|
||||||
useSettingsStore((state) => state.soundEnabled);
|
|
||||||
|
|
||||||
// 获取触觉反馈状态
|
// 获取触觉反馈状态
|
||||||
export const useHapticsEnabled = () =>
|
export const useHapticsEnabled = () => useSettingsStore((state) => state.hapticsEnabled);
|
||||||
useSettingsStore((state) => state.hapticsEnabled);
|
|
||||||
|
|
||||||
// 获取设置操作方法
|
// 获取设置操作方法
|
||||||
export const useSettingsActions = () =>
|
export const useSettingsActions = () =>
|
||||||
@@ -144,4 +142,3 @@ export const useSettingsActions = () =>
|
|||||||
setHapticsEnabled: state.setHapticsEnabled,
|
setHapticsEnabled: state.setHapticsEnabled,
|
||||||
resetSettings: state.resetSettings,
|
resetSettings: state.resetSettings,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -139,4 +139,3 @@ export const useUserActions = () =>
|
|||||||
logout: state.logout,
|
logout: state.logout,
|
||||||
updateUser: state.updateUser,
|
updateUser: state.updateUser,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
73
src/types/api.ts
Normal file
73
src/types/api.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* API 相关类型定义
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页请求参数
|
||||||
|
*/
|
||||||
|
export interface PaginationParams {
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
sortBy?: string;
|
||||||
|
sortOrder?: 'asc' | 'desc';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页响应数据
|
||||||
|
*/
|
||||||
|
export interface PaginationResponse<T> {
|
||||||
|
list: T[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
totalPages: number;
|
||||||
|
hasMore: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表响应数据
|
||||||
|
*/
|
||||||
|
export interface ListResponse<T> {
|
||||||
|
items: T[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID 参数
|
||||||
|
*/
|
||||||
|
export interface IdParams {
|
||||||
|
id: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量操作参数
|
||||||
|
*/
|
||||||
|
export interface BatchParams {
|
||||||
|
ids: (string | number)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索参数
|
||||||
|
*/
|
||||||
|
export interface SearchParams {
|
||||||
|
keyword: string;
|
||||||
|
filters?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传响应
|
||||||
|
*/
|
||||||
|
export interface UploadResponse {
|
||||||
|
url: string;
|
||||||
|
filename: string;
|
||||||
|
size: number;
|
||||||
|
mimeType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用操作响应
|
||||||
|
*/
|
||||||
|
export interface OperationResponse {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
@@ -78,4 +78,3 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
||||||
|
|||||||
144
src/utils/api.ts
144
src/utils/api.ts
@@ -1,144 +0,0 @@
|
|||||||
/**
|
|
||||||
* Axios API 配置
|
|
||||||
* 统一管理 HTTP 请求
|
|
||||||
*/
|
|
||||||
|
|
||||||
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
||||||
|
|
||||||
// API 基础配置
|
|
||||||
const API_CONFIG = {
|
|
||||||
baseURL: process.env.EXPO_PUBLIC_API_URL || 'https://api.example.com',
|
|
||||||
timeout: 10000,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建 axios 实例
|
|
||||||
const api = axios.create(API_CONFIG);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 请求拦截器
|
|
||||||
* 在请求发送前添加 token 等信息
|
|
||||||
*/
|
|
||||||
api.interceptors.request.use(
|
|
||||||
async (config) => {
|
|
||||||
try {
|
|
||||||
// 从本地存储获取 token
|
|
||||||
const token = await AsyncStorage.getItem('auth_token');
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
config.headers.Authorization = `Bearer ${token}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打印请求信息(开发环境)
|
|
||||||
if (__DEV__) {
|
|
||||||
console.log('📤 API Request:', {
|
|
||||||
method: config.method?.toUpperCase(),
|
|
||||||
url: config.url,
|
|
||||||
data: config.data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Request interceptor error:', error);
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.error('Request error:', error);
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 响应拦截器
|
|
||||||
* 统一处理响应和错误
|
|
||||||
*/
|
|
||||||
api.interceptors.response.use(
|
|
||||||
(response: AxiosResponse) => {
|
|
||||||
// 打印响应信息(开发环境)
|
|
||||||
if (__DEV__) {
|
|
||||||
console.log('📥 API Response:', {
|
|
||||||
url: response.config.url,
|
|
||||||
status: response.status,
|
|
||||||
data: response.data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回响应数据
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
async (error: AxiosError) => {
|
|
||||||
// 打印错误信息
|
|
||||||
console.error('❌ API Error:', {
|
|
||||||
url: error.config?.url,
|
|
||||||
status: error.response?.status,
|
|
||||||
message: error.message,
|
|
||||||
data: error.response?.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 处理不同的错误状态码
|
|
||||||
if (error.response) {
|
|
||||||
switch (error.response.status) {
|
|
||||||
case 401:
|
|
||||||
// 未授权,清除 token 并跳转到登录页
|
|
||||||
await AsyncStorage.removeItem('auth_token');
|
|
||||||
// TODO: 导航到登录页
|
|
||||||
// router.replace('/login');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 403:
|
|
||||||
// 禁止访问
|
|
||||||
console.error('Access forbidden');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 404:
|
|
||||||
// 资源不存在
|
|
||||||
console.error('Resource not found');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 500:
|
|
||||||
// 服务器错误
|
|
||||||
console.error('Server error');
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.error('Unknown error');
|
|
||||||
}
|
|
||||||
} else if (error.request) {
|
|
||||||
// 请求已发送但没有收到响应
|
|
||||||
console.error('No response received');
|
|
||||||
} else {
|
|
||||||
// 请求配置出错
|
|
||||||
console.error('Request configuration error');
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用请求方法
|
|
||||||
*/
|
|
||||||
export const request = {
|
|
||||||
get: <T = any>(url: string, config?: AxiosRequestConfig) =>
|
|
||||||
api.get<T, T>(url, config),
|
|
||||||
|
|
||||||
post: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) =>
|
|
||||||
api.post<T, T>(url, data, config),
|
|
||||||
|
|
||||||
put: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) =>
|
|
||||||
api.put<T, T>(url, data, config),
|
|
||||||
|
|
||||||
delete: <T = any>(url: string, config?: AxiosRequestConfig) =>
|
|
||||||
api.delete<T, T>(url, config),
|
|
||||||
|
|
||||||
patch: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) =>
|
|
||||||
api.patch<T, T>(url, data, config),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default api;
|
|
||||||
|
|
||||||
0
src/utils/common.ts
Normal file
0
src/utils/common.ts
Normal file
130
src/utils/config.ts
Normal file
130
src/utils/config.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* 应用配置工具
|
||||||
|
* 统一管理环境变量和配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Constants from 'expo-constants';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环境类型
|
||||||
|
*/
|
||||||
|
export type Environment = 'development' | 'staging' | 'production';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前环境
|
||||||
|
*/
|
||||||
|
export const getEnvironment = (): Environment => {
|
||||||
|
if (__DEV__) {
|
||||||
|
return 'development';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可以通过环境变量或其他方式判断 staging 环境
|
||||||
|
const env = process.env.EXPO_PUBLIC_ENV;
|
||||||
|
if (env === 'staging') {
|
||||||
|
return 'staging';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'production';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 API 基础 URL
|
||||||
|
*/
|
||||||
|
export const getApiBaseUrl = (): string => {
|
||||||
|
// 1. 优先使用环境变量
|
||||||
|
const envApiUrl = process.env.EXPO_PUBLIC_API_URL;
|
||||||
|
if (envApiUrl) {
|
||||||
|
return envApiUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 根据环境返回不同的 URL
|
||||||
|
const env = getEnvironment();
|
||||||
|
|
||||||
|
switch (env) {
|
||||||
|
case 'development':
|
||||||
|
// 开发环境
|
||||||
|
if (Platform.OS === 'web') {
|
||||||
|
// Web 平台使用相对路径(会被 webpack devServer 代理)
|
||||||
|
return '/api';
|
||||||
|
} else {
|
||||||
|
// iOS/Android 使用本机 IP
|
||||||
|
// ⚠️ 重要:需要替换为你的本机 IP 地址
|
||||||
|
// 查看本机 IP:
|
||||||
|
// - Windows: ipconfig
|
||||||
|
// - Mac/Linux: ifconfig
|
||||||
|
// - 或者使用 Metro Bundler 显示的 IP
|
||||||
|
return 'http://192.168.1.100:3000/api';
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'staging':
|
||||||
|
// 预发布环境
|
||||||
|
return 'https://staging-api.yourdomain.com/api';
|
||||||
|
|
||||||
|
case 'production':
|
||||||
|
// 生产环境
|
||||||
|
return 'https://api.yourdomain.com/api';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return '/api';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 API 超时时间
|
||||||
|
*/
|
||||||
|
export const getApiTimeout = (): number => {
|
||||||
|
const timeout = process.env.EXPO_PUBLIC_API_TIMEOUT;
|
||||||
|
return timeout ? Number(timeout) : 10000;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用配置
|
||||||
|
*/
|
||||||
|
export const config = {
|
||||||
|
// 环境
|
||||||
|
env: getEnvironment(),
|
||||||
|
isDev: __DEV__,
|
||||||
|
|
||||||
|
// API 配置
|
||||||
|
api: {
|
||||||
|
baseURL: getApiBaseUrl(),
|
||||||
|
timeout: getApiTimeout(),
|
||||||
|
},
|
||||||
|
|
||||||
|
// 应用信息
|
||||||
|
app: {
|
||||||
|
name: process.env.EXPO_PUBLIC_APP_NAME || 'RN Demo',
|
||||||
|
version: process.env.EXPO_PUBLIC_APP_VERSION || '1.0.0',
|
||||||
|
bundleId: Constants.expoConfig?.ios?.bundleIdentifier || '',
|
||||||
|
packageName: Constants.expoConfig?.android?.package || '',
|
||||||
|
vk: 'fT6phq0wkOPRlAoyToidAnkogUV7ttGo',
|
||||||
|
nc: 1,
|
||||||
|
aseqId: '7',
|
||||||
|
},
|
||||||
|
|
||||||
|
// 平台信息
|
||||||
|
platform: {
|
||||||
|
os: Platform.OS,
|
||||||
|
version: Platform.Version,
|
||||||
|
isWeb: Platform.OS === 'web',
|
||||||
|
isIOS: Platform.OS === 'ios',
|
||||||
|
isAndroid: Platform.OS === 'android',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印配置信息(仅开发环境)
|
||||||
|
*/
|
||||||
|
export const printConfig = () => {
|
||||||
|
if (__DEV__) {
|
||||||
|
console.log('📋 App Configuration:', {
|
||||||
|
environment: config.env,
|
||||||
|
apiBaseURL: config.api.baseURL,
|
||||||
|
platform: config.platform.os,
|
||||||
|
version: config.app.version,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -216,4 +216,3 @@ export const nowInSeconds = (): number => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default dayjs;
|
export default dayjs;
|
||||||
|
|
||||||
|
|||||||
589
src/utils/network/api.ts
Normal file
589
src/utils/network/api.ts
Normal file
@@ -0,0 +1,589 @@
|
|||||||
|
/**
|
||||||
|
* Axios API 配置
|
||||||
|
* 统一管理 HTTP 请求
|
||||||
|
*
|
||||||
|
* 功能特性:
|
||||||
|
* - 自动添加 Token
|
||||||
|
* - Token 自动刷新
|
||||||
|
* - 请求重试机制
|
||||||
|
* - 请求取消功能
|
||||||
|
* - 统一错误处理
|
||||||
|
* - 请求/响应日志
|
||||||
|
* - Loading 状态管理
|
||||||
|
*/
|
||||||
|
|
||||||
|
import axios, {
|
||||||
|
AxiosError,
|
||||||
|
AxiosRequestConfig,
|
||||||
|
AxiosResponse,
|
||||||
|
InternalAxiosRequestConfig,
|
||||||
|
CancelTokenSource,
|
||||||
|
AxiosRequestHeaders,
|
||||||
|
} from 'axios';
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
import { router } from 'expo-router';
|
||||||
|
import { config } from '../config';
|
||||||
|
import { transformRequest, parseResponse } from './helper';
|
||||||
|
import { cloneDeep, pick } from 'lodash-es';
|
||||||
|
import md5 from 'md5';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 响应数据结构
|
||||||
|
*/
|
||||||
|
export interface ApiResponse<T = any> {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: T;
|
||||||
|
success?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 错误响应
|
||||||
|
*/
|
||||||
|
export interface ApiError {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
errors?: Record<string, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求配置扩展
|
||||||
|
*/
|
||||||
|
export interface RequestConfig extends AxiosRequestConfig {
|
||||||
|
/** 是否显示 loading */
|
||||||
|
showLoading?: boolean;
|
||||||
|
/** 是否显示错误提示 */
|
||||||
|
showError?: boolean;
|
||||||
|
/** 是否重试 */
|
||||||
|
retry?: boolean;
|
||||||
|
/** 重试次数 */
|
||||||
|
retryCount?: number;
|
||||||
|
/** 是否需要 token */
|
||||||
|
requiresAuth?: boolean;
|
||||||
|
/** 自定义错误处理 */
|
||||||
|
customErrorHandler?: (error: AxiosError<ApiError>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// API 基础配置
|
||||||
|
const API_CONFIG = {
|
||||||
|
baseURL: config.api.baseURL,
|
||||||
|
timeout: config.api.timeout,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
|
Accept: 'application/json, application/xml, text/play, text/html, *.*',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建 axios 实例
|
||||||
|
const api = axios.create(API_CONFIG);
|
||||||
|
|
||||||
|
// 请求队列(用于取消请求)
|
||||||
|
const pendingRequests = new Map<string, CancelTokenSource>();
|
||||||
|
|
||||||
|
// 是否正在刷新 token
|
||||||
|
let isRefreshing = false;
|
||||||
|
|
||||||
|
// 刷新 token 时的请求队列
|
||||||
|
let refreshSubscribers: Array<(token: string) => void> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成请求唯一标识
|
||||||
|
*/
|
||||||
|
function generateRequestKey(config: InternalAxiosRequestConfig): string {
|
||||||
|
const cmdId = config.headers.cmdId || config.url;
|
||||||
|
const data = cloneDeep(config.method === 'post' ? config.data : config.params);
|
||||||
|
return `${cmdId}&${data ? md5(JSON.stringify(data)) : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加请求到队列
|
||||||
|
*/
|
||||||
|
function addPendingRequest(config: InternalAxiosRequestConfig): void {
|
||||||
|
const requestKey = generateRequestKey(config);
|
||||||
|
|
||||||
|
// 如果已存在相同请求,取消之前的请求
|
||||||
|
if (pendingRequests.has(requestKey)) {
|
||||||
|
const source = pendingRequests.get(requestKey);
|
||||||
|
source?.cancel('重复请求已取消');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的取消令牌
|
||||||
|
const source = axios.CancelToken.source();
|
||||||
|
config.cancelToken = source.token;
|
||||||
|
pendingRequests.set(requestKey, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从队列中移除请求
|
||||||
|
*/
|
||||||
|
function removePendingRequest(config: InternalAxiosRequestConfig | AxiosRequestConfig): void {
|
||||||
|
const requestKey = generateRequestKey(config as InternalAxiosRequestConfig);
|
||||||
|
pendingRequests.delete(requestKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅 token 刷新
|
||||||
|
*/
|
||||||
|
function subscribeTokenRefresh(callback: (token: string) => void): void {
|
||||||
|
refreshSubscribers.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知所有订阅者 token 已刷新
|
||||||
|
*/
|
||||||
|
function onTokenRefreshed(token: string): void {
|
||||||
|
refreshSubscribers.forEach((callback) => callback(token));
|
||||||
|
refreshSubscribers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新 token
|
||||||
|
*/
|
||||||
|
async function refreshAccessToken(): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const refreshToken = await AsyncStorage.getItem('refresh_token');
|
||||||
|
|
||||||
|
if (!refreshToken) {
|
||||||
|
throw new Error('No refresh token');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用刷新 token 接口
|
||||||
|
const response = await axios.post<ApiResponse<{ token: string; refreshToken: string }>>(
|
||||||
|
`${config.api.baseURL}/auth/refresh-token`,
|
||||||
|
{ refreshToken }
|
||||||
|
);
|
||||||
|
|
||||||
|
const { token, refreshToken: newRefreshToken } = response.data.data;
|
||||||
|
|
||||||
|
// 保存新的 token
|
||||||
|
await AsyncStorage.setItem('auth_token', token);
|
||||||
|
await AsyncStorage.setItem('refresh_token', newRefreshToken);
|
||||||
|
|
||||||
|
return token;
|
||||||
|
} catch (error) {
|
||||||
|
// 刷新失败,清除所有 token
|
||||||
|
await AsyncStorage.multiRemove(['auth_token', 'refresh_token']);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求拦截器
|
||||||
|
* 在请求发送前添加 token 等信息
|
||||||
|
*/
|
||||||
|
api.interceptors.request.use(
|
||||||
|
async (config: InternalAxiosRequestConfig) => {
|
||||||
|
try {
|
||||||
|
// 添加到请求队列(防止重复请求)
|
||||||
|
addPendingRequest(config);
|
||||||
|
|
||||||
|
const { apiName } = config.headers;
|
||||||
|
|
||||||
|
const { headers, data } = transformRequest(pick(config, ['headers', 'data']));
|
||||||
|
|
||||||
|
config.headers = {
|
||||||
|
...headers,
|
||||||
|
...(__DEV__ ? { apiName } : {}),
|
||||||
|
} as AxiosRequestHeaders;
|
||||||
|
|
||||||
|
config.data = data;
|
||||||
|
|
||||||
|
if (Number(config.headers.cmdId) !== 381120) {
|
||||||
|
config.url = '/v2/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__DEV__ && apiName) {
|
||||||
|
config.url = `${config.url}?${apiName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // 从本地存储获取 token
|
||||||
|
// const token = await AsyncStorage.getItem('auth_token');
|
||||||
|
//
|
||||||
|
// // 添加 token 到请求头
|
||||||
|
// if (token && config.headers) {
|
||||||
|
// config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 添加请求时间戳(用于计算请求耗时)
|
||||||
|
(config as any).metadata = { startTime: Date.now() };
|
||||||
|
|
||||||
|
// 打印请求信息(开发环境)
|
||||||
|
// if (__DEV__) {
|
||||||
|
// console.log('📤 API Request:', {
|
||||||
|
// method: config.method?.toUpperCase(),
|
||||||
|
// url: config.url,
|
||||||
|
// baseURL: config.baseURL,
|
||||||
|
// params: config.params,
|
||||||
|
// data: config.data,
|
||||||
|
// headers: config.headers,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
return config;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Request interceptor error:', error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('❌ Request error:', error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应拦截器
|
||||||
|
* 统一处理响应和错误
|
||||||
|
*/
|
||||||
|
api.interceptors.response.use(
|
||||||
|
async (response: AxiosResponse) => {
|
||||||
|
// 从请求队列中移除
|
||||||
|
removePendingRequest(response.config);
|
||||||
|
|
||||||
|
// 计算请求耗时
|
||||||
|
const duration = Date.now() - (response.config as any).metadata?.startTime;
|
||||||
|
|
||||||
|
const resData: any = await parseResponse(response);
|
||||||
|
|
||||||
|
// 打印响应信息(开发环境)
|
||||||
|
// if (__DEV__) {
|
||||||
|
// console.log('📥 API Response:', {
|
||||||
|
// url: response.config.url,
|
||||||
|
// status: response.status,
|
||||||
|
// duration: `${duration}ms`,
|
||||||
|
// data: response.data,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 统一处理响应数据格式
|
||||||
|
// const apiResponse = response.data as ApiResponse;
|
||||||
|
|
||||||
|
// 如果后端返回的数据结构包含 code 和 data
|
||||||
|
// if (apiResponse && typeof apiResponse === 'object' && 'code' in apiResponse) {
|
||||||
|
// // 检查业务状态码
|
||||||
|
// if (apiResponse.code !== 0 && apiResponse.code !== 200) {
|
||||||
|
// // 业务错误
|
||||||
|
// const error = new Error(apiResponse.message || '请求失败') as any;
|
||||||
|
// error.code = apiResponse.code;
|
||||||
|
// error.response = response;
|
||||||
|
// return Promise.reject(error);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 返回 data 字段
|
||||||
|
// return apiResponse.data;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 直接返回响应数据
|
||||||
|
// return response.data;
|
||||||
|
return Promise.resolve(resData);
|
||||||
|
},
|
||||||
|
async (error: AxiosError<ApiError>) => {
|
||||||
|
// 从请求队列中移除
|
||||||
|
if (error.config) {
|
||||||
|
removePendingRequest(error.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是取消的请求,直接返回
|
||||||
|
if (axios.isCancel(error)) {
|
||||||
|
if (__DEV__) {
|
||||||
|
console.log('🚫 Request cancelled:', error.message);
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalRequest = error.config as RequestConfig & { _retry?: boolean };
|
||||||
|
|
||||||
|
// 打印错误信息
|
||||||
|
if (__DEV__) {
|
||||||
|
console.error('❌ API Error:', {
|
||||||
|
method: error.config?.method,
|
||||||
|
cmdId: error.config?.headers?.cmdId,
|
||||||
|
status: error.response?.status,
|
||||||
|
message: error.message,
|
||||||
|
data: error.response?.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理不同的错误状态码
|
||||||
|
if (error.response) {
|
||||||
|
const { status, data } = error.response;
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 401: {
|
||||||
|
// Token 过期,尝试刷新
|
||||||
|
if (!originalRequest._retry) {
|
||||||
|
if (isRefreshing) {
|
||||||
|
// 如果正在刷新,将请求加入队列
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
subscribeTokenRefresh((token: string) => {
|
||||||
|
if (originalRequest.headers) {
|
||||||
|
originalRequest.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
resolve(api(originalRequest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
originalRequest._retry = true;
|
||||||
|
isRefreshing = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newToken = await refreshAccessToken();
|
||||||
|
|
||||||
|
if (newToken) {
|
||||||
|
// Token 刷新成功
|
||||||
|
isRefreshing = false;
|
||||||
|
onTokenRefreshed(newToken);
|
||||||
|
|
||||||
|
// 重试原请求
|
||||||
|
if (originalRequest.headers) {
|
||||||
|
originalRequest.headers.Authorization = `Bearer ${newToken}`;
|
||||||
|
}
|
||||||
|
return api(originalRequest);
|
||||||
|
} else {
|
||||||
|
// Token 刷新失败,跳转到登录页
|
||||||
|
isRefreshing = false;
|
||||||
|
await AsyncStorage.multiRemove(['auth_token', 'refresh_token']);
|
||||||
|
|
||||||
|
// 跳转到登录页
|
||||||
|
if (router.canGoBack()) {
|
||||||
|
router.replace('/(auth)/login' as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (refreshError) {
|
||||||
|
isRefreshing = false;
|
||||||
|
await AsyncStorage.multiRemove(['auth_token', 'refresh_token']);
|
||||||
|
|
||||||
|
// 跳转到登录页
|
||||||
|
if (router.canGoBack()) {
|
||||||
|
router.replace('/(auth)/login' as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(refreshError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 403:
|
||||||
|
// 禁止访问
|
||||||
|
console.error('❌ 403: 没有权限访问该资源');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 404:
|
||||||
|
// 资源不存在
|
||||||
|
console.error('❌ 404: 请求的资源不存在');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 422:
|
||||||
|
// 表单验证错误
|
||||||
|
console.error('❌ 422: 表单验证失败', data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 429:
|
||||||
|
// 请求过于频繁
|
||||||
|
console.error('❌ 429: 请求过于频繁,请稍后再试');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 500:
|
||||||
|
// 服务器错误
|
||||||
|
console.error('❌ 500: 服务器内部错误');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 502:
|
||||||
|
// 网关错误
|
||||||
|
console.error('❌ 502: 网关错误');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 503:
|
||||||
|
// 服务不可用
|
||||||
|
console.error('❌ 503: 服务暂时不可用');
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.error(`❌ ${status}: 未知错误`);
|
||||||
|
}
|
||||||
|
} else if (error.request) {
|
||||||
|
// 请求已发送但没有收到响应
|
||||||
|
console.error('❌ 网络错误: 请检查网络连接');
|
||||||
|
} else {
|
||||||
|
// 请求配置出错
|
||||||
|
console.error('❌ 请求配置错误:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义错误处理
|
||||||
|
if (originalRequest?.customErrorHandler) {
|
||||||
|
originalRequest.customErrorHandler(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消所有待处理的请求
|
||||||
|
*/
|
||||||
|
export function cancelAllRequests(message = '请求已取消'): void {
|
||||||
|
pendingRequests.forEach((source) => {
|
||||||
|
source.cancel(message);
|
||||||
|
});
|
||||||
|
pendingRequests.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消指定 URL 的请求
|
||||||
|
*/
|
||||||
|
export function cancelRequest(url: string): void {
|
||||||
|
pendingRequests.forEach((source, key) => {
|
||||||
|
if (key.includes(url)) {
|
||||||
|
source.cancel('请求已取消');
|
||||||
|
pendingRequests.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用请求方法(增强版)
|
||||||
|
*/
|
||||||
|
export const request = {
|
||||||
|
/**
|
||||||
|
* GET 请求
|
||||||
|
*/
|
||||||
|
get: <T = any>(url: string, config?: RequestConfig) => api.get<T, T>(url, config),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST 请求
|
||||||
|
*/
|
||||||
|
post: <T = any>(url: string, data?: any, config?: RequestConfig) =>
|
||||||
|
api.post<T, T>(url, data, config),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT 请求
|
||||||
|
*/
|
||||||
|
put: <T = any>(url: string, data?: any, config?: RequestConfig) =>
|
||||||
|
api.put<T, T>(url, data, config),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE 请求
|
||||||
|
*/
|
||||||
|
delete: <T = any>(url: string, config?: RequestConfig) => api.delete<T, T>(url, config),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PATCH 请求
|
||||||
|
*/
|
||||||
|
patch: <T = any>(url: string, data?: any, config?: RequestConfig) =>
|
||||||
|
api.patch<T, T>(url, data, config),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件
|
||||||
|
*/
|
||||||
|
upload: <T = any>(
|
||||||
|
url: string,
|
||||||
|
file: File | Blob,
|
||||||
|
onProgress?: (progress: number) => void,
|
||||||
|
config?: RequestConfig
|
||||||
|
) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
return api.post<T, T>(url, formData, {
|
||||||
|
...config,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
...config?.headers,
|
||||||
|
},
|
||||||
|
onUploadProgress: (progressEvent) => {
|
||||||
|
if (onProgress && progressEvent.total) {
|
||||||
|
const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
||||||
|
onProgress(progress);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文件
|
||||||
|
*/
|
||||||
|
download: async (
|
||||||
|
url: string,
|
||||||
|
filename?: string,
|
||||||
|
onProgress?: (progress: number) => void,
|
||||||
|
config?: RequestConfig
|
||||||
|
) => {
|
||||||
|
const response = await api.get(url, {
|
||||||
|
...config,
|
||||||
|
responseType: 'blob',
|
||||||
|
onDownloadProgress: (progressEvent) => {
|
||||||
|
if (onProgress && progressEvent.total) {
|
||||||
|
const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
||||||
|
onProgress(progress);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建下载链接
|
||||||
|
const blob = new Blob([response]);
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = downloadUrl;
|
||||||
|
link.download = filename || 'download';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 并发请求
|
||||||
|
*/
|
||||||
|
all: <T = any>(requests: Promise<T>[]) => Promise.all(requests),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 串行请求
|
||||||
|
*/
|
||||||
|
series: async <T = any>(requests: (() => Promise<T>)[]): Promise<T[]> => {
|
||||||
|
const results: T[] = [];
|
||||||
|
for (const request of requests) {
|
||||||
|
const result = await request();
|
||||||
|
results.push(result);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建带重试的请求
|
||||||
|
*/
|
||||||
|
export function createRetryRequest<T = any>(
|
||||||
|
requestFn: () => Promise<T>,
|
||||||
|
maxRetries = 3,
|
||||||
|
retryDelay = 1000
|
||||||
|
): Promise<T> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let retries = 0;
|
||||||
|
|
||||||
|
const attempt = async () => {
|
||||||
|
try {
|
||||||
|
const result = await requestFn();
|
||||||
|
resolve(result);
|
||||||
|
} catch (error) {
|
||||||
|
retries++;
|
||||||
|
|
||||||
|
if (retries < maxRetries) {
|
||||||
|
if (__DEV__) {
|
||||||
|
console.log(`🔄 Retrying request (${retries}/${maxRetries})...`);
|
||||||
|
}
|
||||||
|
setTimeout(attempt, retryDelay * retries);
|
||||||
|
} else {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
attempt();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default api;
|
||||||
@@ -178,4 +178,3 @@ class Storage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default Storage;
|
export default Storage;
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,8 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": ["./*"]
|
||||||
"./*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
|
||||||
"**/*.ts",
|
|
||||||
"**/*.tsx",
|
|
||||||
".expo/types/**/*.ts",
|
|
||||||
"expo-env.d.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user