# uni-app 工具库

一个简洁稳定的 uni-app 开发工具库，提供剪贴板、本地存储、导航、系统信息、文件上传等常用功能。

## ✨ 特性

- 🚀 **简洁稳定**: 简化缓存机制，删除过度设计，减少维护成本
- 🛡️ **类型安全**: 完整的 TypeScript 支持
- 🔧 **统一错误处理**: 全局错误管理和监控
- 💾 **本地存储**: 支持TTL过期管理
- 🔄 **简洁设计**: 遵循Linus"好品味"原则，消除特殊情况
- 📱 **跨平台**: 支持 H5、App、微信/支付宝小程序

## 📦 安装

```bash
npm install my-uniapp-tools
# 或
yarn add my-uniapp-tools
```

## 🚀 快速开始

### 基础使用

```javascript
import { copyText } from 'my-uniapp-tools/clipboard';
import { setStorageSync } from 'my-uniapp-tools/localStorage';
import { useToast } from 'my-uniapp-tools/ui';

// 复制文本
await copyText('Hello World!');

// 本地存储
setStorageSync('userInfo', { name: '张三', age: 25 });

// 显示提示
useToast('操作成功');
```

### 按需引入

新项目推荐使用模块级子路径入口，减少无关模块被打包器纳入依赖图。根入口仍保留，兼容已有项目。

```javascript
import { deepClone } from 'my-uniapp-tools/utils';
import { selectAndUpload } from 'my-uniapp-tools/upload';
import { areaList } from 'my-uniapp-tools/regions';
```

省市区数据单独放在 `my-uniapp-tools/regions`，避免只使用 `utils` 时携带 `@vant/area-data`。

### 错误监听（可选）

```javascript
import { ErrorHandler } from 'my-uniapp-tools/core';

ErrorHandler.getInstance().onError((error) => {
  console.error(`[${error.module}] ${error.code}: ${error.message}`, error);
});
```

## 📚 API 文档

### 🎯 核心功能

#### ErrorHandler

全局错误处理器

```javascript
import { ErrorHandler } from 'my-uniapp-tools/core';

const errorHandler = ErrorHandler.getInstance();

// 注册错误监听
errorHandler.onError((error) => {
  console.log(`[${error.module}] ${error.message}`);
  // 上报错误到服务器
});
```

### 📋 剪贴板功能

#### copyText(text, config?)

跨平台文本复制

```javascript
// 基础使用
await copyText('要复制的文本');

// 高级配置
await copyText('要复制的文本', {
  showToast: true,           // 是否显示提示
  successMessage: '复制成功', // 成功提示文本
  failMessage: '复制失败',    // 失败提示文本
   timeout: 5000             // 超时时间(ms)
 });
```

### 💾 本地存储功能

#### setStorageSync(key, value, options?)

设置本地存储（同步）

```javascript
// 基础使用
setStorageSync('key', 'value');

// 带过期时间
setStorageSync('userData', userData, {
  ttl: 24 * 60 * 60 * 1000  // 24小时后过期
});
```

#### getStorageSync(key, defaultValue?)

获取本地存储（同步）

```javascript
const userData = getStorageSync('userData', {});
```

#### 批量操作

```javascript
// 批量设置
const count = batchSetStorage({
  'key1': 'value1',
  'key2': 'value2'
});

// 批量获取
const data = batchGetStorage(['key1', 'key2']);
```

#### cleanExpiredStorage()

清理过期数据

```javascript
const cleanedCount = cleanExpiredStorage();
console.log(`清理了 ${cleanedCount} 项过期数据`);
```

### 🧭 导航功能

本模块以页面返回与页面信息查询为主，常用 API 包括：

- `configureNavigation(config)`：配置导航模块（例如设置默认首页）
- `useBuildUrl(url, params)`：构建带参数的页面 URL
- `useBack(params?, options?)`：返回上一页并支持传参和超时保护
- `useBackOrHome(params?, options?)`：返回上一页或在页面栈不足时跳转到首页
- `useBackDebounced`：`useBack` 的防抖版本（300ms）
- `useCurrentPageInfo()` / `getCurrentPageInfo()`：获取当前页面信息（推荐使用 `useCurrentPageInfo`）
- `usePageStack()` / `getPageStack()`：获取页面栈信息（推荐使用 `usePageStack`）

如果需要页面跳转（如 `navigateTo` / `redirectTo` / `switchTab` / `reLaunch`），建议直接使用 `uni` 提供的原生 API，或在应用层实现自己的“安全导航”封装（例如 `useSafeNavigateTo`）。下面给出常用示例：

