@bettergi/utils 0.1.11 → 0.1.13
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.
- package/README.md +54 -25
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mouse.d.ts +2 -0
- package/dist/mouse.js +4 -4
- package/dist/ocr.d.ts +26 -1
- package/dist/ocr.js +28 -0
- package/dist/progress.d.ts +56 -0
- package/dist/progress.js +73 -0
- package/dist/time.d.ts +20 -0
- package/dist/time.js +32 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -50,20 +50,26 @@ await navigateToTab(() => {
|
|
|
50
50
|
// 在整个画面内搜索图片,找不到返回 undefined
|
|
51
51
|
const i1 = findImage("assets/关闭.png", { use3Channels: true }); // 匹配颜色
|
|
52
52
|
|
|
53
|
-
// 在指定方向上搜索图片,找不到返回 undefined
|
|
54
|
-
const i2 = findImageInDirection("assets/关闭.png", "north-east");
|
|
55
|
-
|
|
56
53
|
// 在指定区域内搜索图片,找不到返回 undefined
|
|
57
|
-
const
|
|
54
|
+
const i2 = findImageWithinBounds("assets/关闭.png", 960, 0, 960, 1080);
|
|
55
|
+
|
|
56
|
+
// 在指定坐标范围内搜索图片,找不到返回 undefined
|
|
57
|
+
const i3 = findImageBetweenCoordinates("assets/关闭.png", 960, 0, 1920, 1080);
|
|
58
|
+
|
|
59
|
+
// 在指定方向上搜索图片,找不到返回 undefined
|
|
60
|
+
const i4 = findImageInDirection("assets/关闭.png", "north-east");
|
|
58
61
|
|
|
59
62
|
// 在整个画面内搜索文本(不包含、忽略大小写),找不到返回 undefined
|
|
60
63
|
const t1 = findText("购买");
|
|
61
64
|
|
|
62
|
-
// 在指定方向上搜索文本(包含、忽略大小写),找不到返回 undefined
|
|
63
|
-
const t2 = findTextInDirection("师傅", "east", { contains: true, ignoreCase: true });
|
|
64
|
-
|
|
65
65
|
// 在指定区域内搜索文本(不包含、忽略大小写),找不到返回 undefined
|
|
66
|
-
const
|
|
66
|
+
const t2 = findTextWithinBounds("确认", 960, 540, 960, 540);
|
|
67
|
+
|
|
68
|
+
// 在指定坐标范围内搜索文本(不包含、忽略大小写),找不到返回 undefined
|
|
69
|
+
const t3 = findTextBetweenCoordinates("确认", 960, 540, 1920, 1080);
|
|
70
|
+
|
|
71
|
+
// 在指定方向上搜索文本(包含、忽略大小写),找不到返回 undefined
|
|
72
|
+
const t4 = findTextInDirection("师傅", "east", { contains: true, ignoreCase: true });
|
|
67
73
|
```
|
|
68
74
|
|
|
69
75
|
### 行为流程
|
|
@@ -71,28 +77,24 @@ const t3 = findTextWithinBounds("确认", 960, 540, 960, 540);
|
|
|
71
77
|
> 对脚本开发过程中常见工作流的抽象,例如: 等待/断言 操作/元素/区域 完成/出现/消失。
|
|
72
78
|
|
|
73
79
|
```ts
|
|
74
|
-
// 等待直到找不到 [关闭按钮] ,重试5次,每隔1
|
|
80
|
+
// 等待直到找不到 [关闭按钮] ,重试5次,每隔1秒重试一次(默认参数),期间按 Esc 键
|
|
75
81
|
const done = await waitForAction(
|
|
76
82
|
() => findImageInDirection("assets/关闭.png", "north-east") === undefined,
|
|
77
|
-
() => keyPress("ESCAPE")
|
|
78
|
-
{ maxAttempts: 5, retryInterval: 1000 }
|
|
83
|
+
() => keyPress("ESCAPE")
|
|
79
84
|
);
|
|
80
85
|
if (!done) throw new Error("关闭页面超时");
|
|
81
86
|
|
|
82
|
-
// 断言 "生日" 文字区域即将出现,重试
|
|
87
|
+
// 断言 "生日" 文字区域即将出现,重试10次,每隔1秒重试一次,期间按 Esc 键
|
|
83
88
|
await assertRegionAppearing(
|
|
84
89
|
() => findTextInDirection("生日", "north-west"),
|
|
85
90
|
"打开派蒙菜单超时",
|
|
86
91
|
() => keyPress("ESCAPE"),
|
|
87
|
-
{ maxAttempts:
|
|
92
|
+
{ maxAttempts: 10, retryInterval: 1000 }
|
|
88
93
|
);
|
|
89
94
|
|
|
90
|
-
// 断言 "购买"
|
|
95
|
+
// 断言 "购买" 文字区域即将消失,重试5次,每隔1秒重试一次(默认参数),期间如果存在 "购买" 按钮则点击
|
|
91
96
|
const findButton = () => findTextWithinBounds("购买", 500, 740, 900, 110);
|
|
92
|
-
await assertRegionDisappearing(findButton, "点击购买按钮超时", () => findButton()?.click()
|
|
93
|
-
maxAttempts: 5,
|
|
94
|
-
retryInterval: 1000
|
|
95
|
-
});
|
|
97
|
+
await assertRegionDisappearing(findButton, "点击购买按钮超时", () => findButton()?.click());
|
|
96
98
|
```
|
|
97
99
|
|
|
98
100
|
### 鼠标操作
|
|
@@ -119,27 +121,54 @@ await mouseScrollUpLines(99);
|
|
|
119
121
|
await mouseScrollDownLines(1, 115);
|
|
120
122
|
```
|
|
121
123
|
|
|
122
|
-
###
|
|
124
|
+
### 状态管理和持久化
|
|
123
125
|
|
|
124
|
-
>
|
|
126
|
+
> 基于深度 Proxy 实现的对象数据持久化,能够在数据被修改时自动同步至文件。使开发其能够像操作普通对象一样进行数据读写,而无需关心底层的持久化细节。
|
|
125
127
|
|
|
126
128
|
```ts
|
|
127
129
|
// 创建/读取存储对象,保存到存储文件 store/my-data.json 中
|
|
128
|
-
const
|
|
130
|
+
const store = useStore<{ lastUsedTime?: number; count: number }>("my-data");
|
|
129
131
|
// 默认值版本
|
|
130
132
|
// const state = useStoreWithDefaults("my-data", { lastUsedTime: 0, count: 0 });
|
|
131
133
|
|
|
132
|
-
if (
|
|
133
|
-
log.info(`欢迎回来!上次使用时间: ${
|
|
134
|
+
if (store?.lastUsedTime) {
|
|
135
|
+
log.info(`欢迎回来!上次使用时间: ${store.lastUsedTime},计数器已累计至: ${store.count}`);
|
|
134
136
|
}
|
|
135
137
|
try {
|
|
136
138
|
// 模拟脚本运行期间状态的变化
|
|
137
139
|
for (let i = 0; i < Math.floor(Math.random() * 100); i++) {
|
|
138
|
-
|
|
140
|
+
store.count = (store.count || 0) + 1; // 自动保存到文件
|
|
139
141
|
}
|
|
140
142
|
} finally {
|
|
141
|
-
|
|
143
|
+
store.lastUsedTime = Date.now(); // 自动保存到文件
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 进度追踪
|
|
148
|
+
|
|
149
|
+
> 创建进度追踪器并设置总进度,通过递进函数增加进度,开发者可以获取当前进度、当前耗时、平均耗时、预估剩余时间等数据。
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const total = 100;
|
|
153
|
+
// 创建进度追踪器,使用默认日志记录器(可配置),打印间隔最小3秒
|
|
154
|
+
const tracker = new ProgressTracker(total, { interval: 3000 });
|
|
155
|
+
for (let i = 0; i < total; i++) {
|
|
156
|
+
await sleep(Math.round(Math.random() * 200));
|
|
157
|
+
// 递进并尝试打印进度和消息
|
|
158
|
+
tracker.tick({ message: "等待任务完成..." });
|
|
159
|
+
|
|
160
|
+
// 也可以主动追踪,使用传递的进度信息自主控制打印时机和内容
|
|
161
|
+
// tracker.track((progress, shouldPrint, printed) => {
|
|
162
|
+
// if (!shouldPrint()) return;
|
|
163
|
+
// log.info(
|
|
164
|
+
// "[进度: {pct} 预计剩余时间: {eta}]: 等待任务完成...",
|
|
165
|
+
// progress.formatted.percentage,
|
|
166
|
+
// progress.formatted.remaining
|
|
167
|
+
// );
|
|
168
|
+
// printed();
|
|
169
|
+
// });
|
|
142
170
|
}
|
|
171
|
+
tracker.complete(`执行完成`);
|
|
143
172
|
```
|
|
144
173
|
|
|
145
174
|
### 网络请求
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/mouse.d.ts
CHANGED
package/dist/mouse.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @param options 鼠标移动选项
|
|
5
5
|
*/
|
|
6
6
|
export const mouseMoveAlongWaypoints = async (waypoints, options) => {
|
|
7
|
-
const { shouldDrag = false, timeout = 0 } = options || {};
|
|
7
|
+
const { shouldDrag = false, delay = 50, timeout = 0 } = options || {};
|
|
8
8
|
try {
|
|
9
9
|
const startTime = Date.now();
|
|
10
10
|
for (let i = 0; i < waypoints.length; i++) {
|
|
@@ -13,9 +13,9 @@ export const mouseMoveAlongWaypoints = async (waypoints, options) => {
|
|
|
13
13
|
leftButtonDown();
|
|
14
14
|
moveMouseTo(Math.trunc(waypoints[i].x), Math.trunc(waypoints[i].y));
|
|
15
15
|
// 等待指定延迟
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
18
|
-
await sleep(
|
|
16
|
+
const duration = Math.trunc(waypoints[i].delay || delay);
|
|
17
|
+
if (duration > 0)
|
|
18
|
+
await sleep(duration);
|
|
19
19
|
// 超时检查
|
|
20
20
|
if (timeout > 0 && Date.now() - startTime > timeout)
|
|
21
21
|
return false;
|
package/dist/ocr.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { RetryOptions } from "./workflow";
|
|
2
|
+
/** 识别对象实例 */
|
|
2
3
|
export type ROInstance = InstanceType<typeof RecognitionObject>;
|
|
4
|
+
/** 识别对象配置 */
|
|
3
5
|
export type ROConfig = Partial<{
|
|
4
|
-
[K in keyof ROInstance]: ROInstance[K];
|
|
6
|
+
[K in keyof ROInstance as ROInstance[K] extends Function ? never : K]: ROInstance[K];
|
|
5
7
|
}>;
|
|
6
8
|
export type MatchDirection = "north" /** 上半边 */ | "north-east" /** 右上四分之一 */ | "east" /** 右半边 */ | "south-east" /** 右下四分之一 */ | "south" /** 下半边 */ | "south-west" /** 左下四分之一 */ | "west" /** 左半边 */ | "north-west"; /** 左上四分之一 */
|
|
7
9
|
/**
|
|
@@ -22,6 +24,17 @@ export declare const findImage: (path: string, config?: ROConfig) => Region | un
|
|
|
22
24
|
* @returns 如果找到匹配的图片区域,则返回该区域
|
|
23
25
|
*/
|
|
24
26
|
export declare const findImageWithinBounds: (path: string, x: number, y: number, w: number, h: number, config?: ROConfig) => Region | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* 在指定坐标范围内搜索图片
|
|
29
|
+
* @param path 图片路径
|
|
30
|
+
* @param left 左边界偏移量(像素)
|
|
31
|
+
* @param top 上边界偏移量(像素)
|
|
32
|
+
* @param right 右边界偏移量(像素)
|
|
33
|
+
* @param bottom 下边界偏移量(像素)
|
|
34
|
+
* @param config 识别对象配置
|
|
35
|
+
* @returns 如果找到匹配的图片区域,则返回该区域
|
|
36
|
+
*/
|
|
37
|
+
export declare const findImageBetweenCoordinates: (path: string, left: number, top: number, right: number, bottom: number, config?: ROConfig) => Region | undefined;
|
|
25
38
|
/**
|
|
26
39
|
* 在指定方向上搜索图片
|
|
27
40
|
* @param path 图片路径
|
|
@@ -57,6 +70,18 @@ export declare const findText: (text: string, options?: TextMatchOptions, config
|
|
|
57
70
|
* @returns 如果找到匹配的文本区域,则返回该区域
|
|
58
71
|
*/
|
|
59
72
|
export declare const findTextWithinBounds: (text: string, x: number, y: number, w: number, h: number, options?: TextMatchOptions, config?: ROConfig) => Region | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* 在指定坐标范围内搜索文本
|
|
75
|
+
* @param text 待搜索文本
|
|
76
|
+
* @param left 左边界偏移量(像素)
|
|
77
|
+
* @param top 上边界偏移量(像素)
|
|
78
|
+
* @param right 右边界偏移量(像素)
|
|
79
|
+
* @param bottom 下边界偏移量(像素)
|
|
80
|
+
* @param options 搜索选项
|
|
81
|
+
* @param config 识别对象配置
|
|
82
|
+
* @returns 如果找到匹配的文本区域,则返回该区域
|
|
83
|
+
*/
|
|
84
|
+
export declare const findTextBetweenCoordinates: (text: string, left: number, top: number, right: number, bottom: number, options?: TextMatchOptions, config?: ROConfig) => Region | undefined;
|
|
60
85
|
/**
|
|
61
86
|
* 在指定方向上搜索文本
|
|
62
87
|
* @param text 待搜索文本
|
package/dist/ocr.js
CHANGED
|
@@ -65,6 +65,19 @@ export const findImageWithinBounds = (path, x, y, w, h, config = {}) => {
|
|
|
65
65
|
ir.dispose();
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* 在指定坐标范围内搜索图片
|
|
70
|
+
* @param path 图片路径
|
|
71
|
+
* @param left 左边界偏移量(像素)
|
|
72
|
+
* @param top 上边界偏移量(像素)
|
|
73
|
+
* @param right 右边界偏移量(像素)
|
|
74
|
+
* @param bottom 下边界偏移量(像素)
|
|
75
|
+
* @param config 识别对象配置
|
|
76
|
+
* @returns 如果找到匹配的图片区域,则返回该区域
|
|
77
|
+
*/
|
|
78
|
+
export const findImageBetweenCoordinates = (path, left, top, right, bottom, config = {}) => {
|
|
79
|
+
return findImageWithinBounds(path, left, top, right - left, bottom - top, config);
|
|
80
|
+
};
|
|
68
81
|
/**
|
|
69
82
|
* 在指定方向上搜索图片
|
|
70
83
|
* @param path 图片路径
|
|
@@ -134,6 +147,20 @@ export const findTextWithinBounds = (text, x, y, w, h, options, config = {}) =>
|
|
|
134
147
|
ir.dispose();
|
|
135
148
|
}
|
|
136
149
|
};
|
|
150
|
+
/**
|
|
151
|
+
* 在指定坐标范围内搜索文本
|
|
152
|
+
* @param text 待搜索文本
|
|
153
|
+
* @param left 左边界偏移量(像素)
|
|
154
|
+
* @param top 上边界偏移量(像素)
|
|
155
|
+
* @param right 右边界偏移量(像素)
|
|
156
|
+
* @param bottom 下边界偏移量(像素)
|
|
157
|
+
* @param options 搜索选项
|
|
158
|
+
* @param config 识别对象配置
|
|
159
|
+
* @returns 如果找到匹配的文本区域,则返回该区域
|
|
160
|
+
*/
|
|
161
|
+
export const findTextBetweenCoordinates = (text, left, top, right, bottom, options, config = {}) => {
|
|
162
|
+
return findTextWithinBounds(text, left, top, right - left, bottom - top, options, config);
|
|
163
|
+
};
|
|
137
164
|
/**
|
|
138
165
|
* 在指定方向上搜索文本
|
|
139
166
|
* @param text 待搜索文本
|
|
@@ -179,6 +206,7 @@ export const findTextWithinListView = async (text, listView, matchOptions, retry
|
|
|
179
206
|
};
|
|
180
207
|
const isTextFoundOrBottomReached = await waitForAction(() => findTargetText() != undefined || isReachedBottom(), async () => {
|
|
181
208
|
moveMouseTo(x + w - paddingX, y + paddingY); // 移动到滚动条附近
|
|
209
|
+
await sleep(50);
|
|
182
210
|
await mouseScrollDownLines(scrollLines, lineHeight); // 滚动指定行数
|
|
183
211
|
}, { maxAttempts, retryInterval });
|
|
184
212
|
return isTextFoundOrBottomReached ? findTargetText() : undefined;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** 进度信息 */
|
|
2
|
+
export type Progress = {
|
|
3
|
+
/** 当前进度 */
|
|
4
|
+
current: number;
|
|
5
|
+
/** 总进度 */
|
|
6
|
+
total: number;
|
|
7
|
+
/** 完成百分比 */
|
|
8
|
+
percentage: number;
|
|
9
|
+
/** 已用时间(毫秒) */
|
|
10
|
+
elapsed: number;
|
|
11
|
+
/** 平均每单位时间(毫秒) */
|
|
12
|
+
average: number;
|
|
13
|
+
/** 预计剩余时间(毫秒) */
|
|
14
|
+
remaining: number;
|
|
15
|
+
/** 格式化后的进度信息 */
|
|
16
|
+
formatted: {
|
|
17
|
+
percentage: string;
|
|
18
|
+
elapsed: string;
|
|
19
|
+
average: string;
|
|
20
|
+
remaining: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
/** 进度日志记录器 */
|
|
24
|
+
export type ProgressLogger = (message: string, progress: Progress) => void;
|
|
25
|
+
export type ProgressTrackerConfig = {
|
|
26
|
+
/** 日志记录器 */
|
|
27
|
+
logger?: ProgressLogger;
|
|
28
|
+
/** 节流间隔(毫秒),默认3000 */
|
|
29
|
+
interval?: number;
|
|
30
|
+
};
|
|
31
|
+
/** 进度递进选项 */
|
|
32
|
+
export type ProgressTickOptions = {
|
|
33
|
+
/** 递进后打印消息 */
|
|
34
|
+
message?: string;
|
|
35
|
+
/** 递进的幅度 */
|
|
36
|
+
increment?: number;
|
|
37
|
+
};
|
|
38
|
+
/** 进度追踪器 */
|
|
39
|
+
export declare class ProgressTracker {
|
|
40
|
+
private total;
|
|
41
|
+
private current;
|
|
42
|
+
private startTime;
|
|
43
|
+
private readonly logger;
|
|
44
|
+
private readonly interval;
|
|
45
|
+
private lastPrintTime;
|
|
46
|
+
constructor(total: number, config?: ProgressTrackerConfig);
|
|
47
|
+
private readonly defaultLogger;
|
|
48
|
+
tick(options?: ProgressTickOptions): void;
|
|
49
|
+
track(callback: (progress: Progress, shouldPrint: () => boolean, printed: () => void) => void): void;
|
|
50
|
+
complete(message: string): void;
|
|
51
|
+
reset(): void;
|
|
52
|
+
private print;
|
|
53
|
+
private shouldPrint;
|
|
54
|
+
private printed;
|
|
55
|
+
getProgress(): Progress;
|
|
56
|
+
}
|
package/dist/progress.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { formatDurationAsClock, formatDurationAsReadable } from "./time";
|
|
2
|
+
/** 进度追踪器 */
|
|
3
|
+
export class ProgressTracker {
|
|
4
|
+
total = 0;
|
|
5
|
+
current = 0;
|
|
6
|
+
startTime = Date.now();
|
|
7
|
+
logger;
|
|
8
|
+
interval;
|
|
9
|
+
lastPrintTime = 0;
|
|
10
|
+
constructor(total, config) {
|
|
11
|
+
const { logger, interval: throttleInterval = 3000 } = config || {};
|
|
12
|
+
this.total = total;
|
|
13
|
+
this.logger = logger || this.defaultLogger;
|
|
14
|
+
this.interval = throttleInterval;
|
|
15
|
+
}
|
|
16
|
+
defaultLogger = (message, progress) => {
|
|
17
|
+
log.info("[🚧 {pct} ⏳ {eta}]: {msg}", progress.formatted.percentage.padStart(6), progress.current > 0 && progress.elapsed > 0 ? progress.formatted.remaining : "--:--:--", message);
|
|
18
|
+
};
|
|
19
|
+
tick(options) {
|
|
20
|
+
const { message, increment = 1 } = options || {};
|
|
21
|
+
this.current = Math.min(this.current + increment, this.total);
|
|
22
|
+
if (message)
|
|
23
|
+
this.print(message);
|
|
24
|
+
}
|
|
25
|
+
track(callback) {
|
|
26
|
+
const progress = this.getProgress();
|
|
27
|
+
const shouldPrint = this.shouldPrint.bind(this);
|
|
28
|
+
const printed = this.printed.bind(this);
|
|
29
|
+
callback(progress, shouldPrint, printed);
|
|
30
|
+
}
|
|
31
|
+
complete(message) {
|
|
32
|
+
this.current = this.total;
|
|
33
|
+
if (message)
|
|
34
|
+
this.print(message, true);
|
|
35
|
+
}
|
|
36
|
+
reset() {
|
|
37
|
+
this.current = 0;
|
|
38
|
+
this.startTime = Date.now();
|
|
39
|
+
this.lastPrintTime = 0;
|
|
40
|
+
}
|
|
41
|
+
print(message, force = false) {
|
|
42
|
+
if (force || this.shouldPrint()) {
|
|
43
|
+
this.logger(message, this.getProgress());
|
|
44
|
+
this.printed();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
shouldPrint() {
|
|
48
|
+
return Date.now() - this.lastPrintTime >= this.interval;
|
|
49
|
+
}
|
|
50
|
+
printed() {
|
|
51
|
+
this.lastPrintTime = Date.now();
|
|
52
|
+
}
|
|
53
|
+
getProgress() {
|
|
54
|
+
const percentage = this.current / this.total;
|
|
55
|
+
const elapsed = Date.now() - this.startTime;
|
|
56
|
+
const average = this.current > 0 ? elapsed / this.current : 0;
|
|
57
|
+
const remaining = (this.total - this.current) * average;
|
|
58
|
+
return {
|
|
59
|
+
current: this.current,
|
|
60
|
+
total: this.total,
|
|
61
|
+
percentage,
|
|
62
|
+
elapsed,
|
|
63
|
+
average,
|
|
64
|
+
remaining,
|
|
65
|
+
formatted: {
|
|
66
|
+
percentage: `${(percentage * 100).toFixed(1)}%`,
|
|
67
|
+
elapsed: formatDurationAsReadable(elapsed),
|
|
68
|
+
average: formatDurationAsReadable(average),
|
|
69
|
+
remaining: formatDurationAsClock(remaining)
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
package/dist/time.d.ts
CHANGED
|
@@ -6,3 +6,23 @@ export declare const getNextDay4AM: () => Date;
|
|
|
6
6
|
* 获取下一个(含当日)周一凌晨4点的时间
|
|
7
7
|
*/
|
|
8
8
|
export declare const getNextMonday4AM: () => Date;
|
|
9
|
+
/**
|
|
10
|
+
* 解析时长
|
|
11
|
+
* @param duration 时长(毫秒)
|
|
12
|
+
*/
|
|
13
|
+
export declare const parseDuration: (duration: number) => {
|
|
14
|
+
h: number;
|
|
15
|
+
m: number;
|
|
16
|
+
s: number;
|
|
17
|
+
ms: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* 将时长转换为时钟字符串
|
|
21
|
+
* @param duration 时长(毫秒)
|
|
22
|
+
*/
|
|
23
|
+
export declare const formatDurationAsClock: (duration: number) => string;
|
|
24
|
+
/**
|
|
25
|
+
* 将时长转换为可读格式
|
|
26
|
+
* @param duration 时长(毫秒)
|
|
27
|
+
*/
|
|
28
|
+
export declare const formatDurationAsReadable: (duration: number) => string;
|
package/dist/time.js
CHANGED
|
@@ -23,3 +23,35 @@ export const getNextMonday4AM = () => {
|
|
|
23
23
|
result.setDate(now.getDate() + daysUntilNextMonday);
|
|
24
24
|
return result;
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* 解析时长
|
|
28
|
+
* @param duration 时长(毫秒)
|
|
29
|
+
*/
|
|
30
|
+
export const parseDuration = (duration) => {
|
|
31
|
+
return {
|
|
32
|
+
h: Math.floor(duration / 3600000),
|
|
33
|
+
m: Math.floor((duration % 3600000) / 60000),
|
|
34
|
+
s: Math.floor((duration % 60000) / 1000),
|
|
35
|
+
ms: Math.floor(duration % 1000)
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* 将时长转换为时钟字符串
|
|
40
|
+
* @param duration 时长(毫秒)
|
|
41
|
+
*/
|
|
42
|
+
export const formatDurationAsClock = (duration) => {
|
|
43
|
+
return Object.values(parseDuration(duration))
|
|
44
|
+
.slice(0, 3)
|
|
45
|
+
.map(num => String(num).padStart(2, "0"))
|
|
46
|
+
.join(":");
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* 将时长转换为可读格式
|
|
50
|
+
* @param duration 时长(毫秒)
|
|
51
|
+
*/
|
|
52
|
+
export const formatDurationAsReadable = (duration) => {
|
|
53
|
+
return Object.entries(parseDuration(duration))
|
|
54
|
+
.filter(([, value]) => value > 0)
|
|
55
|
+
.map(([unit, value]) => `${value}${unit}`)
|
|
56
|
+
.join(" ");
|
|
57
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bettergi/utils",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "开发 BetterGI 脚本常用工具集",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Bread Grocery<https://github.com/breadgrocery>",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@bettergi/types": "^0.1.3",
|
|
37
|
-
"rimraf": "^6.0
|
|
37
|
+
"rimraf": "^6.1.0",
|
|
38
38
|
"typescript": "^5.9.3"
|
|
39
39
|
}
|
|
40
40
|
}
|