fezbox 0.0.1-security → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fezbox might be problematic. Click here for more details.
- package/README.md +368 -3
- package/dist/crypto/decrypt.d.ts +8 -0
- package/dist/crypto/decrypt.d.ts.map +1 -0
- package/dist/crypto/index.d.ts +2 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/data/clone.d.ts +7 -0
- package/dist/data/clone.d.ts.map +1 -0
- package/dist/data/index.d.ts +2 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/dom/index.d.ts +2 -0
- package/dist/dom/index.d.ts.map +1 -0
- package/dist/dom/outsideClick.d.ts +27 -0
- package/dist/dom/outsideClick.d.ts.map +1 -0
- package/dist/fezbox.cjs +1 -0
- package/dist/fezbox.js +250 -0
- package/dist/file/fetch.d.ts +9 -0
- package/dist/file/fetch.d.ts.map +1 -0
- package/dist/file/format.d.ts +7 -0
- package/dist/file/format.d.ts.map +1 -0
- package/dist/file/index.d.ts +4 -0
- package/dist/file/index.d.ts.map +1 -0
- package/dist/file/upload.d.ts +12 -0
- package/dist/file/upload.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/qr/index.d.ts +57 -0
- package/dist/qr/index.d.ts.map +1 -0
- package/dist/time/index.d.ts +2 -0
- package/dist/time/index.d.ts.map +1 -0
- package/dist/time/relative.d.ts +8 -0
- package/dist/time/relative.d.ts.map +1 -0
- package/dist/url/index.d.ts +2 -0
- package/dist/url/index.d.ts.map +1 -0
- package/dist/url/query.d.ts +8 -0
- package/dist/url/query.d.ts.map +1 -0
- package/package.json +60 -4
package/README.md
CHANGED
@@ -1,5 +1,370 @@
|
|
1
|
-
#
|
1
|
+
# FezBox
|
2
2
|
|
3
|
-
|
3
|
+
[](https://badge.fury.io/js/fezbox)
|
4
|
+
[](https://github.com/wancj/fezbox)
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
6
|
+
[](https://nodejs.org/)
|
4
7
|
|
5
|
-
|
8
|
+
一个包含常用工具函数的 JavaScript/TypeScript 库,按功能模块分类,便于按需引入。
|
9
|
+
|
10
|
+
## ✨ 特性
|
11
|
+
|
12
|
+
- 📦 **模块化设计** - 支持按需引入,减少包体积
|
13
|
+
- 🔒 **完整 TypeScript 支持** - 提供准确的类型定义
|
14
|
+
- 🧪 **90.96% 测试覆盖率** - 77个测试用例覆盖所有核心功能
|
15
|
+
- 🎯 **轻量级** - 仅依赖必要的 peer dependencies
|
16
|
+
- 📚 **详细文档** - 包含完整的使用示例和 API 说明
|
17
|
+
- 🚀 **高性能** - 优化的算法和实现
|
18
|
+
- 🔧 **易维护** - 清晰的代码结构和完整的测试
|
19
|
+
|
20
|
+
## 安装
|
21
|
+
|
22
|
+
```bash
|
23
|
+
# 使用 npm
|
24
|
+
npm install fezbox
|
25
|
+
|
26
|
+
# 使用 pnpm
|
27
|
+
pnpm add fezbox
|
28
|
+
|
29
|
+
# 使用 yarn
|
30
|
+
yarn add fezbox
|
31
|
+
```
|
32
|
+
|
33
|
+
## 🚀 快速开始
|
34
|
+
|
35
|
+
### 完整引入
|
36
|
+
|
37
|
+
```typescript
|
38
|
+
import { deepClone, formatFileSize, relativeTime, outsideClick } from 'fezbox'
|
39
|
+
|
40
|
+
// 深拷贝对象
|
41
|
+
const original = { a: 1, b: { c: 2 } }
|
42
|
+
const cloned = deepClone(original)
|
43
|
+
|
44
|
+
// 格式化文件大小
|
45
|
+
const size = formatFileSize(1048576) // "1.00 Mb"
|
46
|
+
|
47
|
+
// 相对时间格式化
|
48
|
+
const time = relativeTime(new Date(Date.now() - 3600000)) // "1小时前"
|
49
|
+
|
50
|
+
// 外部点击监听
|
51
|
+
const element = document.getElementById('myElement')
|
52
|
+
const unlisten = outsideClick.listen({
|
53
|
+
domRefs: element,
|
54
|
+
handler: () => console.log('点击了外部区域')
|
55
|
+
})
|
56
|
+
```
|
57
|
+
|
58
|
+
### 按需引入(推荐)
|
59
|
+
|
60
|
+
```typescript
|
61
|
+
// 只引入需要的模块,减少包体积
|
62
|
+
import { deepClone } from 'fezbox/data'
|
63
|
+
import { formatFileSize, fetchFile, uploadFileByUrl } from 'fezbox/file'
|
64
|
+
import { relativeTime } from 'fezbox/time'
|
65
|
+
import { getQueryParam } from 'fezbox/url'
|
66
|
+
import { decryptAES } from 'fezbox/crypto'
|
67
|
+
import { outsideClick } from 'fezbox/dom'
|
68
|
+
import { QRCodeScriptLoader } from 'fezbox/qr'
|
69
|
+
```
|
70
|
+
|
71
|
+
## 📚 API 文档
|
72
|
+
|
73
|
+
### 📊 Data 模块
|
74
|
+
|
75
|
+
#### `deepClone<T>(data: T): T`
|
76
|
+
|
77
|
+
深拷贝函数,基于 JSON 序列化实现,支持对象和数组的深度复制。
|
78
|
+
|
79
|
+
**特性:**
|
80
|
+
- 简单易用的 JSON 序列化实现
|
81
|
+
- 性能优化
|
82
|
+
- 支持嵌套对象和数组
|
83
|
+
- 不支持循环引用、函数、Map、Set 等复杂类型
|
84
|
+
|
85
|
+
```typescript
|
86
|
+
// 基础对象
|
87
|
+
const obj = { a: 1, b: { c: 2 } }
|
88
|
+
const cloned = deepClone(obj)
|
89
|
+
|
90
|
+
// 数组
|
91
|
+
const arr = [1, 2, { nested: true }]
|
92
|
+
const clonedArr = deepClone(arr)
|
93
|
+
|
94
|
+
// 复杂对象
|
95
|
+
const complex = {
|
96
|
+
date: new Date(),
|
97
|
+
map: new Map([['key', 'value']]),
|
98
|
+
set: new Set([1, 2, 3])
|
99
|
+
}
|
100
|
+
const clonedComplex = deepClone(complex)
|
101
|
+
```
|
102
|
+
|
103
|
+
### 📁 File 模块
|
104
|
+
|
105
|
+
#### `formatFileSize(sizeInBytes: string | number): string`
|
106
|
+
|
107
|
+
格式化文件大小为人类可读格式。
|
108
|
+
|
109
|
+
**支持的格式:**
|
110
|
+
- Bytes (B)
|
111
|
+
- Kilobytes (Kb)
|
112
|
+
- Megabytes (Mb)
|
113
|
+
- Gigabytes (Gb)
|
114
|
+
|
115
|
+
```typescript
|
116
|
+
formatFileSize(500) // "500.00 B"
|
117
|
+
formatFileSize(1024) // "1.00 Kb"
|
118
|
+
formatFileSize(1048576) // "1.00 Mb"
|
119
|
+
formatFileSize(1073741824) // "1.00 Gb"
|
120
|
+
```
|
121
|
+
|
122
|
+
#### `fetchFile(url: string, fileName: string, fetchFn?: typeof fetch): Promise<File>`
|
123
|
+
|
124
|
+
通过 URL 获取文件并创建 File 对象。
|
125
|
+
|
126
|
+
**特性:**
|
127
|
+
- 自动处理 MP4 文件的 MIME 类型
|
128
|
+
- 支持自定义 fetch 函数
|
129
|
+
- 错误处理
|
130
|
+
|
131
|
+
```typescript
|
132
|
+
// 使用默认 fetch
|
133
|
+
const file = await fetchFile('https://example.com/image.jpg', 'image.jpg')
|
134
|
+
|
135
|
+
// 使用自定义 fetch
|
136
|
+
const customFetch = (url: string) => fetch(url, { headers: { 'Authorization': 'Bearer token' } })
|
137
|
+
const file = await fetchFile('https://api.example.com/file.jpg', 'image.jpg', customFetch)
|
138
|
+
```
|
139
|
+
|
140
|
+
#### `uploadFileByUrl(url: string, uploadFn: (formData: FormData) => Promise<{ url: string }>, defaultFileType?: string, fetchFn?: typeof fetch): Promise<string>`
|
141
|
+
|
142
|
+
通过 URL 下载并上传文件到服务器。
|
143
|
+
|
144
|
+
**特性:**
|
145
|
+
- 自动提取文件名
|
146
|
+
- 支持查询参数 URL
|
147
|
+
- 可设置默认文件类型
|
148
|
+
- 完整的错误处理
|
149
|
+
|
150
|
+
```typescript
|
151
|
+
// 基本用法
|
152
|
+
const uploadedUrl = await uploadFileByUrl(
|
153
|
+
'https://example.com/image.jpg',
|
154
|
+
async (formData) => {
|
155
|
+
const response = await fetch('https://api.example.com/upload', {
|
156
|
+
method: 'POST',
|
157
|
+
body: formData
|
158
|
+
})
|
159
|
+
return await response.json()
|
160
|
+
}
|
161
|
+
)
|
162
|
+
|
163
|
+
// 带默认文件类型
|
164
|
+
const uploadedUrl = await uploadFileByUrl(
|
165
|
+
'https://example.com/image',
|
166
|
+
uploadFunction,
|
167
|
+
'jpg' // 当 URL 没有文件扩展名时使用
|
168
|
+
)
|
169
|
+
```
|
170
|
+
|
171
|
+
### ⏰ Time 模块
|
172
|
+
|
173
|
+
#### `relativeTime(date: string | number | Date | dayjs.Dayjs | null | undefined): string`
|
174
|
+
|
175
|
+
相对时间格式化,支持多种输入格式。
|
176
|
+
|
177
|
+
**时间格式:**
|
178
|
+
- < 1 分钟:刚刚
|
179
|
+
- 1-24 小时:X 小时前
|
180
|
+
- 1-30 天:X 天前
|
181
|
+
- 30-60 天:1 月前
|
182
|
+
- > 60 天:更早
|
183
|
+
|
184
|
+
```typescript
|
185
|
+
// 支持多种输入格式
|
186
|
+
relativeTime(new Date()) // "刚刚"
|
187
|
+
relativeTime(new Date(Date.now() - 3600000)) // "1小时前"
|
188
|
+
relativeTime(new Date(Date.now() - 86400000)) // "1天前"
|
189
|
+
relativeTime('2024-01-01T00:00:00Z') // 支持字符串
|
190
|
+
relativeTime(1640995200000) // 支持时间戳
|
191
|
+
relativeTime(dayjs()) // 支持 dayjs
|
192
|
+
```
|
193
|
+
|
194
|
+
### 🔗 URL 模块
|
195
|
+
|
196
|
+
#### `getQueryParam(name: string, url?: string): string | undefined`
|
197
|
+
|
198
|
+
获取 URL 查询参数,支持当前 URL 和指定 URL。
|
199
|
+
|
200
|
+
```typescript
|
201
|
+
// 从指定 URL 获取
|
202
|
+
getQueryParam('id', 'https://example.com?id=123&name=test') // "123"
|
203
|
+
|
204
|
+
// 从当前 URL 获取
|
205
|
+
getQueryParam('search') // 获取当前页面的 search 参数
|
206
|
+
|
207
|
+
// 处理特殊字符
|
208
|
+
getQueryParam('query', 'https://example.com?query=hello%20world') // "hello world"
|
209
|
+
```
|
210
|
+
|
211
|
+
### 🔐 Crypto 模块
|
212
|
+
|
213
|
+
#### `decryptAES(data: string, key: string | number): string`
|
214
|
+
|
215
|
+
AES 解密函数,支持字符串和数字类型的密钥。
|
216
|
+
|
217
|
+
**特性:**
|
218
|
+
- 自动截取密钥前 16 位
|
219
|
+
- 支持多种密钥类型
|
220
|
+
- 错误容错处理
|
221
|
+
|
222
|
+
```typescript
|
223
|
+
// 字符串密钥
|
224
|
+
const decrypted = decryptAES('encryptedData', 'my-secret-key')
|
225
|
+
|
226
|
+
// 数字密钥
|
227
|
+
const decrypted = decryptAES('encryptedData', 1234567890123456)
|
228
|
+
|
229
|
+
// 长密钥自动截取
|
230
|
+
const decrypted = decryptAES('encryptedData', 'this-is-a-very-long-key')
|
231
|
+
```
|
232
|
+
|
233
|
+
### 🎯 DOM 模块
|
234
|
+
|
235
|
+
#### `outsideClick`
|
236
|
+
|
237
|
+
外部点击监听工具,用于检测点击元素外部的事件。
|
238
|
+
|
239
|
+
**特性:**
|
240
|
+
- WeakMap 存储避免内存泄漏
|
241
|
+
- 支持多个监听器
|
242
|
+
- 简单的解绑机制
|
243
|
+
|
244
|
+
```typescript
|
245
|
+
// 基本用法
|
246
|
+
const element = document.getElementById('dropdown')
|
247
|
+
const unlisten = outsideClick.listen({
|
248
|
+
domRefs: element,
|
249
|
+
handler: () => {
|
250
|
+
console.log('点击了下拉菜单外部')
|
251
|
+
element.classList.remove('open')
|
252
|
+
}
|
253
|
+
})
|
254
|
+
|
255
|
+
// 解绑监听
|
256
|
+
unlisten()
|
257
|
+
```
|
258
|
+
|
259
|
+
### 🔲 QR Code 模块
|
260
|
+
|
261
|
+
#### `QRCodeScriptLoader`
|
262
|
+
|
263
|
+
将 JavaScript 代码编码到二维码中,并从二维码解析和执行代码的工具。
|
264
|
+
|
265
|
+
**特性:**
|
266
|
+
- 代码压缩和解压缩
|
267
|
+
- 沙箱执行环境
|
268
|
+
- 多种纠错级别支持
|
269
|
+
- 完整的错误处理
|
270
|
+
|
271
|
+
```typescript
|
272
|
+
// 创建实例
|
273
|
+
const loader = new QRCodeScriptLoader({
|
274
|
+
debug: true,
|
275
|
+
autoCompress: true
|
276
|
+
})
|
277
|
+
|
278
|
+
// 生成二维码数据
|
279
|
+
const result = await loader.generateQRCodeData('console.log("Hello World");')
|
280
|
+
if (result.success) {
|
281
|
+
console.log('生成成功:', result.data)
|
282
|
+
}
|
283
|
+
|
284
|
+
// 从图片解析二维码
|
285
|
+
const code = await loader.parseQRCodeFromImageData(imageData)
|
286
|
+
|
287
|
+
// 执行解析的代码
|
288
|
+
const executeResult = loader.executeCode(code)
|
289
|
+
```
|
290
|
+
|
291
|
+
## 📦 依赖
|
292
|
+
|
293
|
+
### 运行时依赖
|
294
|
+
- `crypto-js` (用于加密功能)
|
295
|
+
- `dayjs` (用于时间处理)
|
296
|
+
|
297
|
+
### 开发依赖
|
298
|
+
- `vitest` (测试框架)
|
299
|
+
- `typescript` (类型支持)
|
300
|
+
- `vite` (构建工具)
|
301
|
+
- `eslint` + `prettier` (代码质量)
|
302
|
+
|
303
|
+
## 🛠️ 开发
|
304
|
+
|
305
|
+
```bash
|
306
|
+
# 安装依赖
|
307
|
+
pnpm install
|
308
|
+
|
309
|
+
# 开发模式 (监听文件变化)
|
310
|
+
pnpm run dev
|
311
|
+
|
312
|
+
# 构建
|
313
|
+
pnpm run build
|
314
|
+
|
315
|
+
# 运行测试
|
316
|
+
pnpm test
|
317
|
+
|
318
|
+
# 生成覆盖率报告
|
319
|
+
pnpm run test:coverage
|
320
|
+
|
321
|
+
# 类型检查
|
322
|
+
pnpm run typecheck
|
323
|
+
|
324
|
+
# 代码格式化
|
325
|
+
pnpm run format
|
326
|
+
|
327
|
+
# 代码检查
|
328
|
+
pnpm run lint
|
329
|
+
|
330
|
+
# 修复代码问题
|
331
|
+
pnpm run lint:fix
|
332
|
+
```
|
333
|
+
|
334
|
+
## 📊 测试
|
335
|
+
|
336
|
+
项目包含完整的测试覆盖:
|
337
|
+
|
338
|
+
- **77个测试用例** 覆盖所有核心功能
|
339
|
+
- **90.96% 代码覆盖率** 确保代码质量
|
340
|
+
- **9个测试文件** 按模块组织
|
341
|
+
- **自动化的 CI/CD** 集成
|
342
|
+
|
343
|
+
运行测试并查看覆盖率报告:
|
344
|
+
|
345
|
+
```bash
|
346
|
+
pnpm run test:coverage
|
347
|
+
```
|
348
|
+
|
349
|
+
覆盖率报告会生成在 `coverage/` 目录下。
|
350
|
+
|
351
|
+
## 🤝 贡献
|
352
|
+
|
353
|
+
欢迎提交 Issue 和 Pull Request!
|
354
|
+
|
355
|
+
### 开发流程
|
356
|
+
1. Fork 项目
|
357
|
+
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
|
358
|
+
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
359
|
+
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
360
|
+
5. 创建 Pull Request
|
361
|
+
|
362
|
+
### 代码规范
|
363
|
+
- 使用 TypeScript 编写
|
364
|
+
- 遵循 ESLint 和 Prettier 规范
|
365
|
+
- 编写对应的测试用例
|
366
|
+
- 确保所有测试通过
|
367
|
+
|
368
|
+
## 许可证
|
369
|
+
|
370
|
+
MIT
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../../src/crypto/decrypt.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,GAAG,MAAM,KAAG,MAO/D,CAAA"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/crypto/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"clone.d.ts","sourceRoot":"","sources":["../../src/data/clone.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,MAAM,CAAC,KAAG,CAKtC,CAAA"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dom/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA"}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
/**
|
2
|
+
* 外部点击监听器接口
|
3
|
+
*/
|
4
|
+
interface OutsideClickListener {
|
5
|
+
domRefs: HTMLElement | null;
|
6
|
+
handler: () => void;
|
7
|
+
}
|
8
|
+
/**
|
9
|
+
* outsideClick 工具方法
|
10
|
+
* 用于监听外部点击事件,并在点击外部时执行指定的处理函数。
|
11
|
+
*/
|
12
|
+
export declare const outsideClick: {
|
13
|
+
listeners: WeakMap<() => void, (event: Event) => void>;
|
14
|
+
/**
|
15
|
+
* 监听外部点击事件
|
16
|
+
* @param {OutsideClickListener} param0 - 包含 domRefs 和 handler 的对象
|
17
|
+
* @returns {() => void} 返回一个解绑函数,用于在不需要监听时解绑事件
|
18
|
+
*/
|
19
|
+
listen({ domRefs, handler }: OutsideClickListener): () => void;
|
20
|
+
/**
|
21
|
+
* 解绑外部点击事件
|
22
|
+
* @param {() => void} handler - 需要解绑的处理函数
|
23
|
+
*/
|
24
|
+
unlisten(handler: () => void): void;
|
25
|
+
};
|
26
|
+
export {};
|
27
|
+
//# sourceMappingURL=outsideClick.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"outsideClick.d.ts","sourceRoot":"","sources":["../../src/dom/outsideClick.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,UAAU,oBAAoB;IAC5B,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY;6BACM,IAAI,UAAU,KAAK,KAAK,IAAI;IAEzD;;;;OAIG;iCAC0B,oBAAoB,GAAG,MAAM,IAAI;IAkB9D;;;OAGG;sBACe,MAAM,IAAI;CAO7B,CAAA"}
|
package/dist/fezbox.cjs
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const CryptoJS=require("crypto-js"),dayjs=require("dayjs"),decryptAES=(e,t)=>{const o=CryptoJS.enc.Utf8.parse(String(t).slice(0,16)),s=CryptoJS.AES.decrypt(e,o,{mode:CryptoJS.mode.ECB,padding:CryptoJS.pad.Pkcs7});return CryptoJS.enc.Utf8.stringify(s)},deepClone=e=>e==null?e:JSON.parse(JSON.stringify(e)),outsideClick={listeners:new WeakMap,listen({domRefs:e,handler:t}){if(this.listeners.has(t))return()=>this.unlisten(t);const o=s=>{e&&!(e===s.target||e.contains(s.target))&&t()};return document.addEventListener("click",o),this.listeners.set(t,o),()=>this.unlisten(t)},unlisten(e){const t=this.listeners.get(e);t&&(document.removeEventListener("click",t),this.listeners.delete(e))}},formatFileSize=e=>{const t=typeof e=="string"?parseFloat(e):e;return t>=1048576?`${(t/1048576).toFixed(2)} Mb`:t>=1024?`${(t/1024).toFixed(2)} Kb`:`${t.toFixed(2)} B`},fetchFile=async(e,t,o=globalThis.fetch)=>{const s=await o(e),n=await s.blob(),i=t.endsWith(".mp4"),r=s.headers.get("content-type"),c=new Blob([n],{type:i?"video/mp4":r||"application/octet-stream"});return new File([c],t,{type:c.type})},uploadFileByUrl=async(e,t,o,s=globalThis.fetch)=>{try{const i=await(await s(e)).blob();let r=e.split("/").pop()?.split("?")[0];r&&!r.includes(".")&&o&&(r+=`.${o}`);const c=new FormData;return c.append("file",i,r),(await t(c)).url}catch(n){return console.error("操作失败",n),Promise.reject(n)}};function relativeTime(e){const t=dayjs(),o=dayjs(e),s=t.diff(o,"minute"),n=t.diff(o,"hour"),i=t.diff(o,"day");return s<60?"刚刚":n<24?`${n}小时前`:i<30?`${i}天前`:i<60?"1月前":"更早"}const getQueryParam=(e,t)=>{const s=(t||(typeof window<"u"?window.location.href:"")).split("?")[1];if(!s)return;const n=s.split("&");for(let i=0;i<n.length;i++){const r=n[i].split("=");if(decodeURIComponent(r[0])===e)return decodeURIComponent(r[1]||"")}};class QRCodeScriptLoader{options;capacities;correctLevels;onLog=null;onProgress=null;constructor(e={}){this.options={maxRetries:3,compressionLevel:9,autoCompress:!0,compress:!1,debug:!1,sandbox:!0,qrCodeOptions:{width:256,height:256,colorDark:"#000000",colorLight:"#ffffff"},...e},this.capacities={L:2953,M:2331,Q:1663,H:1273},this.correctLevels={L:0,M:1,Q:2,H:3}}async generateQRCodeData(e,t={}){const o={...this.options,...t};try{let s=e,n=!1;const i=this.getByteLength(e);if(this.log(`原始代码: ${e.length} 字符, ${i} 字节`,"info"),"compress"in t&&t.compress){s=await this.compressCode(e),n=!0;const a=this.getByteLength(s);this.log(`压缩后: ${s.length} 字符, ${a} 字节`,"info")}const r=this.getByteLength(s),c=this.selectCorrectLevel(r);if(r>this.capacities.L){if(!n&&o.autoCompress)return this.log("数据太长,自动尝试压缩...","info"),this.generateQRCodeData(e,{...o,compress:!0});throw new Error(`数据太长: ${r} 字节超过最大容量 ${this.capacities.L} 字节`)}return this.log(`使用纠错级别: ${c.name} (最大容量: ${c.capacity} 字节)`,"info"),{success:!0,data:s,originalLength:e.length,encodedLength:s.length,originalBytes:i,encodedBytes:r,compressed:n,correctLevel:c,qrCodeConfig:{text:s,...o.qrCodeOptions,correctLevel:this.correctLevels[c.name]}}}catch(s){const n=s instanceof Error?s.message:String(s);return this.log(`生成失败: ${n}`,"error"),{success:!1,error:n}}}async parseQRCodeFromImageData(e){try{if(typeof globalThis.jsQR>"u")throw new Error("jsQR 库未加载");const t=globalThis.jsQR(e.data,e.width,e.height);if(!t)throw new Error("无法识别二维码");let o=t.data;return o.startsWith("COMPRESSED:")&&(this.log("检测到压缩代码,正在解压...","info"),o=await this.decompressCode(o.substring(11)),this.log("解压成功!","info")),o}catch(t){const o=t instanceof Error?t.message:String(t);throw this.log(`解析失败: ${o}`,"error"),t}}executeCode(code,context={},options={}){const opts={...this.options,...options};try{if(this.log("开始执行代码...","info"),opts.sandbox){const e=this.executeInSandbox(code,context);return this.log("代码执行完成!","info"),e}else{const result=eval(code);return this.log("代码执行完成!","info"),result}}catch(e){const t=e instanceof Error?e.message:String(e);throw this.log(`执行错误: ${t}`,"error"),e}}async compressCode(e){if(typeof globalThis.pako>"u")throw new Error("pako 压缩库未加载");const o=globalThis.pako.deflate(new TextEncoder().encode(e),{level:this.options.compressionLevel});return"COMPRESSED:"+btoa(String.fromCharCode.apply(null,Array.from(o)))}async decompressCode(e){if(typeof globalThis.pako>"u")throw new Error("pako 压缩库未加载");const t=globalThis.pako,o=atob(e),s=new Uint8Array(o.length);for(let i=0;i<o.length;i++)s[i]=o.charCodeAt(i);const n=t.inflate(s);return new TextDecoder().decode(n)}selectCorrectLevel(e){return e<=this.capacities.H?{name:"H",capacity:this.capacities.H}:e<=this.capacities.Q?{name:"Q",capacity:this.capacities.Q}:e<=this.capacities.M?{name:"M",capacity:this.capacities.M}:{name:"L",capacity:this.capacities.L}}executeInSandbox(e,t={}){const o=this,s={console:{log:(...r)=>{o.log("[Console] "+r.join(" "),"normal")},error:(...r)=>{o.log("[Console] "+r.join(" "),"error")},warn:(...r)=>{o.log("[Console] "+r.join(" "),"warning")},info:(...r)=>{o.log("[Console] "+r.join(" "),"info")}},Math,Date,JSON,Array,Object,String,Number,Boolean,...t},n=Object.keys(s),i=Object.values(s);try{return new Function(...n,e).apply(null,i)}catch(r){const c=r instanceof Error?r.message:String(r);throw o.log("执行出错: "+c,"error"),r}}getByteLength(e){return new TextEncoder().encode(e).length}log(e,t="normal"){this.onLog&&this.onLog(e,t)}getCapacityInfo(){return{capacities:this.capacities,recommendations:{small:"< 500 字符:使用 H 级纠错",medium:"500-1500 字符:使用 M 或 Q 级纠错",large:"1500-2900 字符:使用 L 级纠错",veryLarge:"> 2900 字符:需要压缩或分片"}}}}const version="1.0.0";exports.QRCodeScriptLoader=QRCodeScriptLoader;exports.decryptAES=decryptAES;exports.deepClone=deepClone;exports.fetchFile=fetchFile;exports.formatFileSize=formatFileSize;exports.getQueryParam=getQueryParam;exports.outsideClick=outsideClick;exports.relativeTime=relativeTime;exports.uploadFileByUrl=uploadFileByUrl;exports.version=version;
|
package/dist/fezbox.js
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
import CryptoJS from "crypto-js";
|
2
|
+
import dayjs from "dayjs";
|
3
|
+
const decryptAES = (e, t) => {
|
4
|
+
const o = CryptoJS.enc.Utf8.parse(String(t).slice(0, 16)), s = CryptoJS.AES.decrypt(e, o, {
|
5
|
+
mode: CryptoJS.mode.ECB,
|
6
|
+
padding: CryptoJS.pad.Pkcs7
|
7
|
+
});
|
8
|
+
return CryptoJS.enc.Utf8.stringify(s);
|
9
|
+
}, deepClone = (e) => e == null ? e : JSON.parse(JSON.stringify(e)), outsideClick = {
|
10
|
+
listeners: /* @__PURE__ */ new WeakMap(),
|
11
|
+
// 存储 handler 和对应的 listener
|
12
|
+
/**
|
13
|
+
* 监听外部点击事件
|
14
|
+
* @param {OutsideClickListener} param0 - 包含 domRefs 和 handler 的对象
|
15
|
+
* @returns {() => void} 返回一个解绑函数,用于在不需要监听时解绑事件
|
16
|
+
*/
|
17
|
+
listen({ domRefs: e, handler: t }) {
|
18
|
+
if (this.listeners.has(t))
|
19
|
+
return () => this.unlisten(t);
|
20
|
+
const o = (s) => {
|
21
|
+
e && !(e === s.target || e.contains(s.target)) && t();
|
22
|
+
};
|
23
|
+
return document.addEventListener("click", o), this.listeners.set(t, o), () => this.unlisten(t);
|
24
|
+
},
|
25
|
+
/**
|
26
|
+
* 解绑外部点击事件
|
27
|
+
* @param {() => void} handler - 需要解绑的处理函数
|
28
|
+
*/
|
29
|
+
unlisten(e) {
|
30
|
+
const t = this.listeners.get(e);
|
31
|
+
t && (document.removeEventListener("click", t), this.listeners.delete(e));
|
32
|
+
}
|
33
|
+
}, formatFileSize = (e) => {
|
34
|
+
const t = typeof e == "string" ? parseFloat(e) : e;
|
35
|
+
return t >= 1048576 ? `${(t / 1048576).toFixed(2)} Mb` : t >= 1024 ? `${(t / 1024).toFixed(2)} Kb` : `${t.toFixed(2)} B`;
|
36
|
+
}, fetchFile = async (e, t, o = globalThis.fetch) => {
|
37
|
+
const s = await o(e), n = await s.blob(), i = t.endsWith(".mp4"), r = s.headers.get("content-type"), c = new Blob([n], {
|
38
|
+
type: i ? "video/mp4" : r || "application/octet-stream"
|
39
|
+
});
|
40
|
+
return new File([c], t, { type: c.type });
|
41
|
+
}, uploadFileByUrl = async (e, t, o, s = globalThis.fetch) => {
|
42
|
+
try {
|
43
|
+
const i = await (await s(e)).blob();
|
44
|
+
let r = e.split("/").pop()?.split("?")[0];
|
45
|
+
r && !r.includes(".") && o && (r += `.${o}`);
|
46
|
+
const c = new FormData();
|
47
|
+
return c.append("file", i, r), (await t(c)).url;
|
48
|
+
} catch (n) {
|
49
|
+
return console.error("操作失败", n), Promise.reject(n);
|
50
|
+
}
|
51
|
+
};
|
52
|
+
function relativeTime(e) {
|
53
|
+
const t = dayjs(), o = dayjs(e), s = t.diff(o, "minute"), n = t.diff(o, "hour"), i = t.diff(o, "day");
|
54
|
+
return s < 60 ? "刚刚" : n < 24 ? `${n}小时前` : i < 30 ? `${i}天前` : i < 60 ? "1月前" : "更早";
|
55
|
+
}
|
56
|
+
const getQueryParam = (e, t) => {
|
57
|
+
const s = (t || (typeof window < "u" ? window.location.href : "")).split("?")[1];
|
58
|
+
if (!s) return;
|
59
|
+
const n = s.split("&");
|
60
|
+
for (let i = 0; i < n.length; i++) {
|
61
|
+
const r = n[i].split("=");
|
62
|
+
if (decodeURIComponent(r[0]) === e)
|
63
|
+
return decodeURIComponent(r[1] || "");
|
64
|
+
}
|
65
|
+
};
|
66
|
+
class QRCodeScriptLoader {
|
67
|
+
options;
|
68
|
+
capacities;
|
69
|
+
correctLevels;
|
70
|
+
onLog = null;
|
71
|
+
onProgress = null;
|
72
|
+
constructor(e = {}) {
|
73
|
+
this.options = {
|
74
|
+
maxRetries: 3,
|
75
|
+
compressionLevel: 9,
|
76
|
+
autoCompress: !0,
|
77
|
+
compress: !1,
|
78
|
+
debug: !1,
|
79
|
+
sandbox: !0,
|
80
|
+
qrCodeOptions: {
|
81
|
+
width: 256,
|
82
|
+
height: 256,
|
83
|
+
colorDark: "#000000",
|
84
|
+
colorLight: "#ffffff"
|
85
|
+
},
|
86
|
+
...e
|
87
|
+
}, this.capacities = {
|
88
|
+
L: 2953,
|
89
|
+
M: 2331,
|
90
|
+
Q: 1663,
|
91
|
+
H: 1273
|
92
|
+
}, this.correctLevels = {
|
93
|
+
L: 0,
|
94
|
+
M: 1,
|
95
|
+
Q: 2,
|
96
|
+
H: 3
|
97
|
+
};
|
98
|
+
}
|
99
|
+
async generateQRCodeData(e, t = {}) {
|
100
|
+
const o = { ...this.options, ...t };
|
101
|
+
try {
|
102
|
+
let s = e, n = !1;
|
103
|
+
const i = this.getByteLength(e);
|
104
|
+
if (this.log(`原始代码: ${e.length} 字符, ${i} 字节`, "info"), "compress" in t && t.compress) {
|
105
|
+
s = await this.compressCode(e), n = !0;
|
106
|
+
const a = this.getByteLength(s);
|
107
|
+
this.log(`压缩后: ${s.length} 字符, ${a} 字节`, "info");
|
108
|
+
}
|
109
|
+
const r = this.getByteLength(s), c = this.selectCorrectLevel(r);
|
110
|
+
if (r > this.capacities.L) {
|
111
|
+
if (!n && o.autoCompress)
|
112
|
+
return this.log("数据太长,自动尝试压缩...", "info"), this.generateQRCodeData(e, { ...o, compress: !0 });
|
113
|
+
throw new Error(`数据太长: ${r} 字节超过最大容量 ${this.capacities.L} 字节`);
|
114
|
+
}
|
115
|
+
return this.log(`使用纠错级别: ${c.name} (最大容量: ${c.capacity} 字节)`, "info"), {
|
116
|
+
success: !0,
|
117
|
+
data: s,
|
118
|
+
originalLength: e.length,
|
119
|
+
encodedLength: s.length,
|
120
|
+
originalBytes: i,
|
121
|
+
encodedBytes: r,
|
122
|
+
compressed: n,
|
123
|
+
correctLevel: c,
|
124
|
+
qrCodeConfig: {
|
125
|
+
text: s,
|
126
|
+
...o.qrCodeOptions,
|
127
|
+
correctLevel: this.correctLevels[c.name]
|
128
|
+
}
|
129
|
+
};
|
130
|
+
} catch (s) {
|
131
|
+
const n = s instanceof Error ? s.message : String(s);
|
132
|
+
return this.log(`生成失败: ${n}`, "error"), {
|
133
|
+
success: !1,
|
134
|
+
error: n
|
135
|
+
};
|
136
|
+
}
|
137
|
+
}
|
138
|
+
async parseQRCodeFromImageData(e) {
|
139
|
+
try {
|
140
|
+
if (typeof globalThis.jsQR > "u")
|
141
|
+
throw new Error("jsQR 库未加载");
|
142
|
+
const t = globalThis.jsQR(e.data, e.width, e.height);
|
143
|
+
if (!t)
|
144
|
+
throw new Error("无法识别二维码");
|
145
|
+
let o = t.data;
|
146
|
+
return o.startsWith("COMPRESSED:") && (this.log("检测到压缩代码,正在解压...", "info"), o = await this.decompressCode(o.substring(11)), this.log("解压成功!", "info")), o;
|
147
|
+
} catch (t) {
|
148
|
+
const o = t instanceof Error ? t.message : String(t);
|
149
|
+
throw this.log(`解析失败: ${o}`, "error"), t;
|
150
|
+
}
|
151
|
+
}
|
152
|
+
executeCode(code, context = {}, options = {}) {
|
153
|
+
const opts = { ...this.options, ...options };
|
154
|
+
try {
|
155
|
+
if (this.log("开始执行代码...", "info"), opts.sandbox) {
|
156
|
+
const e = this.executeInSandbox(code, context);
|
157
|
+
return this.log("代码执行完成!", "info"), e;
|
158
|
+
} else {
|
159
|
+
const result = eval(code);
|
160
|
+
return this.log("代码执行完成!", "info"), result;
|
161
|
+
}
|
162
|
+
} catch (e) {
|
163
|
+
const t = e instanceof Error ? e.message : String(e);
|
164
|
+
throw this.log(`执行错误: ${t}`, "error"), e;
|
165
|
+
}
|
166
|
+
}
|
167
|
+
async compressCode(e) {
|
168
|
+
if (typeof globalThis.pako > "u")
|
169
|
+
throw new Error("pako 压缩库未加载");
|
170
|
+
const o = globalThis.pako.deflate(new TextEncoder().encode(e), {
|
171
|
+
level: this.options.compressionLevel
|
172
|
+
});
|
173
|
+
return "COMPRESSED:" + btoa(String.fromCharCode.apply(null, Array.from(o)));
|
174
|
+
}
|
175
|
+
async decompressCode(e) {
|
176
|
+
if (typeof globalThis.pako > "u")
|
177
|
+
throw new Error("pako 压缩库未加载");
|
178
|
+
const t = globalThis.pako, o = atob(e), s = new Uint8Array(o.length);
|
179
|
+
for (let i = 0; i < o.length; i++)
|
180
|
+
s[i] = o.charCodeAt(i);
|
181
|
+
const n = t.inflate(s);
|
182
|
+
return new TextDecoder().decode(n);
|
183
|
+
}
|
184
|
+
selectCorrectLevel(e) {
|
185
|
+
return e <= this.capacities.H ? { name: "H", capacity: this.capacities.H } : e <= this.capacities.Q ? { name: "Q", capacity: this.capacities.Q } : e <= this.capacities.M ? { name: "M", capacity: this.capacities.M } : { name: "L", capacity: this.capacities.L };
|
186
|
+
}
|
187
|
+
executeInSandbox(e, t = {}) {
|
188
|
+
const o = this, s = {
|
189
|
+
console: {
|
190
|
+
log: (...r) => {
|
191
|
+
o.log("[Console] " + r.join(" "), "normal");
|
192
|
+
},
|
193
|
+
error: (...r) => {
|
194
|
+
o.log("[Console] " + r.join(" "), "error");
|
195
|
+
},
|
196
|
+
warn: (...r) => {
|
197
|
+
o.log("[Console] " + r.join(" "), "warning");
|
198
|
+
},
|
199
|
+
info: (...r) => {
|
200
|
+
o.log("[Console] " + r.join(" "), "info");
|
201
|
+
}
|
202
|
+
},
|
203
|
+
Math,
|
204
|
+
Date,
|
205
|
+
JSON,
|
206
|
+
Array,
|
207
|
+
Object,
|
208
|
+
String,
|
209
|
+
Number,
|
210
|
+
Boolean,
|
211
|
+
...t
|
212
|
+
}, n = Object.keys(s), i = Object.values(s);
|
213
|
+
try {
|
214
|
+
return new Function(...n, e).apply(null, i);
|
215
|
+
} catch (r) {
|
216
|
+
const c = r instanceof Error ? r.message : String(r);
|
217
|
+
throw o.log("执行出错: " + c, "error"), r;
|
218
|
+
}
|
219
|
+
}
|
220
|
+
getByteLength(e) {
|
221
|
+
return new TextEncoder().encode(e).length;
|
222
|
+
}
|
223
|
+
log(e, t = "normal") {
|
224
|
+
this.onLog && this.onLog(e, t);
|
225
|
+
}
|
226
|
+
getCapacityInfo() {
|
227
|
+
return {
|
228
|
+
capacities: this.capacities,
|
229
|
+
recommendations: {
|
230
|
+
small: "< 500 字符:使用 H 级纠错",
|
231
|
+
medium: "500-1500 字符:使用 M 或 Q 级纠错",
|
232
|
+
large: "1500-2900 字符:使用 L 级纠错",
|
233
|
+
veryLarge: "> 2900 字符:需要压缩或分片"
|
234
|
+
}
|
235
|
+
};
|
236
|
+
}
|
237
|
+
}
|
238
|
+
const version = "1.0.0";
|
239
|
+
export {
|
240
|
+
QRCodeScriptLoader,
|
241
|
+
decryptAES,
|
242
|
+
deepClone,
|
243
|
+
fetchFile,
|
244
|
+
formatFileSize,
|
245
|
+
getQueryParam,
|
246
|
+
outsideClick,
|
247
|
+
relativeTime,
|
248
|
+
uploadFileByUrl,
|
249
|
+
version
|
250
|
+
};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
/**
|
2
|
+
* 通过 URL 获取文件
|
3
|
+
* @param url - 文件 URL
|
4
|
+
* @param fileName - 文件名
|
5
|
+
* @param fetchFn - 自定义 fetch 函数,可选
|
6
|
+
* @returns Promise<File> 返回文件对象
|
7
|
+
*/
|
8
|
+
export declare const fetchFile: (url: string, fileName: string, fetchFn?: typeof fetch) => Promise<File>;
|
9
|
+
//# sourceMappingURL=fetch.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/file/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,GACpB,KAAK,MAAM,EACX,UAAU,MAAM,EAChB,UAAS,OAAO,KAAwB,KACvC,OAAO,CAAC,IAAI,CAYd,CAAA"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/file/format.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,aAAa,MAAM,GAAG,MAAM,KAAG,MAe7D,CAAA"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/file/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA"}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
/**
|
2
|
+
* 通过 URL 上传文件
|
3
|
+
* @param url - 文件 URL
|
4
|
+
* @param uploadFn - 上传函数,接收 FormData 并返回上传后的 URL
|
5
|
+
* @param defaultFileType - 默认文件类型,当 URL 没有文件后缀时使用
|
6
|
+
* @param fetchFn - 自定义 fetch 函数,可选
|
7
|
+
* @returns Promise<string> 返回上传后的文件 URL
|
8
|
+
*/
|
9
|
+
export declare const uploadFileByUrl: (url: string, uploadFn: (formData: FormData) => Promise<{
|
10
|
+
url: string;
|
11
|
+
}>, defaultFileType?: string, fetchFn?: typeof fetch) => Promise<string>;
|
12
|
+
//# sourceMappingURL=upload.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/file/upload.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAC1B,KAAK,MAAM,EACX,UAAU,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1D,kBAAkB,MAAM,EACxB,UAAS,OAAO,KAAwB,KACvC,OAAO,CAAC,MAAM,CA2BhB,CAAA"}
|
package/dist/index.d.ts
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
/**
|
2
|
+
* FezBox - 常用工具函数库
|
3
|
+
*
|
4
|
+
* 一个包含常用工具函数的 JavaScript/TypeScript 库
|
5
|
+
* 按功能模块分类,便于按需引入
|
6
|
+
*/
|
7
|
+
export { decryptAES } from './crypto';
|
8
|
+
export { deepClone } from './data';
|
9
|
+
export { outsideClick } from './dom';
|
10
|
+
export { formatFileSize, fetchFile, uploadFileByUrl } from './file';
|
11
|
+
export { relativeTime } from './time';
|
12
|
+
export { getQueryParam } from './url';
|
13
|
+
export { QRCodeScriptLoader } from './qr';
|
14
|
+
export type { QRCodeOptions, QRCodeLoaderOptions, QRCodeGenerateResult, QRCodeCapacityInfo, LogLevel } from './qr';
|
15
|
+
export declare const version = "1.0.0";
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAGrC,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAGlC,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAA;AAGpC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAA;AAGnE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAGrC,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAGrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,MAAM,CAAA;AACzC,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,QAAQ,EACT,MAAM,MAAM,CAAA;AAGb,eAAO,MAAM,OAAO,UAAU,CAAA"}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
export interface QRCodeOptions {
|
2
|
+
width?: number;
|
3
|
+
height?: number;
|
4
|
+
colorDark?: string;
|
5
|
+
colorLight?: string;
|
6
|
+
}
|
7
|
+
export interface QRCodeLoaderOptions {
|
8
|
+
maxRetries?: number;
|
9
|
+
compressionLevel?: number;
|
10
|
+
autoCompress?: boolean;
|
11
|
+
compress?: boolean;
|
12
|
+
debug?: boolean;
|
13
|
+
sandbox?: boolean;
|
14
|
+
qrCodeOptions?: QRCodeOptions;
|
15
|
+
}
|
16
|
+
export interface QRCodeGenerateResult {
|
17
|
+
success: boolean;
|
18
|
+
data?: string;
|
19
|
+
originalLength?: number;
|
20
|
+
encodedLength?: number;
|
21
|
+
originalBytes?: number;
|
22
|
+
encodedBytes?: number;
|
23
|
+
compressed?: boolean;
|
24
|
+
correctLevel?: {
|
25
|
+
name: string;
|
26
|
+
capacity: number;
|
27
|
+
};
|
28
|
+
qrCodeConfig?: {
|
29
|
+
text: string;
|
30
|
+
correctLevel: number;
|
31
|
+
} & QRCodeOptions;
|
32
|
+
error?: string;
|
33
|
+
}
|
34
|
+
export interface QRCodeCapacityInfo {
|
35
|
+
capacities: Record<string, number>;
|
36
|
+
recommendations: Record<string, string>;
|
37
|
+
}
|
38
|
+
export type LogLevel = 'normal' | 'info' | 'warning' | 'error';
|
39
|
+
export declare class QRCodeScriptLoader {
|
40
|
+
private options;
|
41
|
+
private capacities;
|
42
|
+
private correctLevels;
|
43
|
+
onLog: ((message: string, level: LogLevel) => void) | null;
|
44
|
+
onProgress: ((progress: number) => void) | null;
|
45
|
+
constructor(options?: QRCodeLoaderOptions);
|
46
|
+
generateQRCodeData(code: string, options?: Partial<QRCodeLoaderOptions>): Promise<QRCodeGenerateResult>;
|
47
|
+
parseQRCodeFromImageData(imageData: ImageData): Promise<string>;
|
48
|
+
executeCode(code: string, context?: Record<string, any>, options?: Partial<QRCodeLoaderOptions>): any;
|
49
|
+
compressCode(code: string): Promise<string>;
|
50
|
+
decompressCode(compressedData: string): Promise<string>;
|
51
|
+
private selectCorrectLevel;
|
52
|
+
private executeInSandbox;
|
53
|
+
private getByteLength;
|
54
|
+
private log;
|
55
|
+
getCapacityInfo(): QRCodeCapacityInfo;
|
56
|
+
}
|
57
|
+
//# sourceMappingURL=index.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/qr/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,YAAY,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,CAAC;KACtB,GAAG,aAAa,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAE/D,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,aAAa,CAAyB;IAEvC,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IAClE,UAAU,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;gBAElD,OAAO,GAAE,mBAAwB;IAgCvC,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,CAAC,mBAAmB,CAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAwD3G,wBAAwB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IA4BrE,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAAE,OAAO,GAAE,OAAO,CAAC,mBAAmB,CAAM,GAAG,GAAG;IAsBvG,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAc3C,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgB7D,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,gBAAgB;IA0CxB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,GAAG;IAMX,eAAe,IAAI,kBAAkB;CAWtC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/time/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import { default as dayjs } from 'dayjs';
|
2
|
+
/**
|
3
|
+
* 相对时间格式化
|
4
|
+
* @param date - 日期,可以是字符串、数字、Date 对象或 dayjs 对象
|
5
|
+
* @returns 相对时间字符串
|
6
|
+
*/
|
7
|
+
export declare function relativeTime(date: string | number | Date | dayjs.Dayjs | null | undefined): string;
|
8
|
+
//# sourceMappingURL=relative.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"relative.d.ts","sourceRoot":"","sources":["../../src/time/relative.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC,KAAK,GAAG,IAAI,GAAG,SAAS,GAC5D,MAAM,CAkBR"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/url/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/url/query.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,EAAE,MAAM,MAAM,KAAG,MAAM,GAAG,SAsBnE,CAAA"}
|
package/package.json
CHANGED
@@ -1,6 +1,62 @@
|
|
1
1
|
{
|
2
2
|
"name": "fezbox",
|
3
|
-
"version": "0.0
|
4
|
-
"description": "
|
5
|
-
"
|
6
|
-
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "A comprehensive JavaScript/TypeScript utility library with modular design, complete test coverage, and optimized performance",
|
5
|
+
"main": "./dist/fezbox.cjs",
|
6
|
+
"module": "./dist/fezbox.js",
|
7
|
+
"types": "./dist/index.d.ts",
|
8
|
+
"exports": {
|
9
|
+
".": {
|
10
|
+
"types": "./dist/index.d.ts",
|
11
|
+
"import": "./dist/fezbox.js",
|
12
|
+
"require": "./dist/fezbox.cjs"
|
13
|
+
}
|
14
|
+
},
|
15
|
+
"files": [
|
16
|
+
"dist",
|
17
|
+
"README.md"
|
18
|
+
],
|
19
|
+
"scripts": {
|
20
|
+
"build": "vite build",
|
21
|
+
"dev": "vitest --watch",
|
22
|
+
"docs": "vitepress dev docs",
|
23
|
+
"test": "vitest",
|
24
|
+
"test:coverage": "vitest --coverage",
|
25
|
+
"lint": "eslint src --ext .ts,.tsx,.js,.jsx",
|
26
|
+
"lint:fix": "eslint src --ext .ts,.tsx,.js,.jsx --fix",
|
27
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,.js,jsx,json,md}\"",
|
28
|
+
"typecheck": "tsc --noEmit",
|
29
|
+
"prepublishOnly": "pnpm run build"
|
30
|
+
},
|
31
|
+
"keywords": [
|
32
|
+
"utils",
|
33
|
+
"typescript"
|
34
|
+
],
|
35
|
+
"author": "jane",
|
36
|
+
"license": "MIT",
|
37
|
+
"devDependencies": {
|
38
|
+
"@types/crypto-js": "^4.2.2",
|
39
|
+
"@typescript-eslint/eslint-plugin": "^8.40.0",
|
40
|
+
"@typescript-eslint/parser": "^8.40.0",
|
41
|
+
"@vitest/coverage-v8": "^3.2.4",
|
42
|
+
"eslint": "^9.33.0",
|
43
|
+
"eslint-config-prettier": "^10.1.8",
|
44
|
+
"eslint-plugin-prettier": "^5.0.0",
|
45
|
+
"jsdom": "^26.1.0",
|
46
|
+
"prettier": "^3.0.0",
|
47
|
+
"typescript": "^5.0.0",
|
48
|
+
"vite": "^7.1.2",
|
49
|
+
"vite-plugin-dts": "^4.5.4",
|
50
|
+
"vitepress": "^1.6.4",
|
51
|
+
"vitest": "^3.2.4"
|
52
|
+
},
|
53
|
+
"dependencies": {
|
54
|
+
"crypto-js": "^4.2.0",
|
55
|
+
"dayjs": "^1.11.0"
|
56
|
+
},
|
57
|
+
"packageManager": "pnpm@10.0.0",
|
58
|
+
"engines": {
|
59
|
+
"node": ">=18.0.0",
|
60
|
+
"pnpm": ">=9.0.0"
|
61
|
+
}
|
62
|
+
}
|