```javascript
import { configureNavigation, useBuildUrl, useBack, useBackOrHome } from 'my-uniapp-tools/navigation';

// 配置默认首页
configureNavigation({ defaultHomePage: '/pages/home/home' });

// 构建带参数的 URL
const url = useBuildUrl('/pages/detail/detail', { id: 123 });

// 返回上一页并传参
await useBack({ refreshData: true });

// 页面栈不足时返回或跳转首页
await useBackOrHome('', { homePage: '/pages/home/home' });
```

### 📱 系统信息

#### getPlatform()

获取当前平台

```javascript
const platform = getPlatform(); // 'weixin' | 'h5' | 'app' | 'alipay' | 'unknown'
```

#### useWindowInfo(useCache?)

获取窗口信息

```javascript
// 使用缓存（默认）
const windowInfo = useWindowInfo();

// 强制刷新
const windowInfo = useWindowInfo(false);
```

#### getTopBarMetrics() ⭐ 推荐

获取顶部区域高度的结构化数据

```javascript
const metrics = getTopBarMetrics();
console.log(metrics.statusBarHeight);      // 状态栏高度
console.log(metrics.navigationBarHeight);  // 导航栏高度（不含状态栏）
console.log(metrics.totalTopHeight);       // 总高度
console.log(metrics.platform);             // 当前平台
```

#### getStatusBarHeight()

获取状态栏高度

```javascript
const height = getStatusBarHeight(); // 返回状态栏高度(px)
```

#### getNavigationBarHeight()

获取导航栏高度（不含状态栏）

```javascript
const height = getNavigationBarHeight(); // 返回导航栏高度(px)
```

#### getNavHeight() ⚠️ 已废弃

> **建议使用**: `getTopBarMetrics().totalTopHeight`

```javascript
const height = getNavHeight(); // 返回状态栏+导航栏总高度
```

#### clearSystemCache()

清除系统信息缓存（横竖屏切换时可调用）

```javascript
clearSystemCache();
```

### 📤 文件上传功能

> **重要变更**: v5.0.0 扁平化 UploadOptions，移除历史兼容层与多层嵌套配置

#### selectAndUpload(options) ⭐ 核心API

选择并上传文件（一体化业务入口）

**参数 UploadOptions(v5):**

- `url` (string, 必须): 上传地址
- `files` (UniFile[]): 直接上传已有文件（跳过选择阶段）
- `type` ('image' | 'file' | 'any'): 文件类型（选择阶段），默认 'image'
- `count` (number): 最多选择文件数，默认 1
- `maxSizeMB` (number): 文件体积限制(MB)，`0` 表示不允许选择/上传任何文件
- `extensions` (string[]): 允许的文件扩展名白名单（严格模式），如 ['jpg', 'png']
- `fieldName` (string): 文件字段名，默认 'file'
- `formData` (Record<string, any>): 额外的表单数据
- `headers` (Record<string, string>): 自定义请求头
- `timeoutMs` (number): 上传超时时间(ms)
- `autoRevokeObjectURL` (boolean): H5环境下是否自动回收 blob URL
- `concurrency` (number): 最大并发上传数（不传默认全并发）
- `signal` (AbortSignal): 取消信号（AbortController.signal）
- `beforeUpload` (function): 上传前拦截钩子，返回 false 跳过该文件
- `onProgress` (function): 进度回调 (file, progress) => void
- `showToast` (boolean): 是否显示提示，默认 true
- `successMessage` (string): 成功提示文本（单文件成功时）
- `failMessage` (string): 失败提示文本（保留字段）

说明：普通文件选择会优先使用平台支持的 `chooseFile` 能力，`extensions` 会在选择器能力允许时前置过滤，并在上传前统一二次校验；所有失败场景都返回结构化 `UploadResult[]`。

**返回值**: `Promise<UploadResult[]>`

- `file` (UniFile | null): 文件信息
- `success` (boolean): 是否成功
- `statusCode` (number): HTTP状态码
- `data` (unknown): 服务器返回数据
- `message` (string): 提示信息

```javascript
// 选择并上传图片
const results = await selectAndUpload({
  url: 'https://api.example.com/upload',
  type: 'image',
  count: 3,
  maxSizeMB: 5,
  autoRevokeObjectURL: true,
  formData: { userId: '123' },
  headers: { 'Authorization': 'Bearer token' },
  onProgress: (file, progress) => {
    console.log(`${file.name}: ${progress}%`);
  }
});

results.forEach((result) => {
  if (result.success) {
    console.log('上传成功:', result.data);
  } else {
    console.log('上传失败:', result.message);
  }
});
```

