Compare commits
13 Commits
61252cdf36
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 54bf84b19b | |||
| 9ef9233797 | |||
| b48cce06f4 | |||
| 230191f181 | |||
| 0f1f775605 | |||
| 10a3408ff6 | |||
| 3d1c4853ce | |||
| f551a26a0a | |||
| b3a06f7f9e | |||
| 170a08e5d1 | |||
| 855f289579 | |||
| c0d54b8513 | |||
| ce324c9bb5 |
7
.env.development
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# 开发环境配置
|
||||||
|
# EXPO_PUBLIC_API_URL=/api # 注释掉,让 config.ts 根据平台自动选择
|
||||||
|
EXPO_PUBLIC_API_TIMEOUT=10000
|
||||||
|
|
||||||
|
# 测试环境的域名
|
||||||
|
API_TARGET=https://51zhh5.notbug.org
|
||||||
|
|
||||||
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
@@ -0,0 +1,4 @@
|
|||||||
|
# 生产环境配置
|
||||||
|
EXPO_PUBLIC_API_URL=/
|
||||||
|
EXPO_PUBLIC_API_TIMEOUT=10000
|
||||||
|
|
||||||
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
@@ -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"
|
||||||
|
}
|
||||||
273
README.md
@@ -20,15 +20,28 @@
|
|||||||
|
|
||||||
## ✨ 项目特性
|
## ✨ 项目特性
|
||||||
|
|
||||||
|
### 核心特性
|
||||||
|
|
||||||
- 🎯 **Expo Router** - 文件路由系统,类似 Next.js,支持类型安全的导航
|
- 🎯 **Expo Router** - 文件路由系统,类似 Next.js,支持类型安全的导航
|
||||||
- 📘 **TypeScript** - 完整的类型支持,提升代码质量
|
- 📘 **TypeScript** - 完整的类型支持,提升代码质量
|
||||||
- 🔥 **EAS Update** - 热更新支持(CodePush 的官方替代方案)
|
- 🔥 **EAS Update** - 热更新支持(CodePush 的官方替代方案)
|
||||||
- ⚡ **React Native 新架构** - 启用 Fabric 渲染器和 TurboModules
|
- ⚡ **React Native 新架构** - 启用 Fabric 渲染器和 TurboModules
|
||||||
- 📦 **pnpm** - 快速、节省磁盘空间的包管理器
|
- 📦 **pnpm** - 快速、节省磁盘空间的包管理器
|
||||||
- 🧭 **标签导航** - 开箱即用的导航示例
|
- 🧭 **标签导航** - 开箱即用的导航示例
|
||||||
- 🎨 **主题支持** - 内置深色/浅色主题切换
|
- 🎨 **完整主题系统** - 深色/浅色/自动主题切换,完整的颜色配置 🎯
|
||||||
- 📱 **跨平台** - 支持 iOS、Android 和 Web
|
- 📱 **跨平台** - 支持 iOS、Android 和 Web
|
||||||
|
|
||||||
|
### 架构特性 🎯
|
||||||
|
|
||||||
|
- 📁 **扁平化目录结构** - 清晰的模块组织,符合社区最佳实践
|
||||||
|
- 🔄 **模块化导出** - 每个目录独立导出,避免循环依赖
|
||||||
|
- 💾 **双存储系统** - AsyncStorage(持久化)+ SessionStorage(临时)
|
||||||
|
- 🎨 **主题化组件** - ThemedText 和 ThemedView,自动适配主题
|
||||||
|
- 🔐 **API 加密** - 请求/响应自动加密解密
|
||||||
|
- 📊 **状态管理** - Zustand + AsyncStorage 持久化
|
||||||
|
- ✅ **数据验证** - Zod 表单验证
|
||||||
|
- 🎯 **类型安全** - 完整的 TypeScript 类型定义
|
||||||
|
|
||||||
## 🚀 快速开始
|
## 🚀 快速开始
|
||||||
|
|
||||||
### 前置要求
|
### 前置要求
|
||||||
@@ -69,16 +82,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
|
||||||
```
|
```
|
||||||
@@ -91,53 +107,100 @@ rn-demo/
|
|||||||
│ ├── (tabs)/ # 标签导航组
|
│ ├── (tabs)/ # 标签导航组
|
||||||
│ │ ├── index.tsx # 首页 - 热更新演示 ⭐
|
│ │ ├── index.tsx # 首页 - 热更新演示 ⭐
|
||||||
│ │ ├── two.tsx # 第二个标签页
|
│ │ ├── two.tsx # 第二个标签页
|
||||||
│ │ ├── demo.tsx # 完整示例页面
|
│ │ ├── demo.tsx # 完整示例页面 🎯
|
||||||
|
│ │ ├── paper.tsx # React Native Paper 示例
|
||||||
│ │ └── _layout.tsx # 标签布局
|
│ │ └── _layout.tsx # 标签布局
|
||||||
|
│ ├── test.tsx # 测试页面(无 tabs)🎯
|
||||||
│ ├── _layout.tsx # 根布局 - 自动检查更新 ⭐
|
│ ├── _layout.tsx # 根布局 - 自动检查更新 ⭐
|
||||||
│ ├── modal.tsx # 模态页面示例
|
│ ├── modal.tsx # 模态页面示例
|
||||||
│ ├── +html.tsx # Web HTML 模板
|
│ ├── +html.tsx # Web HTML 模板
|
||||||
│ └── +not-found.tsx # 404 页面
|
│ └── +not-found.tsx # 404 页面
|
||||||
│
|
│
|
||||||
├── 💻 src/ # 源代码目录
|
├── 💻 业务代码目录
|
||||||
│ ├── utils/ # 工具函数
|
├── utils/ # 工具函数
|
||||||
|
│ ├── network/ # 网络相关
|
||||||
│ │ ├── api.ts # Axios API 配置
|
│ │ ├── api.ts # Axios API 配置
|
||||||
│ │ ├── storage.ts # AsyncStorage 封装
|
│ │ ├── helper.ts # 加密/解密工具
|
||||||
│ │ └── date.ts # Day.js 日期工具
|
│ │ └── error.ts # 错误处理
|
||||||
│ ├── stores/ # Zustand 状态管理
|
│ ├── storage.ts # AsyncStorage 封装
|
||||||
│ │ ├── userStore.ts # 用户状态
|
│ ├── sessionStorage.ts # Session Storage 实现 🎯
|
||||||
│ │ └── settingsStore.ts # 应用设置
|
│ ├── storageManager.ts # 统一存储管理器 🎯
|
||||||
│ ├── schemas/ # Zod 验证规则
|
│ ├── config.ts # 配置管理
|
||||||
│ │ ├── auth.ts # 认证验证
|
│ ├── date.ts # Day.js 日期工具
|
||||||
│ │ └── user.ts # 用户验证
|
│ ├── common.ts # 通用工具
|
||||||
│ ├── services/ # API 服务层
|
│ └── index.ts # 统一导出
|
||||||
│ │ ├── authService.ts # 认证服务
|
│
|
||||||
│ │ └── userService.ts # 用户服务
|
├── stores/ # Zustand 状态管理
|
||||||
│ ├── hooks/ # 自定义 Hooks
|
│ ├── userStore.ts # 用户状态
|
||||||
│ │ ├── useDebounce.ts # 防抖 Hook
|
│ ├── settingsStore.ts # 应用设置
|
||||||
│ │ ├── useThrottle.ts # 节流 Hook
|
│ ├── tenantStore.ts # 租户状态 🎯
|
||||||
│ │ └── useHaptics.ts # 触觉反馈 Hook
|
│ └── index.ts # 统一导出
|
||||||
│ ├── types/ # TypeScript 类型
|
│
|
||||||
│ │ └── index.ts # 全局类型定义
|
├── schemas/ # Zod 验证规则
|
||||||
│ └── index.ts # 统一导出 ⭐
|
│ ├── auth.ts # 认证验证
|
||||||
|
│ ├── user.ts # 用户验证
|
||||||
|
│ └── index.ts # 统一导出
|
||||||
|
│
|
||||||
|
├── services/ # API 服务层
|
||||||
|
│ ├── authService.ts # 认证服务
|
||||||
|
│ ├── userService.ts # 用户服务
|
||||||
|
│ ├── tenantService.ts # 租户服务 🎯
|
||||||
|
│ └── index.ts # 统一导出
|
||||||
|
│
|
||||||
|
├── hooks/ # 自定义 Hooks
|
||||||
|
│ ├── useDebounce.ts # 防抖 Hook
|
||||||
|
│ ├── useThrottle.ts # 节流 Hook
|
||||||
|
│ ├── useHaptics.ts # 触觉反馈 Hook
|
||||||
|
│ ├── useRequest.ts # 请求 Hook 🎯
|
||||||
|
│ ├── useTheme.ts # 主题 Hooks 🎯
|
||||||
|
│ ├── useColorScheme.ts # 颜色方案 Hook 🎯
|
||||||
|
│ ├── useColorScheme.web.ts # Web 平台颜色方案 🎯
|
||||||
|
│ ├── useClientOnlyValue.ts # 客户端值 Hook 🎯
|
||||||
|
│ ├── useClientOnlyValue.web.ts # Web 平台客户端值 🎯
|
||||||
|
│ └── index.ts # 统一导出
|
||||||
|
│
|
||||||
|
├── types/ # TypeScript 类型
|
||||||
|
│ ├── api.ts # API 类型定义
|
||||||
|
│ └── index.ts # 统一导出
|
||||||
|
│
|
||||||
|
├── screens/ # 业务页面组件 🎯
|
||||||
|
│ └── index.ts # 统一导出
|
||||||
│
|
│
|
||||||
├── 🧩 components/ # 可复用组件
|
├── 🧩 components/ # 可复用组件
|
||||||
│ ├── Themed.tsx # 主题化组件
|
│ ├── Themed.tsx # 主题化组件 🎯
|
||||||
|
│ ├── ThemeDemo.tsx # 主题演示组件 🎯
|
||||||
│ ├── ExternalLink.tsx # 外部链接组件
|
│ ├── ExternalLink.tsx # 外部链接组件
|
||||||
│ └── useColorScheme.ts # 主题 Hook
|
│ └── index.ts # 统一导出 🎯
|
||||||
|
│
|
||||||
|
├── 🎨 theme/ # 主题系统 🎯 NEW!
|
||||||
|
│ ├── index.ts # 统一导出
|
||||||
|
│ ├── utils.ts # 主题工具函数
|
||||||
|
│ └── styles.ts # 样式工厂(类似 CSS 类名)
|
||||||
│
|
│
|
||||||
├── 🎯 constants/ # 常量配置
|
├── 🎯 constants/ # 常量配置
|
||||||
│ └── Colors.ts # 颜色主题
|
│ ├── Colors.ts # 颜色主题(完整配置)🎯
|
||||||
|
│ └── network.ts # 网络常量
|
||||||
│
|
│
|
||||||
├── 🎨 assets/ # 静态资源
|
├── 🎨 assets/ # 静态资源
|
||||||
│ ├── images/ # 图片资源
|
│ ├── images/ # 图片资源
|
||||||
│ └── fonts/ # 字体文件
|
│ └── fonts/ # 字体文件
|
||||||
│
|
│
|
||||||
├── 📚 docs/ # 项目文档
|
├── 📚 docs/ # 项目文档 🎯
|
||||||
│ ├── USAGE_EXAMPLES.md # 使用示例
|
│ ├── USAGE_EXAMPLES.md # 使用示例
|
||||||
│ └── LIBRARIES.md # 工具库使用指南
|
│ ├── LIBRARIES.md # 工具库使用指南
|
||||||
|
│ ├── PROJECT_STRUCTURE_V2.md # 项目结构说明 🎯
|
||||||
|
│ ├── MIGRATION_TO_FLAT_STRUCTURE.md # 扁平化迁移报告 🎯
|
||||||
|
│ ├── STORES_ARCHITECTURE.md # Stores 架构设计 🎯
|
||||||
|
│ ├── STORAGE_GUIDE.md # 存储系统使用指南 🎯
|
||||||
|
│ ├── THEME_GUIDE.md # 主题系统使用指南 🎯 NEW!
|
||||||
|
│ └── ... 更多文档
|
||||||
|
│
|
||||||
|
├── 🔧 scripts/ # 脚本文件
|
||||||
|
│ └── proxy-server.js # 代理服务器 🎯
|
||||||
│
|
│
|
||||||
├── ⚙️ 配置文件
|
├── ⚙️ 配置文件
|
||||||
│ ├── .env.example # 环境变量示例 🎯 NEW!
|
│ ├── .env.development # 开发环境变量 🎯
|
||||||
|
│ ├── .env.production # 生产环境变量 🎯
|
||||||
│ ├── app.json # Expo 配置 ⭐
|
│ ├── app.json # Expo 配置 ⭐
|
||||||
│ ├── eas.json # EAS 构建和更新配置 ⭐
|
│ ├── eas.json # EAS 构建和更新配置 ⭐
|
||||||
│ ├── package.json # 项目依赖
|
│ ├── package.json # 项目依赖
|
||||||
@@ -150,78 +213,140 @@ rn-demo/
|
|||||||
└── CHANGELOG.md # 更新日志
|
└── CHANGELOG.md # 更新日志
|
||||||
|
|
||||||
⭐ = 热更新相关的关键文件
|
⭐ = 热更新相关的关键文件
|
||||||
🎯 = 新增的文件/目录
|
🎯 = 新增/更新的文件/目录
|
||||||
```
|
```
|
||||||
|
|
||||||
### 关键目录说明
|
### 关键目录说明
|
||||||
|
|
||||||
#### 📱 `app/` - 路由和页面
|
#### 📱 `app/` - 路由和页面
|
||||||
|
|
||||||
- **`app/_layout.tsx`** - 应用启动时自动检查更新
|
- **`app/_layout.tsx`** - 应用启动时自动检查更新
|
||||||
- **`app/(tabs)/index.tsx`** - 热更新演示页面
|
- **`app/(tabs)/index.tsx`** - 热更新演示页面
|
||||||
- **`app/(tabs)/demo.tsx`** - 完整示例页面,展示所有工具的使用 🎯
|
- **`app/(tabs)/demo.tsx`** - 完整示例页面,展示所有工具的使用 🎯
|
||||||
|
- **`app/test.tsx`** - 测试页面示例(不包含底部 tabs)🎯
|
||||||
|
|
||||||
|
#### 💻 业务代码目录(扁平化结构)🎯
|
||||||
|
|
||||||
|
项目采用扁平化目录结构,所有业务模块都在根目录下:
|
||||||
|
|
||||||
|
- **`utils/`** - 工具函数
|
||||||
|
- 网络请求(Axios + 加密)
|
||||||
|
- 存储管理(AsyncStorage + SessionStorage)🎯
|
||||||
|
- 日期处理(Day.js)
|
||||||
|
- 配置管理
|
||||||
|
|
||||||
|
- **`stores/`** - 状态管理(Zustand)
|
||||||
|
- 用户状态(登录、用户信息)
|
||||||
|
- 应用设置(主题、语言、触觉反馈)
|
||||||
|
- 租户状态 🎯
|
||||||
|
|
||||||
|
- **`schemas/`** - 数据验证(Zod)
|
||||||
|
- 认证表单验证
|
||||||
|
- 用户数据验证
|
||||||
|
|
||||||
#### 💻 `src/` - 源代码目录 🎯
|
|
||||||
项目的核心业务逻辑代码,包含:
|
|
||||||
- **`utils/`** - 工具函数(API、存储、日期)
|
|
||||||
- **`stores/`** - 状态管理(用户、设置)
|
|
||||||
- **`schemas/`** - 数据验证(认证、用户)
|
|
||||||
- **`services/`** - API 服务层
|
- **`services/`** - API 服务层
|
||||||
|
- 认证服务
|
||||||
|
- 用户服务
|
||||||
|
- 租户服务 🎯
|
||||||
|
|
||||||
- **`hooks/`** - 自定义 Hooks
|
- **`hooks/`** - 自定义 Hooks
|
||||||
|
- 防抖/节流
|
||||||
|
- 触觉反馈
|
||||||
|
- 请求管理 🎯
|
||||||
|
- 主题 Hooks(useColorScheme, useTheme, useClientOnlyValue)🎯
|
||||||
|
|
||||||
- **`types/`** - TypeScript 类型定义
|
- **`types/`** - TypeScript 类型定义
|
||||||
- **`index.ts`** - 统一导出所有模块
|
|
||||||
|
- **`screens/`** - 业务页面组件 🎯
|
||||||
|
|
||||||
|
- **`theme/`** - 主题系统 🎯 NEW!
|
||||||
|
- 统一的主题配置导出
|
||||||
|
- 主题工具函数
|
||||||
|
- 样式工厂(类似 CSS 类名)
|
||||||
|
|
||||||
|
每个目录都有 `index.ts` 文件,负责统一导出该目录下的所有模块。
|
||||||
|
|
||||||
|
#### 🧩 `components/` - 可复用组件
|
||||||
|
|
||||||
|
- **`Themed.tsx`** - 主题化组件(ThemedText, ThemedView)🎯
|
||||||
|
- **`ThemeDemo.tsx`** - 主题演示组件 🎯
|
||||||
|
|
||||||
#### 📚 `docs/` - 项目文档 🎯
|
#### 📚 `docs/` - 项目文档 🎯
|
||||||
|
|
||||||
完善的项目文档,包含:
|
完善的项目文档,包含:
|
||||||
|
|
||||||
- **使用指南** - 如何使用各个工具
|
- **使用指南** - 如何使用各个工具
|
||||||
- **代码示例** - 实际的代码示例
|
- **代码示例** - 实际的代码示例
|
||||||
- **配置说明** - 项目配置详解
|
- **配置说明** - 项目配置详解
|
||||||
|
- **架构设计** - Stores 架构、存储系统等
|
||||||
|
- **主题系统** - 主题配置和样式使用指南 🎯 NEW!
|
||||||
|
- **迁移报告** - 扁平化目录结构迁移
|
||||||
|
|
||||||
### 核心文件说明
|
### 核心文件说明
|
||||||
|
|
||||||
#### 热更新相关 ⭐
|
#### 热更新相关 ⭐
|
||||||
|
|
||||||
- **`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`** - 统一导出,从这里导入所有工具
|
|
||||||
- **`.env.example`** - 环境变量配置示例
|
- **各目录的 `index.ts`** - 统一导出,从这里导入所有工具
|
||||||
|
- **`.env.development` / `.env.production`** - 环境变量配置 🎯
|
||||||
- **`tsconfig.json`** - 配置了路径别名 `@/*`
|
- **`tsconfig.json`** - 配置了路径别名 `@/*`
|
||||||
|
|
||||||
### 已安装的工具库
|
### 已安装的工具库
|
||||||
|
|
||||||
项目已安装并配置好以下工具库:
|
项目已安装并配置好以下工具库:
|
||||||
|
|
||||||
| 类别 | 工具库 | 用途 |
|
| 类别 | 工具库 | 用途 | 状态 |
|
||||||
|------|--------|------|
|
| ------------ | ----------------------------------------- | -------------------- | ---- |
|
||||||
| **工具类** | 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 | 触觉反馈 | ✅ |
|
||||||
|
| **UI 组件** | react-native-paper | Material Design 组件 | ✅ |
|
||||||
|
|
||||||
### 快速开始
|
### 快速开始
|
||||||
|
|
||||||
1. **查看完整示例** - 运行应用,点击 "完整示例" tab 🎯
|
1. **查看完整示例** - 运行应用,点击 "Demo" tab 🎯
|
||||||
2. **阅读文档** - 查看 [docs/](./docs/) 目录中的文档
|
2. **阅读文档** - 查看 [docs/](./docs/) 目录中的文档
|
||||||
3. **使用工具** - 从 `@/src` 导入所需的工具
|
3. **使用工具** - 从各个模块目录导入所需的工具
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 示例:导入工具
|
// ✅ 推荐:从模块目录导入
|
||||||
|
import { Storage, SessionStorage, StorageManager, formatDate } from '@/utils';
|
||||||
|
import { useUser, useTheme, useLanguage } from '@/stores';
|
||||||
|
import { authService, userService } from '@/services';
|
||||||
import {
|
import {
|
||||||
api,
|
|
||||||
Storage,
|
|
||||||
formatDate,
|
|
||||||
useUserStore,
|
|
||||||
authService,
|
|
||||||
useDebounce,
|
useDebounce,
|
||||||
useHaptics,
|
useHaptics,
|
||||||
} from '@/src';
|
useColorScheme,
|
||||||
|
useThemeColors,
|
||||||
|
useClientOnlyValue,
|
||||||
|
} from '@/hooks';
|
||||||
|
import { loginSchema } from '@/schemas';
|
||||||
|
|
||||||
|
// ✅ 主题系统:统一从 theme 目录导入 🎯 NEW!
|
||||||
|
import {
|
||||||
|
useColorScheme,
|
||||||
|
useThemeColors,
|
||||||
|
ThemedText,
|
||||||
|
ThemedView,
|
||||||
|
commonStyles,
|
||||||
|
createThemeStyles,
|
||||||
|
} from '@/theme';
|
||||||
|
|
||||||
|
// ✅ 也可以:直接从具体文件导入
|
||||||
|
import { useUser } from '@/stores/userStore';
|
||||||
|
import { formatDate } from '@/utils/date';
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔥 热更新使用指南
|
## 🔥 热更新使用指南
|
||||||
@@ -243,22 +368,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. 安装到你的设备上
|
||||||
|
|
||||||
@@ -292,7 +421,7 @@ eas update --channel production --message "v1.0.1: 添加了新功能"
|
|||||||
项目配置了三个更新通道(在 `eas.json` 中定义):
|
项目配置了三个更新通道(在 `eas.json` 中定义):
|
||||||
|
|
||||||
| 通道 | 用途 | 适用场景 |
|
| 通道 | 用途 | 适用场景 |
|
||||||
|------|------|----------|
|
| --------------- | -------- | -------------- |
|
||||||
| **development** | 开发环境 | 日常开发和测试 |
|
| **development** | 开发环境 | 日常开发和测试 |
|
||||||
| **preview** | 预览环境 | 内部测试和 QA |
|
| **preview** | 预览环境 | 内部测试和 QA |
|
||||||
| **production** | 生产环境 | 正式发布给用户 |
|
| **production** | 生产环境 | 正式发布给用户 |
|
||||||
@@ -404,12 +533,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` 发布更新,然后在应用中检查更新即可看到变化。
|
||||||
@@ -481,6 +607,7 @@ Alert.alert(
|
|||||||
```
|
```
|
||||||
|
|
||||||
**配置说明:**
|
**配置说明:**
|
||||||
|
|
||||||
- `updates.url` - EAS Update 服务器地址(运行 `eas init` 后自动生成)
|
- `updates.url` - EAS Update 服务器地址(运行 `eas init` 后自动生成)
|
||||||
- `runtimeVersion.policy` - 运行时版本策略,确保更新兼容性
|
- `runtimeVersion.policy` - 运行时版本策略,确保更新兼容性
|
||||||
- `appVersion` - 基于 `version` 字段(当前使用)
|
- `appVersion` - 基于 `version` 字段(当前使用)
|
||||||
@@ -512,6 +639,7 @@ Alert.alert(
|
|||||||
```
|
```
|
||||||
|
|
||||||
**配置说明:**
|
**配置说明:**
|
||||||
|
|
||||||
- `developmentClient` - 是否为开发客户端(包含开发工具)
|
- `developmentClient` - 是否为开发客户端(包含开发工具)
|
||||||
- `distribution` - 分发方式(`internal` 或 `store`)
|
- `distribution` - 分发方式(`internal` 或 `store`)
|
||||||
- `channel` - 更新通道,决定应用接收哪个通道的更新
|
- `channel` - 更新通道,决定应用接收哪个通道的更新
|
||||||
@@ -521,6 +649,7 @@ Alert.alert(
|
|||||||
### 何时使用热更新
|
### 何时使用热更新
|
||||||
|
|
||||||
✅ **适合热更新的场景:**
|
✅ **适合热更新的场景:**
|
||||||
|
|
||||||
- ✅ 修复 JavaScript/TypeScript 代码 bug
|
- ✅ 修复 JavaScript/TypeScript 代码 bug
|
||||||
- ✅ 更新 UI 样式和布局
|
- ✅ 更新 UI 样式和布局
|
||||||
- ✅ 修改业务逻辑
|
- ✅ 修改业务逻辑
|
||||||
@@ -529,6 +658,7 @@ Alert.alert(
|
|||||||
- ✅ 添加新的 JS 功能
|
- ✅ 添加新的 JS 功能
|
||||||
|
|
||||||
❌ **不适合热更新的场景(需要重新构建):**
|
❌ **不适合热更新的场景(需要重新构建):**
|
||||||
|
|
||||||
- ❌ 添加/删除原生依赖(如 `react-native-camera`)
|
- ❌ 添加/删除原生依赖(如 `react-native-camera`)
|
||||||
- ❌ 修改原生代码(iOS/Android)
|
- ❌ 修改原生代码(iOS/Android)
|
||||||
- ❌ 更改应用权限(如相机、位置权限)
|
- ❌ 更改应用权限(如相机、位置权限)
|
||||||
@@ -539,11 +669,13 @@ Alert.alert(
|
|||||||
### 版本管理策略
|
### 版本管理策略
|
||||||
|
|
||||||
**appVersion 策略**(当前使用):
|
**appVersion 策略**(当前使用):
|
||||||
|
|
||||||
- 基于 `app.json` 中的 `version` 字段
|
- 基于 `app.json` 中的 `version` 字段
|
||||||
- ✅ 优点:简单直观,易于理解
|
- ✅ 优点:简单直观,易于理解
|
||||||
- ⚠️ 注意:每次原生构建需要手动更新版本号
|
- ⚠️ 注意:每次原生构建需要手动更新版本号
|
||||||
|
|
||||||
**nativeVersion 策略**:
|
**nativeVersion 策略**:
|
||||||
|
|
||||||
- 基于原生构建号(iOS 的 `buildNumber`,Android 的 `versionCode`)
|
- 基于原生构建号(iOS 的 `buildNumber`,Android 的 `versionCode`)
|
||||||
- ✅ 优点:自动管理,无需手动更新
|
- ✅ 优点:自动管理,无需手动更新
|
||||||
- ⚠️ 注意:需要配置原生构建号
|
- ⚠️ 注意:需要配置原生构建号
|
||||||
@@ -564,6 +696,7 @@ Alert.alert(
|
|||||||
### Q: 更新后没有生效?
|
### Q: 更新后没有生效?
|
||||||
|
|
||||||
**A:** 检查以下几点:
|
**A:** 检查以下几点:
|
||||||
|
|
||||||
1. 确保应用完全关闭后重新打开(不是后台切换)
|
1. 确保应用完全关闭后重新打开(不是后台切换)
|
||||||
2. 检查更新通道是否匹配(development/preview/production)
|
2. 检查更新通道是否匹配(development/preview/production)
|
||||||
3. 确保 `runtimeVersion` 匹配
|
3. 确保 `runtimeVersion` 匹配
|
||||||
@@ -573,6 +706,7 @@ Alert.alert(
|
|||||||
### Q: 如何回滚到之前的版本?
|
### Q: 如何回滚到之前的版本?
|
||||||
|
|
||||||
**A:**
|
**A:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 查看更新历史
|
# 查看更新历史
|
||||||
eas update:list --channel production
|
eas update:list --channel production
|
||||||
@@ -584,6 +718,7 @@ eas update --channel production --branch main --message "回滚到稳定版本"
|
|||||||
### Q: 热更新的大小限制是多少?
|
### Q: 热更新的大小限制是多少?
|
||||||
|
|
||||||
**A:** EAS Update 没有严格的大小限制,但建议:
|
**A:** EAS Update 没有严格的大小限制,但建议:
|
||||||
|
|
||||||
- 保持更新包 < 10MB 以确保良好的用户体验
|
- 保持更新包 < 10MB 以确保良好的用户体验
|
||||||
- 避免在更新中包含大量图片或资源文件
|
- 避免在更新中包含大量图片或资源文件
|
||||||
- 使用 CDN 托管大型资源
|
- 使用 CDN 托管大型资源
|
||||||
@@ -607,6 +742,7 @@ if (update.isAvailable) {
|
|||||||
### Q: 更新检查的频率是多少?
|
### Q: 更新检查的频率是多少?
|
||||||
|
|
||||||
**A:**
|
**A:**
|
||||||
|
|
||||||
- **应用启动时**:自动检查(在 `app/_layout.tsx` 中配置)
|
- **应用启动时**:自动检查(在 `app/_layout.tsx` 中配置)
|
||||||
- **手动检查**:用户点击"检查更新"按钮
|
- **手动检查**:用户点击"检查更新"按钮
|
||||||
- **后台检查**:可以配置定时检查(需要自己实现)
|
- **后台检查**:可以配置定时检查(需要自己实现)
|
||||||
@@ -614,6 +750,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 +772,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 +780,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)
|
||||||
@@ -655,13 +795,14 @@ if (update.isAvailable) {
|
|||||||
|
|
||||||
更多详细文档请查看 [docs](./docs/) 目录:
|
更多详细文档请查看 [docs](./docs/) 目录:
|
||||||
|
|
||||||
|
### 使用指南
|
||||||
|
|
||||||
- **[使用示例](./docs/USAGE_EXAMPLES.md)** - 实际代码示例
|
- **[使用示例](./docs/USAGE_EXAMPLES.md)** - 实际代码示例
|
||||||
- **[工具库使用指南](./docs/LIBRARIES.md)** - 详细的工具库使用方法和示例
|
- **[工具库使用指南](./docs/LIBRARIES.md)** - 详细的工具库使用方法和示例
|
||||||
|
- **[存储系统使用指南](./docs/STORAGE_GUIDE.md)** - AsyncStorage 和 SessionStorage 使用 🎯
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**祝你开发愉快!** 🎉
|
**祝你开发愉快!** 🎉
|
||||||
|
|
||||||
如有问题,请查看 [常见问题](#-常见问题) 或访问 [Expo 官方文档](https://docs.expo.dev/)。
|
如有问题,请查看 [常见问题](#-常见问题) 或访问 [Expo 官方文档](https://docs.expo.dev/)。
|
||||||
|
|
||||||
|
|||||||
16
app.json
@@ -31,23 +31,9 @@
|
|||||||
"output": "static",
|
"output": "static",
|
||||||
"favicon": "./assets/images/favicon.png"
|
"favicon": "./assets/images/favicon.png"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": ["expo-router"],
|
||||||
"expo-router",
|
|
||||||
"expo-updates"
|
|
||||||
],
|
|
||||||
"experiments": {
|
"experiments": {
|
||||||
"typedRoutes": true
|
"typedRoutes": true
|
||||||
},
|
|
||||||
"updates": {
|
|
||||||
"url": "https://u.expo.dev/your-project-id"
|
|
||||||
},
|
|
||||||
"runtimeVersion": {
|
|
||||||
"policy": "appVersion"
|
|
||||||
},
|
|
||||||
"extra": {
|
|
||||||
"eas": {
|
|
||||||
"projectId": "your-project-id"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import FontAwesome from '@expo/vector-icons/FontAwesome';
|
|||||||
import { Link, Tabs } from 'expo-router';
|
import { Link, Tabs } from 'expo-router';
|
||||||
import { Pressable } from 'react-native';
|
import { Pressable } from 'react-native';
|
||||||
|
|
||||||
import Colors from '@/constants/Colors';
|
import { Colors } from '@/theme';
|
||||||
import { useColorScheme } from '@/components/useColorScheme';
|
import { useColorScheme, useClientOnlyValue } from '@/hooks';
|
||||||
import { useClientOnlyValue } from '@/components/useClientOnlyValue';
|
|
||||||
|
|
||||||
// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
|
// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
|
||||||
function TabBarIcon(props: {
|
function TabBarIcon(props: {
|
||||||
@@ -25,46 +24,33 @@ 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={{
|
||||||
title: 'Tab One',
|
title: '首页',
|
||||||
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
|
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
|
||||||
headerRight: () => (
|
|
||||||
<Link href="/modal" asChild>
|
|
||||||
<Pressable>
|
|
||||||
{({ pressed }) => (
|
|
||||||
<FontAwesome
|
|
||||||
name="info-circle"
|
|
||||||
size={25}
|
|
||||||
color={Colors[colorScheme ?? 'light'].text}
|
|
||||||
style={{ marginRight: 15, opacity: pressed ? 0.5 : 1 }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="two"
|
name="two"
|
||||||
options={{
|
options={{
|
||||||
title: 'Tab Two',
|
title: '充值',
|
||||||
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
|
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="demo"
|
name="demo"
|
||||||
options={{
|
options={{
|
||||||
title: '完整示例',
|
title: '活动',
|
||||||
tabBarIcon: ({ color }) => <TabBarIcon name="rocket" color={color} />,
|
tabBarIcon: ({ color }) => <TabBarIcon name="rocket" color={color} />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="paper"
|
name="paper"
|
||||||
options={{
|
options={{
|
||||||
title: 'Paper UI',
|
title: '我的',
|
||||||
tabBarIcon: ({ color }) => <TabBarIcon name="paint-brush" color={color} />,
|
tabBarIcon: ({ color }) => <TabBarIcon name="paint-brush" color={color} />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -18,17 +18,21 @@ import {
|
|||||||
import { useForm, Controller } from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
|
import { useRouter } from 'expo-router';
|
||||||
|
|
||||||
|
// ✅ 扁平化导入:从根目录的各个模块导入
|
||||||
|
|
||||||
// 导入所有工具
|
|
||||||
import {
|
|
||||||
// 工具函数
|
// 工具函数
|
||||||
Storage,
|
import {
|
||||||
|
storageManager,
|
||||||
STORAGE_KEYS,
|
STORAGE_KEYS,
|
||||||
formatDate,
|
formatDate,
|
||||||
formatRelativeTime,
|
formatRelativeTime,
|
||||||
formatChatTime,
|
formatChatTime,
|
||||||
|
} from '@/utils';
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
|
import {
|
||||||
useUserStore,
|
useUserStore,
|
||||||
useUser,
|
useUser,
|
||||||
useIsLoggedIn,
|
useIsLoggedIn,
|
||||||
@@ -36,22 +40,23 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
useLanguage,
|
useLanguage,
|
||||||
useHapticsEnabled,
|
useHapticsEnabled,
|
||||||
|
} from '@/stores';
|
||||||
|
import { useTenantLoad, useTenantInfo } from '@/stores/tenantStore';
|
||||||
|
|
||||||
// 验证规则
|
// 验证规则
|
||||||
loginSchema,
|
import { loginSchema } from '@/schemas';
|
||||||
type LoginFormData,
|
import type { LoginFormData } from '@/schemas';
|
||||||
|
|
||||||
// API 服务
|
|
||||||
authService,
|
|
||||||
|
|
||||||
// 自定义 Hooks
|
// 自定义 Hooks
|
||||||
useDebounce,
|
import { useDebounce, useThrottle, useHaptics } from '@/hooks';
|
||||||
useThrottle,
|
|
||||||
useHaptics,
|
// 主题组件
|
||||||
} from '@/src';
|
import { ThemeDemo } from '@/components/ThemeDemo';
|
||||||
|
|
||||||
export default function DemoScreen() {
|
export default function DemoScreen() {
|
||||||
|
console.log('=== DemoScreen 组件已渲染 ===');
|
||||||
const haptics = useHaptics();
|
const haptics = useHaptics();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
// 状态管理示例
|
// 状态管理示例
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
@@ -59,13 +64,17 @@ export default function DemoScreen() {
|
|||||||
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 tenantLoad = useTenantLoad();
|
||||||
|
const tenantInfo = useTenantInfo();
|
||||||
|
|
||||||
// 设置状态
|
// 设置状态
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const language = useLanguage();
|
const language = useLanguage();
|
||||||
const hapticsEnabled = useHapticsEnabled();
|
const hapticsEnabled = useHapticsEnabled();
|
||||||
const setTheme = useSettingsStore((state) => state.setTheme);
|
const { setTheme, setLanguage, setHapticsEnabled } = useSettingsStore();
|
||||||
const setLanguage = useSettingsStore((state) => state.setLanguage);
|
// const setTheme = useSettingsStore((state) => state.setTheme);
|
||||||
const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled);
|
// const setLanguage = useSettingsStore((state) => state.setLanguage);
|
||||||
|
// const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled);
|
||||||
|
|
||||||
// 本地状态
|
// 本地状态
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
@@ -73,6 +82,7 @@ export default function DemoScreen() {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [counter, setCounter] = useState(0);
|
const [counter, setCounter] = useState(0);
|
||||||
const [storageValue, setStorageValue] = useState('');
|
const [storageValue, setStorageValue] = useState('');
|
||||||
|
const [sessionValue, setSessionValue] = useState('');
|
||||||
|
|
||||||
// 表单配置
|
// 表单配置
|
||||||
const {
|
const {
|
||||||
@@ -89,6 +99,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,11 +108,7 @@ 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);
|
||||||
|
|
||||||
// 监听搜索文本变化
|
// 监听搜索文本变化
|
||||||
@@ -153,10 +160,7 @@ export default function DemoScreen() {
|
|||||||
// 登出处理
|
// 登出处理
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
haptics.warning();
|
haptics.warning();
|
||||||
Alert.alert(
|
Alert.alert('确认', '确定要退出登录吗?', [
|
||||||
'确认',
|
|
||||||
'确定要退出登录吗?',
|
|
||||||
[
|
|
||||||
{ text: '取消', style: 'cancel' },
|
{ text: '取消', style: 'cancel' },
|
||||||
{
|
{
|
||||||
text: '确定',
|
text: '确定',
|
||||||
@@ -165,8 +169,7 @@ export default function DemoScreen() {
|
|||||||
haptics.success();
|
haptics.success();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 存储示例
|
// 存储示例
|
||||||
@@ -179,7 +182,7 @@ export default function DemoScreen() {
|
|||||||
counter,
|
counter,
|
||||||
};
|
};
|
||||||
|
|
||||||
await Storage.setObject(STORAGE_KEYS.USER_PREFERENCES, testData);
|
storageManager.session.setItem(STORAGE_KEYS.USER_PREFERENCES, testData);
|
||||||
haptics.success();
|
haptics.success();
|
||||||
Alert.alert('成功', '数据已保存到本地存储');
|
Alert.alert('成功', '数据已保存到本地存储');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -191,7 +194,7 @@ export default function DemoScreen() {
|
|||||||
const handleLoadFromStorage = async () => {
|
const handleLoadFromStorage = async () => {
|
||||||
try {
|
try {
|
||||||
haptics.light();
|
haptics.light();
|
||||||
const data = await Storage.getObject<any>(STORAGE_KEYS.USER_PREFERENCES);
|
const data = storageManager.session.getItem(STORAGE_KEYS.USER_PREFERENCES);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
setStorageValue(JSON.stringify(data, null, 2));
|
setStorageValue(JSON.stringify(data, null, 2));
|
||||||
@@ -206,11 +209,64 @@ export default function DemoScreen() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// SessionStorage 处理函数
|
||||||
|
const handleSaveToSession = () => {
|
||||||
|
try {
|
||||||
|
haptics.light();
|
||||||
|
const testData = {
|
||||||
|
formDraft: {
|
||||||
|
title: '草稿标题',
|
||||||
|
content: '这是一个表单草稿示例',
|
||||||
|
},
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
counter: Math.floor(Math.random() * 100),
|
||||||
|
};
|
||||||
|
|
||||||
|
storageManager.session.setItem(STORAGE_KEYS.FORM_DRAFT, testData);
|
||||||
|
haptics.success();
|
||||||
|
Alert.alert('成功', '数据已保存到会话存储(应用重启后会丢失)');
|
||||||
|
} catch (error) {
|
||||||
|
haptics.error();
|
||||||
|
Alert.alert('失败', '保存失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLoadFromSession = () => {
|
||||||
|
try {
|
||||||
|
haptics.light();
|
||||||
|
const data = storageManager.session.getItem(STORAGE_KEYS.FORM_DRAFT);
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
setSessionValue(JSON.stringify(data, null, 2));
|
||||||
|
haptics.success();
|
||||||
|
} else {
|
||||||
|
setSessionValue('暂无数据(会话存储为空)');
|
||||||
|
haptics.warning();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
haptics.error();
|
||||||
|
Alert.alert('失败', '读取失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearSession = () => {
|
||||||
|
try {
|
||||||
|
haptics.light();
|
||||||
|
storageManager.session.clear();
|
||||||
|
setSessionValue('');
|
||||||
|
haptics.success();
|
||||||
|
Alert.alert('成功', '会话存储已清空');
|
||||||
|
} catch (error) {
|
||||||
|
haptics.error();
|
||||||
|
Alert.alert('失败', '清空失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 主题切换
|
// 主题切换
|
||||||
const handleThemeChange = () => {
|
const handleThemeChange = () => {
|
||||||
haptics.selection();
|
haptics.selection();
|
||||||
const themes: Array<'light' | 'dark' | 'auto'> = ['light', 'dark', 'auto'];
|
const themes: Array<'light' | 'dark' | 'auto'> = ['light', 'dark', 'auto'];
|
||||||
const currentIndex = themes.indexOf(theme);
|
const currentIndex = themes.indexOf(theme as 'light' | 'dark' | 'auto');
|
||||||
const nextTheme = themes[(currentIndex + 1) % themes.length];
|
const nextTheme = themes[(currentIndex + 1) % themes.length];
|
||||||
setTheme(nextTheme);
|
setTheme(nextTheme);
|
||||||
};
|
};
|
||||||
@@ -228,18 +284,64 @@ export default function DemoScreen() {
|
|||||||
<Text style={styles.title}>🎯 完整功能演示</Text>
|
<Text style={styles.title}>🎯 完整功能演示</Text>
|
||||||
<Text style={styles.subtitle}>展示所有工具的使用方法</Text>
|
<Text style={styles.subtitle}>展示所有工具的使用方法</Text>
|
||||||
|
|
||||||
|
{/* 页面导航 */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>📱 页面导航</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, styles.primaryButton]}
|
||||||
|
onPress={() => {
|
||||||
|
haptics.light();
|
||||||
|
router.push('/test');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>跳转到测试页面 →</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.infoText}>测试页面是一个独立的业务页面示例,不包含底部 tabs</Text>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, { backgroundColor: '#9333ea', marginTop: 12 }]}
|
||||||
|
onPress={() => {
|
||||||
|
haptics.light();
|
||||||
|
router.push('/theme-test');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>🎨 主题测试页面 →</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.infoText}>专门的主题测试页面,可以清楚地看到主题切换效果</Text>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, { backgroundColor: '#06b6d4', marginTop: 12 }]}
|
||||||
|
onPress={() => {
|
||||||
|
haptics.light();
|
||||||
|
router.push('/theme-example');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>📚 主题系统示例 →</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.infoText}>展示四种主题样式使用方式(类似 CSS 类名)</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 租户信息显示 */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>🏢 租户信息</Text>
|
||||||
|
<Text style={styles.infoText}>状态: {tenantLoad ? '✅ 已加载' : '❌ 未加载'}</Text>
|
||||||
|
{tenantInfo ? (
|
||||||
|
<>
|
||||||
|
<Text style={styles.infoText}>TID: {tenantInfo.tid || '无'}</Text>
|
||||||
|
<Text style={styles.infoText}>创建时间: {tenantInfo.create_time || '无'}</Text>
|
||||||
|
<Text style={styles.infoText}>域名: {tenantInfo.domain_addr || '无'}</Text>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* 用户状态显示 */}
|
{/* 用户状态显示 */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>👤 用户状态 (Zustand)</Text>
|
<Text style={styles.sectionTitle}>👤 用户状态 (Zustand)</Text>
|
||||||
{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 }}
|
) : null}
|
||||||
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>
|
||||||
<Text style={styles.userEmail}>{user?.email}</Text>
|
<Text style={styles.userEmail}>{user?.email}</Text>
|
||||||
@@ -247,10 +349,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>
|
||||||
@@ -260,7 +359,7 @@ export default function DemoScreen() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 登录表单 */}
|
{/* 登录表单 */}
|
||||||
{!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>
|
||||||
|
|
||||||
@@ -276,14 +375,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,14 +393,11 @@ 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>
|
||||||
)}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -323,7 +414,7 @@ export default function DemoScreen() {
|
|||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
)}
|
) : null}
|
||||||
|
|
||||||
{/* 搜索示例 */}
|
{/* 搜索示例 */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
@@ -334,7 +425,7 @@ export default function DemoScreen() {
|
|||||||
placeholder="输入搜索内容..."
|
placeholder="输入搜索内容..."
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
/>
|
/>
|
||||||
{searchResults.length > 0 && (
|
{searchResults.length > 0 ? (
|
||||||
<View style={styles.searchResults}>
|
<View style={styles.searchResults}>
|
||||||
{searchResults.map((result, index) => (
|
{searchResults.map((result, index) => (
|
||||||
<Text key={index} style={styles.searchResult}>
|
<Text key={index} style={styles.searchResult}>
|
||||||
@@ -342,17 +433,14 @@ export default function DemoScreen() {
|
|||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
)}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 节流点击示例 */}
|
{/* 节流点击示例 */}
|
||||||
<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>
|
||||||
@@ -374,11 +462,44 @@ export default function DemoScreen() {
|
|||||||
<Text style={styles.buttonText}>读取数据</Text>
|
<Text style={styles.buttonText}>读取数据</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
{storageValue && (
|
{storageValue ? (
|
||||||
<View style={styles.codeBlock}>
|
<View style={styles.codeBlock}>
|
||||||
<Text style={styles.codeText}>{storageValue}</Text>
|
<Text style={styles.codeText}>{storageValue}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
) : null}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 会话存储示例 */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>🔄 会话存储 (SessionStorage)</Text>
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
会话存储数据保存在内存中,应用重启后会丢失,适合临时数据
|
||||||
|
</Text>
|
||||||
|
<View style={styles.buttonRow}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, styles.primaryButton, styles.thirdButton]}
|
||||||
|
onPress={handleSaveToSession}
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>保存</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, styles.secondaryButton, styles.thirdButton]}
|
||||||
|
onPress={handleLoadFromSession}
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>读取</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, styles.errorButton, styles.thirdButton]}
|
||||||
|
onPress={handleClearSession}
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>清空</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
{sessionValue ? (
|
||||||
|
<View style={styles.codeBlock}>
|
||||||
|
<Text style={styles.codeText}>{sessionValue}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 日期格式化示例 */}
|
{/* 日期格式化示例 */}
|
||||||
@@ -387,12 +508,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>
|
</View>
|
||||||
<Text style={styles.infoText}>
|
|
||||||
聊天时间: {formatChatTime(Date.now())}
|
{/* 主题演示 */}
|
||||||
</Text>
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>🎨 主题系统演示</Text>
|
||||||
|
<ThemeDemo />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 设置示例 */}
|
{/* 设置示例 */}
|
||||||
@@ -475,9 +598,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,11 +728,15 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
buttonRow: {
|
buttonRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
gap: 12,
|
justifyContent: 'space-between',
|
||||||
},
|
},
|
||||||
halfButton: {
|
halfButton: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
thirdButton: {
|
||||||
|
flex: 1,
|
||||||
|
marginHorizontal: 4,
|
||||||
|
},
|
||||||
searchResults: {
|
searchResults: {
|
||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
},
|
},
|
||||||
@@ -651,27 +776,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 +812,3 @@ const styles = StyleSheet.create({
|
|||||||
color: '#999',
|
color: '#999',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,192 +1,23 @@
|
|||||||
import { useState } from 'react';
|
/**
|
||||||
import { StyleSheet, TouchableOpacity, Alert, ActivityIndicator } from 'react-native';
|
* 首页 - 游戏大厅
|
||||||
import * as Updates from 'expo-updates';
|
*
|
||||||
|
* 重构自 xinyong-web 项目的首页
|
||||||
|
* 支持浅色/深色主题,包含轮播图、分类菜单、游戏大厅等功能
|
||||||
|
*/
|
||||||
|
|
||||||
import { Text, View } from '@/components/Themed';
|
import { Stack } from 'expo-router';
|
||||||
|
import HomeScreen from '@/pages/HomeScreen';
|
||||||
export default function TabOneScreen() {
|
|
||||||
const [isChecking, setIsChecking] = useState(false);
|
|
||||||
const [updateInfo, setUpdateInfo] = useState<string>('');
|
|
||||||
|
|
||||||
const checkForUpdates = async () => {
|
|
||||||
if (__DEV__) {
|
|
||||||
Alert.alert('提示', '开发模式下无法检查更新,请使用生产构建测试热更新功能');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsChecking(true);
|
|
||||||
setUpdateInfo('正在检查更新...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const update = await Updates.checkForUpdateAsync();
|
|
||||||
|
|
||||||
if (update.isAvailable) {
|
|
||||||
setUpdateInfo('发现新版本,正在下载...');
|
|
||||||
await Updates.fetchUpdateAsync();
|
|
||||||
|
|
||||||
Alert.alert(
|
|
||||||
'更新完成',
|
|
||||||
'新版本已下载完成,是否立即重启应用?',
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: '稍后',
|
|
||||||
style: 'cancel',
|
|
||||||
onPress: () => setUpdateInfo('更新已下载,稍后重启应用即可应用'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '立即重启',
|
|
||||||
onPress: async () => {
|
|
||||||
await Updates.reloadAsync();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setUpdateInfo('当前已是最新版本');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setUpdateInfo('检查更新失败: ' + (error as Error).message);
|
|
||||||
Alert.alert('错误', '检查更新失败,请稍后重试');
|
|
||||||
} finally {
|
|
||||||
setIsChecking(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUpdateInfo = () => {
|
|
||||||
const {
|
|
||||||
isEmbeddedLaunch,
|
|
||||||
isEmergencyLaunch,
|
|
||||||
updateId,
|
|
||||||
channel,
|
|
||||||
runtimeVersion,
|
|
||||||
} = Updates.useUpdates();
|
|
||||||
|
|
||||||
return `
|
|
||||||
运行模式: ${__DEV__ ? '开发模式' : '生产模式'}
|
|
||||||
是否为内嵌启动: ${isEmbeddedLaunch ? '是' : '否'}
|
|
||||||
是否为紧急启动: ${isEmergencyLaunch ? '是' : '否'}
|
|
||||||
更新 ID: ${updateId || '无'}
|
|
||||||
更新通道: ${channel || '无'}
|
|
||||||
运行时版本: ${runtimeVersion || '无'}
|
|
||||||
`.trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export default function TabHoneScreen() {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<>
|
||||||
<Text style={styles.title}>🚀 热更新演示</Text>
|
<Stack.Screen
|
||||||
<View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
|
options={{
|
||||||
|
title: '首页',
|
||||||
<View style={styles.infoContainer}>
|
headerShown: false,
|
||||||
<Text style={styles.infoTitle}>当前版本信息:</Text>
|
}}
|
||||||
<Text style={styles.infoText}>{getUpdateInfo()}</Text>
|
/>
|
||||||
</View>
|
<HomeScreen />
|
||||||
|
</>
|
||||||
<TouchableOpacity
|
|
||||||
style={[styles.button, isChecking && styles.buttonDisabled]}
|
|
||||||
onPress={checkForUpdates}
|
|
||||||
disabled={isChecking}
|
|
||||||
>
|
|
||||||
{isChecking ? (
|
|
||||||
<ActivityIndicator color="#fff" />
|
|
||||||
) : (
|
|
||||||
<Text style={styles.buttonText}>检查更新</Text>
|
|
||||||
)}
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
{updateInfo ? (
|
|
||||||
<View style={styles.updateInfoContainer}>
|
|
||||||
<Text style={styles.updateInfoText}>{updateInfo}</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<View style={styles.instructionsContainer}>
|
|
||||||
<Text style={styles.instructionsTitle}>📝 使用说明:</Text>
|
|
||||||
<Text style={styles.instructionsText}>
|
|
||||||
1. 使用 EAS Build 构建生产版本{'\n'}
|
|
||||||
2. 修改代码后运行 eas update 发布更新{'\n'}
|
|
||||||
3. 打开应用点击"检查更新"按钮{'\n'}
|
|
||||||
4. 应用会自动下载并提示重启
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
padding: 20,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontSize: 28,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
separator: {
|
|
||||||
marginVertical: 20,
|
|
||||||
height: 1,
|
|
||||||
width: '80%',
|
|
||||||
},
|
|
||||||
infoContainer: {
|
|
||||||
backgroundColor: 'rgba(0, 122, 255, 0.1)',
|
|
||||||
padding: 15,
|
|
||||||
borderRadius: 10,
|
|
||||||
marginBottom: 20,
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
infoTitle: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
infoText: {
|
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
lineHeight: 18,
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
backgroundColor: '#007AFF',
|
|
||||||
paddingHorizontal: 30,
|
|
||||||
paddingVertical: 15,
|
|
||||||
borderRadius: 10,
|
|
||||||
marginBottom: 20,
|
|
||||||
minWidth: 200,
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
buttonDisabled: {
|
|
||||||
backgroundColor: '#999',
|
|
||||||
},
|
|
||||||
buttonText: {
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
updateInfoContainer: {
|
|
||||||
backgroundColor: 'rgba(52, 199, 89, 0.1)',
|
|
||||||
padding: 15,
|
|
||||||
borderRadius: 10,
|
|
||||||
marginBottom: 20,
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
updateInfoText: {
|
|
||||||
fontSize: 14,
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
instructionsContainer: {
|
|
||||||
backgroundColor: 'rgba(255, 149, 0, 0.1)',
|
|
||||||
padding: 15,
|
|
||||||
borderRadius: 10,
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
instructionsTitle: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
instructionsText: {
|
|
||||||
fontSize: 13,
|
|
||||||
lineHeight: 20,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -82,7 +82,11 @@ 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 +94,14 @@ 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,10 +145,15 @@ 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
|
||||||
@@ -152,6 +161,7 @@ export default function PaperDemo() {
|
|||||||
onPress={() => {}}
|
onPress={() => {}}
|
||||||
onClose={() => {}}
|
onClose={() => {}}
|
||||||
closeIcon="close-circle"
|
closeIcon="close-circle"
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
>
|
>
|
||||||
可关闭
|
可关闭
|
||||||
</Chip>
|
</Chip>
|
||||||
@@ -221,12 +231,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 +284,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
buttonRow: {
|
buttonRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
gap: 8,
|
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
row: {
|
row: {
|
||||||
@@ -291,7 +295,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 +309,3 @@ const styles = StyleSheet.create({
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import { Alert, Platform } from 'react-native';
|
|||||||
import 'react-native-reanimated';
|
import 'react-native-reanimated';
|
||||||
import { PaperProvider, MD3DarkTheme, MD3LightTheme } from 'react-native-paper';
|
import { PaperProvider, MD3DarkTheme, MD3LightTheme } from 'react-native-paper';
|
||||||
|
|
||||||
import { useColorScheme } from '@/components/useColorScheme';
|
// ✅ 从 hooks 目录导入
|
||||||
|
import { useColorScheme } from '@/hooks';
|
||||||
|
// ✅ 从 stores 目录导入
|
||||||
|
import { restoreUserState, restoreSettingsState } from '@/stores';
|
||||||
|
import { requestTenantInfo } from '@/stores/tenantStore';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
// Catch any errors thrown by the Layout component.
|
// Catch any errors thrown by the Layout component.
|
||||||
@@ -41,6 +45,31 @@ export default function RootLayout() {
|
|||||||
}
|
}
|
||||||
}, [loaded]);
|
}, [loaded]);
|
||||||
|
|
||||||
|
// 恢复持久化状态并初始化应用数据
|
||||||
|
useEffect(() => {
|
||||||
|
async function initializeApp() {
|
||||||
|
try {
|
||||||
|
// 1. 恢复本地存储的状态
|
||||||
|
await Promise.all([restoreUserState(), restoreSettingsState()]);
|
||||||
|
|
||||||
|
// 2. 调用初始化接口(获取平台数据等)
|
||||||
|
if (__DEV__) {
|
||||||
|
console.log('🚀 Initializing app data...');
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestTenantInfo();
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
console.log('✅ Platform data loaded:');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize app:', error);
|
||||||
|
// 初始化失败不应该阻止应用启动,只记录错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initializeApp();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 检查热更新
|
// 检查热更新
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function checkForUpdates() {
|
async function checkForUpdates() {
|
||||||
@@ -56,10 +85,7 @@ export default function RootLayout() {
|
|||||||
await Updates.fetchUpdateAsync();
|
await Updates.fetchUpdateAsync();
|
||||||
|
|
||||||
// 提示用户重启应用以应用更新
|
// 提示用户重启应用以应用更新
|
||||||
Alert.alert(
|
Alert.alert('更新可用', '发现新版本,是否立即重启应用?', [
|
||||||
'更新可用',
|
|
||||||
'发现新版本,是否立即重启应用?',
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
text: '稍后',
|
text: '稍后',
|
||||||
style: 'cancel',
|
style: 'cancel',
|
||||||
@@ -70,8 +96,7 @@ export default function RootLayout() {
|
|||||||
await Updates.reloadAsync();
|
await Updates.reloadAsync();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 处理更新检查错误
|
// 处理更新检查错误
|
||||||
|
|||||||
2
app/test.tsx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import { TestPage } from '@/pages';
|
||||||
|
export default TestPage;
|
||||||
254
app/theme-example.tsx
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
/**
|
||||||
|
* 主题系统使用示例
|
||||||
|
*
|
||||||
|
* 展示四种不同的主题样式使用方式
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ScrollView, View, Text, TouchableOpacity } from 'react-native';
|
||||||
|
import { Stack } from 'expo-router';
|
||||||
|
import {
|
||||||
|
useColorScheme,
|
||||||
|
useThemeColors,
|
||||||
|
useThemeInfo,
|
||||||
|
commonStyles,
|
||||||
|
createThemeStyles,
|
||||||
|
ThemedText,
|
||||||
|
ThemedView,
|
||||||
|
} from '@/theme';
|
||||||
|
|
||||||
|
// 方式 3: 创建自定义主题样式(推荐用于复杂组件)
|
||||||
|
const customStyles = createThemeStyles((colors) => ({
|
||||||
|
header: {
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
padding: 20,
|
||||||
|
borderRadius: 12,
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
headerText: {
|
||||||
|
color: '#FFFFFF',
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
backgroundColor: colors.card,
|
||||||
|
padding: 16,
|
||||||
|
borderRadius: 8,
|
||||||
|
marginBottom: 16,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
color: colors.text,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
codeBlock: {
|
||||||
|
backgroundColor: colors.backgroundTertiary,
|
||||||
|
padding: 12,
|
||||||
|
borderRadius: 6,
|
||||||
|
marginTop: 8,
|
||||||
|
},
|
||||||
|
codeText: {
|
||||||
|
color: colors.textSecondary,
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function ThemeExampleScreen() {
|
||||||
|
const theme = useColorScheme();
|
||||||
|
const colors = useThemeColors();
|
||||||
|
const { isDark } = useThemeInfo();
|
||||||
|
const s = commonStyles[theme]; // 通用样式类
|
||||||
|
const custom = customStyles[theme]; // 自定义样式
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack.Screen
|
||||||
|
options={{
|
||||||
|
title: '主题系统示例',
|
||||||
|
headerStyle: {
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
},
|
||||||
|
headerTintColor: colors.text,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ScrollView style={s.containerPadded}>
|
||||||
|
{/* 自定义样式示例 */}
|
||||||
|
<View style={custom.header}>
|
||||||
|
<Text style={custom.headerText}>主题系统使用示例</Text>
|
||||||
|
<Text style={[custom.headerText, { fontSize: 14, marginTop: 8 }]}>
|
||||||
|
当前主题: {theme} {isDark ? '🌙' : '☀️'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 方式 1: 使用主题组件 */}
|
||||||
|
<View style={custom.section}>
|
||||||
|
<Text style={custom.sectionTitle}>方式 1: 使用主题组件</Text>
|
||||||
|
<ThemedView style={{ padding: 12, borderRadius: 6 }}>
|
||||||
|
<ThemedText type="title">这是标题</ThemedText>
|
||||||
|
<ThemedText type="subtitle">这是副标题</ThemedText>
|
||||||
|
<ThemedText>这是普通文本</ThemedText>
|
||||||
|
</ThemedView>
|
||||||
|
<View style={custom.codeBlock}>
|
||||||
|
<Text style={custom.codeText}>
|
||||||
|
{`import { ThemedText, ThemedView } from '@/theme';
|
||||||
|
|
||||||
|
<ThemedView>
|
||||||
|
<ThemedText type="title">标题</ThemedText>
|
||||||
|
</ThemedView>`}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 方式 2: 使用通用样式类 */}
|
||||||
|
<View style={custom.section}>
|
||||||
|
<Text style={custom.sectionTitle}>方式 2: 使用通用样式类(类似 CSS 类名)</Text>
|
||||||
|
<View style={s.card}>
|
||||||
|
<Text style={s.textTitle}>卡片标题</Text>
|
||||||
|
<Text style={s.textSecondary}>卡片描述文本</Text>
|
||||||
|
<View style={s.spacingMd} />
|
||||||
|
<TouchableOpacity style={s.button}>
|
||||||
|
<Text style={s.buttonText}>主要按钮</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={s.spacingSm} />
|
||||||
|
<TouchableOpacity style={s.buttonOutline}>
|
||||||
|
<Text style={s.buttonTextOutline}>次要按钮</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
<View style={custom.codeBlock}>
|
||||||
|
<Text style={custom.codeText}>
|
||||||
|
{`import { useColorScheme, commonStyles } from '@/theme';
|
||||||
|
|
||||||
|
const theme = useColorScheme();
|
||||||
|
const s = commonStyles[theme];
|
||||||
|
|
||||||
|
<View style={s.card}>
|
||||||
|
<Text style={s.textTitle}>标题</Text>
|
||||||
|
<TouchableOpacity style={s.button}>
|
||||||
|
<Text style={s.buttonText}>按钮</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>`}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 方式 3: 使用自定义主题样式 */}
|
||||||
|
<View style={custom.section}>
|
||||||
|
<Text style={custom.sectionTitle}>方式 3: 使用自定义主题样式(推荐)</Text>
|
||||||
|
<View style={{ padding: 12 }}>
|
||||||
|
<Text style={{ color: colors.text }}>
|
||||||
|
本页面的 header 和 section 就是使用自定义主题样式创建的
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={custom.codeBlock}>
|
||||||
|
<Text style={custom.codeText}>
|
||||||
|
{`import { createThemeStyles } from '@/theme';
|
||||||
|
|
||||||
|
const styles = createThemeStyles((colors) => ({
|
||||||
|
header: {
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
headerText: {
|
||||||
|
color: '#FFFFFF',
|
||||||
|
fontSize: 24,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const theme = useColorScheme();
|
||||||
|
<View style={styles[theme].header}>
|
||||||
|
<Text style={styles[theme].headerText}>标题</Text>
|
||||||
|
</View>`}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 方式 4: 使用 Hooks + 内联样式 */}
|
||||||
|
<View style={custom.section}>
|
||||||
|
<Text style={custom.sectionTitle}>方式 4: 使用 Hooks + 内联样式(动态场景)</Text>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.backgroundSecondary,
|
||||||
|
padding: 16,
|
||||||
|
borderRadius: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={{ color: colors.text, fontSize: 16 }}>
|
||||||
|
这是使用 useThemeColors() 动态获取颜色的文本
|
||||||
|
</Text>
|
||||||
|
<View style={{ height: 12 }} />
|
||||||
|
<Text style={{ color: colors.textSecondary, fontSize: 14 }}>
|
||||||
|
适合需要动态计算样式的场景
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={custom.codeBlock}>
|
||||||
|
<Text style={custom.codeText}>
|
||||||
|
{`import { useThemeColors } from '@/theme';
|
||||||
|
|
||||||
|
const colors = useThemeColors();
|
||||||
|
|
||||||
|
<View style={{
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
padding: 16,
|
||||||
|
}}>
|
||||||
|
<Text style={{ color: colors.text }}>
|
||||||
|
动态文本
|
||||||
|
</Text>
|
||||||
|
</View>`}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 颜色展示 */}
|
||||||
|
<View style={custom.section}>
|
||||||
|
<Text style={custom.sectionTitle}>主题颜色展示</Text>
|
||||||
|
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>
|
||||||
|
{Object.entries(colors).map(([key, value]) => (
|
||||||
|
<View
|
||||||
|
key={key}
|
||||||
|
style={{
|
||||||
|
backgroundColor: value as string,
|
||||||
|
padding: 8,
|
||||||
|
borderRadius: 6,
|
||||||
|
minWidth: 100,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color:
|
||||||
|
key.includes('background') || key.includes('card') || key.includes('input')
|
||||||
|
? colors.text
|
||||||
|
: '#FFFFFF',
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: '600',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{key}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color:
|
||||||
|
key.includes('background') || key.includes('card') || key.includes('input')
|
||||||
|
? colors.textSecondary
|
||||||
|
: '#FFFFFF',
|
||||||
|
fontSize: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 底部间距 */}
|
||||||
|
<View style={s.spacingXl} />
|
||||||
|
</ScrollView>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
330
app/theme-test.tsx
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
import {
|
||||||
|
StyleSheet,
|
||||||
|
ScrollView,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
useColorScheme as useSystemColorScheme,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Stack } from 'expo-router';
|
||||||
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
import { useTheme, useSettingsStore } from '@/stores';
|
||||||
|
import { useHaptics } from '@/hooks';
|
||||||
|
import { Colors } from '@/theme';
|
||||||
|
|
||||||
|
export default function ThemeTestScreen() {
|
||||||
|
const currentTheme = useTheme();
|
||||||
|
const { setTheme } = useSettingsStore();
|
||||||
|
const haptics = useHaptics();
|
||||||
|
const systemColorScheme = useSystemColorScheme();
|
||||||
|
|
||||||
|
// 强制重新渲染的状态
|
||||||
|
const [renderKey, setRenderKey] = useState(0);
|
||||||
|
|
||||||
|
// 直接计算实际应用的主题 - 不使用 useColorScheme hook
|
||||||
|
const actualTheme: 'light' | 'dark' = useMemo(() => {
|
||||||
|
return currentTheme === 'auto'
|
||||||
|
? systemColorScheme === 'dark'
|
||||||
|
? 'dark'
|
||||||
|
: 'light'
|
||||||
|
: currentTheme;
|
||||||
|
}, [currentTheme, systemColorScheme]);
|
||||||
|
|
||||||
|
// 使用 useMemo 确保颜色对象在主题改变时重新计算
|
||||||
|
const colors = useMemo(() => {
|
||||||
|
console.log('🎨 Recalculating colors for theme:', actualTheme);
|
||||||
|
return Colors[actualTheme] as Record<string, any>;
|
||||||
|
}, [actualTheme]);
|
||||||
|
|
||||||
|
// 监听主题变化
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('🎨 Theme changed:', { currentTheme, systemColorScheme, actualTheme, renderKey });
|
||||||
|
setRenderKey((prev) => prev + 1);
|
||||||
|
}, [currentTheme, systemColorScheme, actualTheme]);
|
||||||
|
|
||||||
|
const handleThemeChange = (newTheme: 'light' | 'dark' | 'auto') => {
|
||||||
|
haptics.selection();
|
||||||
|
console.log('🎨 Changing theme to:', newTheme);
|
||||||
|
setTheme(newTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack.Screen
|
||||||
|
options={{
|
||||||
|
title: '主题测试',
|
||||||
|
headerStyle: {
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
},
|
||||||
|
headerTintColor: colors.text,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ScrollView style={[styles.container, { backgroundColor: colors.background }]}>
|
||||||
|
{/* 主题信息 */}
|
||||||
|
<View style={[styles.section, { backgroundColor: colors.backgroundSecondary }]}>
|
||||||
|
<Text style={[styles.title, { color: colors.text }]}>当前主题信息</Text>
|
||||||
|
<Text style={[styles.infoText, { color: colors.textSecondary }]}>
|
||||||
|
用户设置: {currentTheme}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.infoText, { color: colors.textSecondary }]}>
|
||||||
|
系统主题: {systemColorScheme || 'light'}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.infoText, { color: colors.textSecondary }]}>
|
||||||
|
实际应用: {actualTheme}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.infoText, { color: colors.textSecondary }]}>
|
||||||
|
渲染次数: {renderKey}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 主题切换按钮 */}
|
||||||
|
<View style={[styles.section, { backgroundColor: colors.backgroundSecondary }]}>
|
||||||
|
<Text style={[styles.title, { color: colors.text }]}>切换主题</Text>
|
||||||
|
<View style={styles.buttonRow}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.themeButton,
|
||||||
|
{
|
||||||
|
backgroundColor:
|
||||||
|
currentTheme === 'light' ? colors.primary : colors.backgroundTertiary,
|
||||||
|
borderColor: colors.border,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onPress={() => handleThemeChange('light')}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.buttonText,
|
||||||
|
{ color: currentTheme === 'light' ? '#fff' : colors.text },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
☀️ 浅色
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.themeButton,
|
||||||
|
{
|
||||||
|
backgroundColor:
|
||||||
|
currentTheme === 'dark' ? colors.primary : colors.backgroundTertiary,
|
||||||
|
borderColor: colors.border,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onPress={() => handleThemeChange('dark')}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.buttonText,
|
||||||
|
{ color: currentTheme === 'dark' ? '#fff' : colors.text },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
🌙 深色
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.themeButton,
|
||||||
|
{
|
||||||
|
backgroundColor:
|
||||||
|
currentTheme === 'auto' ? colors.primary : colors.backgroundTertiary,
|
||||||
|
borderColor: colors.border,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onPress={() => handleThemeChange('auto')}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.buttonText,
|
||||||
|
{ color: currentTheme === 'auto' ? '#fff' : colors.text },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
🔄 自动
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 文本颜色展示 */}
|
||||||
|
<View style={[styles.section, { backgroundColor: colors.backgroundSecondary }]}>
|
||||||
|
<Text style={[styles.title, { color: colors.text }]}>文本颜色</Text>
|
||||||
|
<Text style={[styles.colorText, { color: colors.text }]}>
|
||||||
|
Primary Text - {colors.text}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.colorText, { color: colors.textSecondary }]}>
|
||||||
|
Secondary Text - {colors.textSecondary}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.colorText, { color: colors.textTertiary }]}>
|
||||||
|
Tertiary Text - {colors.textTertiary}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 背景颜色展示 */}
|
||||||
|
<View style={[styles.section, { backgroundColor: colors.backgroundSecondary }]}>
|
||||||
|
<Text style={[styles.title, { color: colors.text }]}>背景颜色</Text>
|
||||||
|
<View style={[styles.colorBox, { backgroundColor: colors.background }]}>
|
||||||
|
<Text style={[styles.colorText, { color: colors.text }]}>
|
||||||
|
Primary Background - {colors.background}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.colorBox,
|
||||||
|
{
|
||||||
|
backgroundColor: colors.backgroundSecondary,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text style={[styles.colorText, { color: colors.text }]}>
|
||||||
|
Secondary Background - {colors.backgroundSecondary}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.colorBox, { backgroundColor: colors.backgroundTertiary }]}>
|
||||||
|
<Text style={[styles.colorText, { color: colors.text }]}>
|
||||||
|
Tertiary Background - {colors.backgroundTertiary}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 主题颜色展示 */}
|
||||||
|
<View style={[styles.section, { backgroundColor: colors.backgroundSecondary }]}>
|
||||||
|
<Text style={[styles.title, { color: colors.text }]}>主题颜色</Text>
|
||||||
|
<View style={styles.colorGrid}>
|
||||||
|
<View style={[styles.colorBox, { backgroundColor: colors.primary }]}>
|
||||||
|
<Text style={[styles.colorText, { color: '#fff' }]}>Primary - {colors.primary}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.colorBox, { backgroundColor: colors.secondary }]}>
|
||||||
|
<Text style={[styles.colorText, { color: '#fff' }]}>
|
||||||
|
Secondary - {colors.secondary}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.colorBox, { backgroundColor: colors.success }]}>
|
||||||
|
<Text style={[styles.colorText, { color: '#fff' }]}>Success - {colors.success}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.colorBox, { backgroundColor: colors.warning }]}>
|
||||||
|
<Text style={[styles.colorText, { color: '#fff' }]}>Warning - {colors.warning}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.colorBox, { backgroundColor: colors.error }]}>
|
||||||
|
<Text style={[styles.colorText, { color: '#fff' }]}>Error - {colors.error}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.colorBox, { backgroundColor: colors.info }]}>
|
||||||
|
<Text style={[styles.colorText, { color: '#fff' }]}>Info - {colors.info}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* UI 元素展示 */}
|
||||||
|
<View style={[styles.section, { backgroundColor: colors.backgroundSecondary }]}>
|
||||||
|
<Text style={[styles.title, { color: colors.text }]}>UI 元素</Text>
|
||||||
|
<View style={[styles.card, { backgroundColor: colors.card, borderColor: colors.border }]}>
|
||||||
|
<Text style={[styles.cardText, { color: colors.text }]}>这是一个卡片组件</Text>
|
||||||
|
<Text style={[styles.cardSubtext, { color: colors.textSecondary }]}>
|
||||||
|
卡片背景色: {colors.card}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.input,
|
||||||
|
{ backgroundColor: colors.inputBackground, borderColor: colors.inputBorder },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text style={[styles.inputText, { color: colors.textSecondary }]}>输入框样式预览</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[styles.separator, { backgroundColor: colors.separator }]} />
|
||||||
|
|
||||||
|
<TouchableOpacity style={[styles.button, { backgroundColor: colors.buttonPrimary }]}>
|
||||||
|
<Text style={[styles.buttonText, { color: '#fff' }]}>按钮示例</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
margin: 16,
|
||||||
|
padding: 16,
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
fontSize: 16,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
buttonRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
themeButton: {
|
||||||
|
flex: 1,
|
||||||
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
borderRadius: 8,
|
||||||
|
borderWidth: 2,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
colorText: {
|
||||||
|
fontSize: 14,
|
||||||
|
marginBottom: 8,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
},
|
||||||
|
colorBox: {
|
||||||
|
padding: 16,
|
||||||
|
borderRadius: 8,
|
||||||
|
marginBottom: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
colorGrid: {
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
padding: 16,
|
||||||
|
borderRadius: 8,
|
||||||
|
borderWidth: 1,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
cardText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
cardSubtext: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
padding: 12,
|
||||||
|
borderRadius: 8,
|
||||||
|
borderWidth: 1,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
inputText: {
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
height: 1,
|
||||||
|
marginVertical: 12,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
padding: 16,
|
||||||
|
borderRadius: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
BIN
assets/images/game/blockThird/color_1390.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/images/game/blockThird/color_1391.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
assets/images/game/blockThird/color_1392.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/images/game/blockThird/color_1560.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
assets/images/game/blockThird/h_1390.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/images/game/blockThird/h_1391.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/images/game/blockThird/h_1391.webp
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
assets/images/game/blockThird/h_1392.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
assets/images/game/blockThird/h_1560.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
assets/images/game/blockThird/m_1390.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
assets/images/game/blockThird/m_1390.webp
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
assets/images/game/blockThird/m_1391.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
assets/images/game/blockThird/m_1391.webp
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/images/game/blockThird/m_1392.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
assets/images/game/blockThird/m_1392.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/images/game/blockThird/m_1560.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
assets/images/game/chess/color_1033.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/game/chess/color_1036.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
assets/images/game/chess/color_1383.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/images/game/chess/color_1537.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
assets/images/game/chess/color_1537.webp
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
assets/images/game/chess/color_297.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/images/game/chess/color_299.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/images/game/chess/color_382.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/images/game/chess/color_388.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/images/game/chess/color_393.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/images/game/chess/color_394.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/images/game/chess/color_401.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/images/game/chess/color_404.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/images/game/chess/color_417.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/images/game/chess/color_506.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/images/game/chess/color_709.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/images/game/chess/color_744.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/images/game/chess/color_745.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/images/game/chess/color_746.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
assets/images/game/chess/color_927.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
assets/images/game/chess/color_928.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/images/game/chess/dark_1383.png
Normal file
|
After Width: | Height: | Size: 981 B |
BIN
assets/images/game/chess/dark_297.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
assets/images/game/chess/dark_299.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/game/chess/dark_382.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/images/game/chess/dark_388.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
assets/images/game/chess/dark_388.webp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
assets/images/game/chess/dark_393.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/images/game/chess/dark_394.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/images/game/chess/dark_401.png
Normal file
|
After Width: | Height: | Size: 940 B |
BIN
assets/images/game/chess/dark_404.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/images/game/chess/dark_417.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/images/game/chess/dark_506.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/images/game/chess/dark_709.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
assets/images/game/chess/dark_744.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/images/game/chess/dark_745.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/images/game/chess/dark_746.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
assets/images/game/chess/dark_927.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
assets/images/game/chess/dark_928.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/images/game/chess/h_1036.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
assets/images/game/chess/h_1036.webp
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
assets/images/game/chess/h_1383.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/images/game/chess/h_1383.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/images/game/chess/h_1537.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
assets/images/game/chess/h_1537.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/game/chess/h_297.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/images/game/chess/h_297.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/images/game/chess/h_299.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/images/game/chess/h_299.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/images/game/chess/h_382.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/images/game/chess/h_382.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/game/chess/h_388.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/images/game/chess/h_388.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/game/chess/h_393.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
assets/images/game/chess/h_393.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
assets/images/game/chess/h_394.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/images/game/chess/h_394.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/game/chess/h_401.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
assets/images/game/chess/h_401.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
assets/images/game/chess/h_404.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
assets/images/game/chess/h_404.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/game/chess/h_506.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/images/game/chess/h_506.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/game/chess/h_709.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
assets/images/game/chess/h_709.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
assets/images/game/chess/h_744.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/images/game/chess/h_744.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
assets/images/game/chess/h_745.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
assets/images/game/chess/h_745.webp
Normal file
|
After Width: | Height: | Size: 11 KiB |