my-uniapp-tools
Version:
一个简洁稳定的 uni-app 开发工具库,提供剪贴板、本地存储、导航、系统信息等常用功能
629 lines (462 loc) • 16.8 kB
Markdown
# 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 开发优化,在其他环境中可能无法正常工作。