#### selectAndUploadImage(options)

选择并上传图片的便捷方法（等价于 type: 'image'）

```javascript
const results = await selectAndUploadImage({
  url: 'https://api.example.com/upload',
  count: 1,
  maxSizeMB: 5
});
```

#### 高级用法示例

```javascript
// 1. 使用并发控制
const concurrencyResults = await selectAndUpload({
  url: 'https://api.example.com/upload',
  count: 10,
  concurrency: 3  // 每次最多同时上传3个文件
});

// 2. 使用上传前拦截
const checkedResults = await selectAndUpload({
  url: 'https://api.example.com/upload',
  beforeUpload: async (file) => {
    // 可以在这里做自定义校验
    if (file.size > 10 * 1024 * 1024) {
      console.warn('文件太大:', file.name);
      return false; // 跳过该文件
    }
    return true; // 继续上传
  }
});

// 3. 指定文件扩展名
const documentResults = await selectAndUpload({
  url: 'https://api.example.com/upload',
  type: 'file',
  extensions: ['pdf', 'doc', 'docx'],
  maxSizeMB: 20
});

// 4. 取消上传
const controller = new AbortController();
const uploadTask = selectAndUpload({
  url: 'https://api.example.com/upload',
  signal: controller.signal
});

// 用户点击取消时调用
controller.abort();

const canceledResults = await uploadTask;
const failed = canceledResults.find((item) => !item.success);
if (failed) {
  console.warn(failed.message);
}
```

### 💳 支付（微信公众号 H5）

#### wechatH5Pay(config, options?)

在微信内置浏览器中调起支付，返回结构化的支付结果

**参数**:

- `config` (WeChatPayConfig): 微信支付配置对象
- `options` (WeChatPayOptions): 可选配置
  - `reportError` (boolean): 是否上报错误到 ErrorHandler，默认 true

**返回值**: `Promise<PaymentResult>`

- `success` (boolean): 是否支付成功
- `status` ('success' | 'error' | 'cancel'): 支付状态
- `code` (string): 状态码
- `message` (string): 状态描述
- `raw` (WeChatPayResult | null): 微信原始回调数据

```javascript
import { wechatH5Pay } from 'my-uniapp-tools/payment';

// 从服务端获取签名后的支付参数
const payConfig = await fetch('/api/pay/wechat/unified-order', {
  method: 'POST',
  body: JSON.stringify({ orderId: '123456' })
}).then(r => r.json());

// 调用支付（结构化返回值）
const result = await wechatH5Pay(payConfig);

// 根据支付结果处理
if (result.success) {
  uni.showToast({ title: '支付成功', icon: 'success' });
  // 处理支付成功逻辑
} else if (result.status === 'cancel') {
  uni.showToast({ title: '已取消支付', icon: 'none' });
} else {
  uni.showToast({
    title: result.message || '支付失败',
    icon: 'none'
  });
}

// 测试时禁用错误上报
const result = await wechatH5Pay(payConfig, {
  reportError: false
});
```

### 🛠️ 工具函数

#### deepClone(obj)

深拷贝对象（使用 structuredClone 标准API）

```javascript
const original = {
  data: [1, 2, 3],
  date: new Date(),
  map: new Map()
};

const cloned = deepClone(original);
```

#### deepMerge(target, source)

深度合并对象

```javascript
const target = { a: 1, b: { c: 2 } };
const source = { b: { d: 3 }, e: 4 };
const merged = deepMerge(target, source);
// 结果: { a: 1, b: { c: 2, d: 3 }, e: 4 }
```

#### debounce(func, wait, immediate?)

防抖函数

```javascript
const debouncedFn = debounce(() => {
  console.log('执行');
}, 1000);

// 取消防抖
debouncedFn.cancel();
```

#### throttle(func, wait, options?)

节流函数

```javascript
const throttledFn = throttle(() => {
  console.log('执行');
}, 1000, {
  leading: true,   // 首次立即执行
  trailing: true   // 结束后执行
});

// 取消节流
throttledFn.cancel();
```

## 🎨 使用示例

### 完整示例

```javascript
import { copyText } from 'my-uniapp-tools/clipboard';
import { getStorageSync, setStorageSync } from 'my-uniapp-tools/localStorage';
import { useBuildUrl } from 'my-uniapp-tools/navigation';
import { useToast } from 'my-uniapp-tools/ui';
import { debounce } from 'my-uniapp-tools/utils';

// 页面中使用
export default {
  data() {
    return {
      userInfo: {}
    };
  },

  onLoad() {
    // 获取用户信息
    this.userInfo = getStorageSync('userInfo', {});
  },

  methods: {
    // 防抖搜索
    onSearch: debounce(function(keyword) {
      // 执行搜索
    }, 500),

    // 复制分享链接
    async onShare() {
      await copyText('https://example.com/share');
    },

    // 跳转详情页（示例：使用 useBuildUrl + uni.navigateTo）
    async goToDetail(id) {
      const url = useBuildUrl('/pages/detail/detail', { id });
      uni.navigateTo({ url });
    }
  }
};
```

### 错误处理示例

```javascript
import { ErrorHandler } from 'my-uniapp-tools/core';

// 全局错误监听
const errorHandler = ErrorHandler.getInstance();
errorHandler.onError((error) => {
  // 上报错误
  uni.request({
    url: 'https://api.example.com/error-report',
    method: 'POST',
    data: {
      module: error.module,
      code: error.code,
      message: error.message,
      timestamp: error.timestamp
    }
  });
});
```

## 📊 结构优化

### v3.0.x 优化方向

| 优化项 | 调整前 | 调整后 | 结果 |
|------|--------|--------|------|
| system模块 | 分散获取系统信息 | 统一入口和缓存 | 结构更清晰 |
| 缓存机制 | 复杂类封装 | 简单模块变量 | 行为更直接 |
| upload模块 | 分散API | 统一入口 | 调用更稳定 |
| 深拷贝算法 | 自实现 | 优先 structuredClone | 使用平台标准能力 |

### 核心优化原则

- ✅ **好品味**: 消除特殊情况，而不是重复它
- ✅ **简洁执念**: 复杂度是万恶之源
- ✅ **向后兼容**: Never break userspace
- ✅ **实用主义**: 解决实际问题，不是假想的威胁

## 🔧 配置选项

### 存储选项

```javascript
{
  ttl: number  // 过期时间(毫秒)
}
```

### 导航配置

```javascript
configureNavigation({
  defaultHomePage: '/pages/index/index'
});
```

## 🐛 常见问题

### Q: 存储的数据会自动过期吗？

A: 是的，设置了TTL的数据会自动过期，可以调用 `cleanExpiredStorage()` 手动清理

### Q: 支持哪些平台？

A: 支持 uni-app 的所有平台：H5、App、微信小程序、支付宝小程序等

### Q: 如何处理导航失败？

A: 本仓库未直接提供 `safeNavigateTo` 包装函数；建议调用 `uni.navigateTo`/`uni.redirectTo` 等原生 API，或在应用层实现带重试/去重/错误处理的自定义封装（例如 `useSafeNavigateTo`），以满足项目特定需求。

## 📄 更新日志

### v5.0.2 (当前版本)

- ✨ **上传能力**: `selectAndUpload()` 使用扁平化 `UploadOptions`，支持直接传入 `files`、体积限制、扩展名限制、进度回调与取消信号。
- 🐛 **边界修复**: 修复扩展名解析、`maxSizeMB=0`、Node 环境安全失败等边界行为。
- 🔧 **发布质量**: 构建前清理 `dist`，发布包包含完整声明文件，并增加真实消费验证。

### v3.0.2

- ✨ **新增**: `getTopBarMetrics()` 返回结构化的导航栏高度信息
- ✨ **新增**: `getNavigationBarHeight()` 获取导航栏高度（不含状态栏）
- ✨ **新增**: `clearSystemCache()` 清除系统信息缓存
- ✨ **新增**: `selectAndUpload()` 全新的文件上传统一入口
- ✨ **新增**: `selectAndUploadImage()` 图片上传便捷方法
- 🚀 **优化**: system模块删除过度设计的缓存机制
- 🚀 **优化**: 统一导航栏高度API，避免重复计算
- 🚀 **优化**: 消除重复的平台判断，提取 `isMiniProgram()` 辅助函数
- ⚠️ **破坏性变更**: 删除旧的 `chooseFile`/`uploadFile`/`chooseAndUploadFile` 等API
- ⚠️ **废弃**: `getNavHeight()` 和 `getTopNavBarHeight()` 标记为废弃，建议使用 `getTopBarMetrics()`

### v2.0.1

- 🐛 修复已废弃的 `uni.getSystemInfoSync` API
- 🚀 本地存储功能大幅增强
- 🚀 导航模块优化
- 📝 完善TypeScript类型定义

## 📜 许可证

MIT License

## 🤝 贡献

欢迎提交 Issue 和 Pull Request！

---

**注意**: 本工具库专为 uni-app 开发优化，在其他环境中可能无法正常工作。
