@anjianshi/utils 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.
- package/.eslintrc.cjs +6 -0
- package/README.md +1 -0
- package/build-cleanup.cjs +10 -0
- package/env-browser/device.d.ts +24 -0
- package/env-browser/device.js +49 -0
- package/env-browser/global.d.ts +11 -0
- package/env-browser/global.js +14 -0
- package/env-browser/load-script.d.ts +5 -0
- package/env-browser/load-script.js +13 -0
- package/env-browser/logging.d.ts +18 -0
- package/env-browser/logging.js +49 -0
- package/env-browser/manage-vconsole.d.ts +16 -0
- package/env-browser/manage-vconsole.js +38 -0
- package/env-node/index.d.ts +17 -0
- package/env-node/index.js +45 -0
- package/env-node/logging.d.ts +64 -0
- package/env-node/logging.js +159 -0
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/init-dayjs.d.ts +2 -0
- package/init-dayjs.js +7 -0
- package/lang/async.d.ts +19 -0
- package/lang/async.js +34 -0
- package/lang/index.d.ts +5 -0
- package/lang/index.js +5 -0
- package/lang/object.d.ts +5 -0
- package/lang/object.js +31 -0
- package/lang/string.d.ts +17 -0
- package/lang/string.js +59 -0
- package/lang/time.d.ts +10 -0
- package/lang/time.js +18 -0
- package/lang/types.d.ts +98 -0
- package/lang/types.js +41 -0
- package/logging/adapt.d.ts +5 -0
- package/logging/adapt.js +38 -0
- package/logging/formatters.d.ts +10 -0
- package/logging/formatters.js +22 -0
- package/logging/index.d.ts +43 -0
- package/logging/index.js +72 -0
- package/package.json +43 -0
- package/src/.eslintrc.cjs +8 -0
- package/src/env-browser/.eslintrc.cjs +6 -0
- package/src/env-browser/device.ts +60 -0
- package/src/env-browser/global.ts +20 -0
- package/src/env-browser/load-script.ts +13 -0
- package/src/env-browser/logging.ts +58 -0
- package/src/env-browser/manage-vconsole.ts +54 -0
- package/src/env-node/.eslintrc.cjs +4 -0
- package/src/env-node/index.ts +46 -0
- package/src/env-node/logging.ts +202 -0
- package/src/index.ts +3 -0
- package/src/init-dayjs.ts +8 -0
- package/src/lang/async.ts +47 -0
- package/src/lang/index.ts +5 -0
- package/src/lang/object.ts +28 -0
- package/src/lang/string.ts +62 -0
- package/src/lang/time.ts +19 -0
- package/src/lang/types.ts +138 -0
- package/src/logging/adapt.ts +40 -0
- package/src/logging/formatters.ts +23 -0
- package/src/logging/index.ts +87 -0
- package/src/url.ts +153 -0
- package/url.d.ts +28 -0
- package/url.js +116 -0
package/lang/object.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 对两个对象进行浅比较
|
|
3
|
+
* 代码取自 react-addons-shallow-compare 包
|
|
4
|
+
*/
|
|
5
|
+
export function shallowEqual(objA, objB) {
|
|
6
|
+
if (is(objA, objB))
|
|
7
|
+
return true;
|
|
8
|
+
if (!isObject(objA) || !isObject(objB))
|
|
9
|
+
return false;
|
|
10
|
+
const keysA = Object.keys(objA);
|
|
11
|
+
const keysB = Object.keys(objB);
|
|
12
|
+
if (keysA.length !== keysB.length)
|
|
13
|
+
return false;
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
15
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
16
|
+
const key = keysA[i];
|
|
17
|
+
// eslint-disable-next-line prefer-object-has-own
|
|
18
|
+
if (!Object.prototype.hasOwnProperty.call(objB, key) || !is(objA[key], objB[key]))
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
function is(x, y) {
|
|
24
|
+
if (x === y)
|
|
25
|
+
return x !== 0 || y !== 0 || 1 / x === 1 / y;
|
|
26
|
+
else
|
|
27
|
+
return x !== x && y !== y; // eslint-disable-line no-self-compare
|
|
28
|
+
}
|
|
29
|
+
function isObject(value) {
|
|
30
|
+
return typeof value === 'object' && value !== null;
|
|
31
|
+
}
|
package/lang/string.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将数字字符串化,并在左侧填充 0 直到达到 length 参数指定的长度
|
|
3
|
+
* 若数字本身达到或超过此长度,则不填充
|
|
4
|
+
*/
|
|
5
|
+
export declare function zfill(num: number, length?: number): string;
|
|
6
|
+
export declare function keywordCompare(keyword: string, target: string): boolean;
|
|
7
|
+
export declare function numericCompare(a: string, b: string): number;
|
|
8
|
+
/**
|
|
9
|
+
* 字符串解析成整数,补充安全措施:
|
|
10
|
+
* 1. 默认设置 radix 为 10,无需再手动指定
|
|
11
|
+
* 2. 支持指定 fallback,当解析出来的数字是 NaN 时返回这个值
|
|
12
|
+
*/
|
|
13
|
+
export declare function safeParseInt(value: string | number, fallback?: number, redix?: number): number;
|
|
14
|
+
/**
|
|
15
|
+
* 字符串解析成浮点数;若解析结果是 NaN,返回 fallback
|
|
16
|
+
*/
|
|
17
|
+
export declare function safeParseFloat(value: string | number, fallback: number): number;
|
package/lang/string.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 字符串处理相关函数
|
|
3
|
+
*/
|
|
4
|
+
import escapeRegExp from 'lodash/escapeRegExp.js';
|
|
5
|
+
import padStart from 'lodash/padStart.js';
|
|
6
|
+
/**
|
|
7
|
+
* 将数字字符串化,并在左侧填充 0 直到达到 length 参数指定的长度
|
|
8
|
+
* 若数字本身达到或超过此长度,则不填充
|
|
9
|
+
*/
|
|
10
|
+
export function zfill(num, length = 2) {
|
|
11
|
+
return padStart(String(num), length, '0');
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 执行关键词匹配
|
|
15
|
+
* 成功返回 true;失败返回 false
|
|
16
|
+
*/
|
|
17
|
+
const kwCache = {}; // 避免大量重复构建正则表达式影响性能
|
|
18
|
+
export function keywordCompare(keyword, target) {
|
|
19
|
+
if (!keyword)
|
|
20
|
+
return true;
|
|
21
|
+
if (!(keyword in kwCache)) {
|
|
22
|
+
const regStr = keyword.split('').map(escapeRegExp).join('.*');
|
|
23
|
+
kwCache[keyword] = new RegExp(regStr, 'i');
|
|
24
|
+
}
|
|
25
|
+
return kwCache[keyword].test(target);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 对两个字符串进行排序,支持处理逻辑上的数字。
|
|
29
|
+
* 两个字符串都完全由数字组成,且都不以 0 开头时,以数字大小排序;否则以字符串形式排序。
|
|
30
|
+
*
|
|
31
|
+
* 以 0 开头是特例,例如 019 和 12 两个字符串,按直觉还是应该 019 在前面,毕竟本质还是字符串排序。
|
|
32
|
+
*
|
|
33
|
+
*
|
|
34
|
+
* "123" "456" 数字排序
|
|
35
|
+
* "123你好" "133我好" 字符串排序
|
|
36
|
+
* "019" "12" 字符串排序
|
|
37
|
+
*/
|
|
38
|
+
const _pattern = /^[1-9][0-9]*$/;
|
|
39
|
+
export function numericCompare(a, b) {
|
|
40
|
+
if (_pattern.exec(a) && _pattern.exec(b))
|
|
41
|
+
return parseInt(a, 10) - parseInt(b, 10);
|
|
42
|
+
return a.localeCompare(b);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 字符串解析成整数,补充安全措施:
|
|
46
|
+
* 1. 默认设置 radix 为 10,无需再手动指定
|
|
47
|
+
* 2. 支持指定 fallback,当解析出来的数字是 NaN 时返回这个值
|
|
48
|
+
*/
|
|
49
|
+
export function safeParseInt(value, fallback, redix = 10) {
|
|
50
|
+
const raw = parseInt(String(value), redix);
|
|
51
|
+
return isFinite(raw) ? raw : fallback ?? raw;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 字符串解析成浮点数;若解析结果是 NaN,返回 fallback
|
|
55
|
+
*/
|
|
56
|
+
export function safeParseFloat(value, fallback) {
|
|
57
|
+
const raw = parseFloat(String(value));
|
|
58
|
+
return isFinite(raw) ? raw : fallback;
|
|
59
|
+
}
|
package/lang/time.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 时间处理相关函数
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 返回指定时间长度的 毫秒数
|
|
6
|
+
*/
|
|
7
|
+
export declare function daysMS(n: number): number;
|
|
8
|
+
export declare function hoursMS(n: number): number;
|
|
9
|
+
export declare function minutesMS(n: number): number;
|
|
10
|
+
export declare function secondsMS(n: number): number;
|
package/lang/time.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 时间处理相关函数
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 返回指定时间长度的 毫秒数
|
|
6
|
+
*/
|
|
7
|
+
export function daysMS(n) {
|
|
8
|
+
return hoursMS(n * 24);
|
|
9
|
+
}
|
|
10
|
+
export function hoursMS(n) {
|
|
11
|
+
return minutesMS(n * 60);
|
|
12
|
+
}
|
|
13
|
+
export function minutesMS(n) {
|
|
14
|
+
return secondsMS(n * 60);
|
|
15
|
+
}
|
|
16
|
+
export function secondsMS(n) {
|
|
17
|
+
return n * 1000;
|
|
18
|
+
}
|
package/lang/types.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 对类型系统的辅助、补充
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 解决 TypeScript 中,数组字面量 [1, 'a'] 无法自动识别为 tuple 的问题
|
|
6
|
+
*
|
|
7
|
+
* 使用方法:
|
|
8
|
+
* const t = tuple(1, 'a') // 类型会识别为 [1, 'a'] 而不是 (number | string)[]
|
|
9
|
+
*
|
|
10
|
+
* 待官方提供语言层面的支持
|
|
11
|
+
* https://github.com/microsoft/TypeScript/issues/27179
|
|
12
|
+
* https://github.com/microsoft/TypeScript/issues/16656
|
|
13
|
+
*/
|
|
14
|
+
export declare function tuple<T extends unknown[]>(...elements: T): T;
|
|
15
|
+
/**
|
|
16
|
+
* 将一个对象中的指定 key 设为必须的
|
|
17
|
+
*/
|
|
18
|
+
export type RequiredFields<T, K extends keyof T> = Omit<T, K> & Pick<Required<T>, K>;
|
|
19
|
+
/**
|
|
20
|
+
* 将一个对象中的指定 key 设为非必须的
|
|
21
|
+
*/
|
|
22
|
+
export type OptionalFields<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
23
|
+
/**
|
|
24
|
+
* 用 ReplaceT 中的字段定义代替 T 中的
|
|
25
|
+
*/
|
|
26
|
+
export type ReplaceFields<T, ReplaceT> = Omit<T, keyof ReplaceT> & ReplaceT;
|
|
27
|
+
/**
|
|
28
|
+
* 获取一个对象所有 value 的集合
|
|
29
|
+
*/
|
|
30
|
+
export type ValueOf<T> = T extends {
|
|
31
|
+
[_ in keyof T]: infer U;
|
|
32
|
+
} ? U : never;
|
|
33
|
+
/**
|
|
34
|
+
* 获取指定类型的 key 的集合
|
|
35
|
+
* 可以解决 keyof 无法正确获取继承了 plain object interface 的类型的 key 的问题
|
|
36
|
+
*
|
|
37
|
+
* interface Base { [key: string]: number }
|
|
38
|
+
* interface MyType extends Base { a: 1, b: 2 }
|
|
39
|
+
* keyof MyType // string | number
|
|
40
|
+
* KnownKeys<MyType> // 'a' | 'b'
|
|
41
|
+
*/
|
|
42
|
+
export type KnownKeys<T> = ValueOf<{
|
|
43
|
+
[K in keyof T]: string extends K ? never : number extends K ? never : K;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* 排除对象中指定类型的项目
|
|
47
|
+
*/
|
|
48
|
+
export type ExcludePropertiesOfType<T, ExcludeValueT> = Pick<T, {
|
|
49
|
+
[K in keyof T]: T[K] extends ExcludeValueT ? never : K;
|
|
50
|
+
}[keyof T]>;
|
|
51
|
+
/**
|
|
52
|
+
* 排除对象的方法(仅保留属性)
|
|
53
|
+
*/
|
|
54
|
+
export type ExcluceMethods<T> = ExcludePropertiesOfType<T, Function>;
|
|
55
|
+
/**
|
|
56
|
+
* 所有“可能失败”的操作都可使用此类型作为返回值
|
|
57
|
+
*/
|
|
58
|
+
export interface Success<T = void> {
|
|
59
|
+
success: true;
|
|
60
|
+
data: T;
|
|
61
|
+
}
|
|
62
|
+
export interface Failed<ET = string> {
|
|
63
|
+
success: false;
|
|
64
|
+
error: ET;
|
|
65
|
+
}
|
|
66
|
+
export type MaySuccess<T = void, ET = string> = Success<T> | Failed<ET>;
|
|
67
|
+
declare function success(): Success;
|
|
68
|
+
declare function success<T>(data: T): Success<T>;
|
|
69
|
+
export { success };
|
|
70
|
+
export declare function failed<ET>(error: ET): Failed<ET>;
|
|
71
|
+
/**
|
|
72
|
+
* 若传入值为 success,格式化其 data;否则原样返回错误
|
|
73
|
+
* 支持传入会返回 MaySuccess 的 Promise
|
|
74
|
+
*/
|
|
75
|
+
declare function formatSuccess<T, ET, FT>(item: MaySuccess<T, ET>, formatter: (data: T) => FT): MaySuccess<FT, ET>;
|
|
76
|
+
declare function formatSuccess<T, ET, FT>(item: Promise<MaySuccess<T, ET>>, formatter: (data: T) => FT): Promise<MaySuccess<FT, ET>>;
|
|
77
|
+
export { formatSuccess };
|
|
78
|
+
/**
|
|
79
|
+
* 确认变量是否有值
|
|
80
|
+
* 注意:空字符串和数字 0 也会判定为没有值
|
|
81
|
+
*/
|
|
82
|
+
declare function truthy(value: string | number | boolean | null | undefined): value is string | number | true;
|
|
83
|
+
declare function truthy<T>(value: T | string | number | boolean | null | undefined): value is T | string | number | true;
|
|
84
|
+
export { truthy };
|
|
85
|
+
/**
|
|
86
|
+
* 定义 JSON 数据
|
|
87
|
+
*/
|
|
88
|
+
export type JSONData = number | boolean | string | null | JSONData[] | {
|
|
89
|
+
[key: string]: JSONData;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* 有些场景不适合用 undefined 判断一个变量是否被赋值(例如它允许被赋值成 undefined)
|
|
93
|
+
* 此时可以用这个 symbol 来做判断。
|
|
94
|
+
*
|
|
95
|
+
* if (value === noValue) console.log('未赋值')
|
|
96
|
+
* else console.log('已赋值', value)
|
|
97
|
+
*/
|
|
98
|
+
export declare const noValue: unique symbol;
|
package/lang/types.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 对类型系统的辅助、补充
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 解决 TypeScript 中,数组字面量 [1, 'a'] 无法自动识别为 tuple 的问题
|
|
6
|
+
*
|
|
7
|
+
* 使用方法:
|
|
8
|
+
* const t = tuple(1, 'a') // 类型会识别为 [1, 'a'] 而不是 (number | string)[]
|
|
9
|
+
*
|
|
10
|
+
* 待官方提供语言层面的支持
|
|
11
|
+
* https://github.com/microsoft/TypeScript/issues/27179
|
|
12
|
+
* https://github.com/microsoft/TypeScript/issues/16656
|
|
13
|
+
*/
|
|
14
|
+
export function tuple(...elements) {
|
|
15
|
+
return elements;
|
|
16
|
+
}
|
|
17
|
+
function success(data) {
|
|
18
|
+
return { success: true, data };
|
|
19
|
+
}
|
|
20
|
+
export { success };
|
|
21
|
+
export function failed(error) {
|
|
22
|
+
return { success: false, error };
|
|
23
|
+
}
|
|
24
|
+
function formatSuccess(item, formatter) {
|
|
25
|
+
if ('then' in item)
|
|
26
|
+
return item.then(finalItem => formatSuccess(finalItem, formatter));
|
|
27
|
+
return item.success ? { ...item, data: formatter(item.data) } : item;
|
|
28
|
+
}
|
|
29
|
+
export { formatSuccess };
|
|
30
|
+
function truthy(value) {
|
|
31
|
+
return value !== null && value !== undefined && value !== '' && value !== 0 && value !== false;
|
|
32
|
+
}
|
|
33
|
+
export { truthy };
|
|
34
|
+
/**
|
|
35
|
+
* 有些场景不适合用 undefined 判断一个变量是否被赋值(例如它允许被赋值成 undefined)
|
|
36
|
+
* 此时可以用这个 symbol 来做判断。
|
|
37
|
+
*
|
|
38
|
+
* if (value === noValue) console.log('未赋值')
|
|
39
|
+
* else console.log('已赋值', value)
|
|
40
|
+
*/
|
|
41
|
+
export const noValue = Symbol('no-value');
|
package/logging/adapt.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getLogger } from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 适配 debug package
|
|
4
|
+
*/
|
|
5
|
+
export function adaptDebugLib(debugLib, enable = '') {
|
|
6
|
+
// 不在 localStorage 里记录 debugLib enable 状态,
|
|
7
|
+
// 以解决 web worker 里读不到 localStorage 而无法启用 debugLib 日志的问题
|
|
8
|
+
const emulate = {
|
|
9
|
+
storage: {
|
|
10
|
+
data: {},
|
|
11
|
+
getItem(name) {
|
|
12
|
+
return emulate.storage.data[name];
|
|
13
|
+
},
|
|
14
|
+
setItem(name, value) {
|
|
15
|
+
emulate.storage.data[name] = value;
|
|
16
|
+
},
|
|
17
|
+
removeItem(name) {
|
|
18
|
+
delete emulate.storage.data[name];
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
save(namespaces) {
|
|
22
|
+
if (namespaces)
|
|
23
|
+
emulate.storage.setItem('debug', namespaces);
|
|
24
|
+
else
|
|
25
|
+
emulate.storage.removeItem('debug');
|
|
26
|
+
},
|
|
27
|
+
load() {
|
|
28
|
+
return emulate.storage.getItem('debug');
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
Object.assign(debugLib, emulate);
|
|
32
|
+
// 将 debugLib 日志转发给 logger
|
|
33
|
+
const logger = getLogger('3rd-library');
|
|
34
|
+
debugLib.log = logger.debug.bind(logger);
|
|
35
|
+
if (enable) {
|
|
36
|
+
debugLib.enable(enable);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志常用数据的格式化函数
|
|
3
|
+
*/
|
|
4
|
+
import { LogLevel } from './index.js';
|
|
5
|
+
const formatters = {
|
|
6
|
+
time(info) {
|
|
7
|
+
return info.time.format('HH:mm:ss.SSS');
|
|
8
|
+
},
|
|
9
|
+
datetime(info) {
|
|
10
|
+
return info.time.format('YY-MM-DD HH:mm:ss.SSS');
|
|
11
|
+
},
|
|
12
|
+
level(info) {
|
|
13
|
+
const map = {
|
|
14
|
+
[LogLevel.Debug]: 'debug',
|
|
15
|
+
[LogLevel.Info]: 'info',
|
|
16
|
+
[LogLevel.Warning]: 'warn',
|
|
17
|
+
[LogLevel.Error]: 'error',
|
|
18
|
+
};
|
|
19
|
+
return map[info.level];
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
export default formatters;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export { default as formatters } from './formatters.js';
|
|
2
|
+
export * from './adapt.js';
|
|
3
|
+
import { type Dayjs } from 'dayjs';
|
|
4
|
+
export declare enum LogLevel {
|
|
5
|
+
Debug = 1,
|
|
6
|
+
Info = 2,
|
|
7
|
+
Warning = 3,
|
|
8
|
+
Error = 4
|
|
9
|
+
}
|
|
10
|
+
export interface LogInfo {
|
|
11
|
+
logger: string;
|
|
12
|
+
level: LogLevel;
|
|
13
|
+
time: Dayjs;
|
|
14
|
+
args: unknown[];
|
|
15
|
+
}
|
|
16
|
+
export declare class LogHandler {
|
|
17
|
+
log(info: LogInfo): void;
|
|
18
|
+
}
|
|
19
|
+
export declare class Logger {
|
|
20
|
+
name: string;
|
|
21
|
+
base: Logger | null;
|
|
22
|
+
level: LogLevel;
|
|
23
|
+
handlers: Set<LogHandler>;
|
|
24
|
+
constructor(name?: string, base?: Logger | null);
|
|
25
|
+
setLevel(level: LogLevel): void;
|
|
26
|
+
addHandler(handler: LogHandler): void;
|
|
27
|
+
/**
|
|
28
|
+
* 创建一个以当前 logger 为 base 的 child logger
|
|
29
|
+
*/
|
|
30
|
+
getChild(name: string): this;
|
|
31
|
+
log(level: LogLevel, args: unknown[]): void;
|
|
32
|
+
protected logByInfo(info: LogInfo): void;
|
|
33
|
+
debug(...args: any[]): void;
|
|
34
|
+
info(...args: any[]): void;
|
|
35
|
+
warn(...args: any[]): void;
|
|
36
|
+
error(...args: any[]): void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 提供一套默认配置好的 logger
|
|
40
|
+
*/
|
|
41
|
+
declare const defaultLogger: Logger;
|
|
42
|
+
export { defaultLogger as logger };
|
|
43
|
+
export declare function getLogger(name: string): Logger;
|
package/logging/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export { default as formatters } from './formatters.js';
|
|
2
|
+
export * from './adapt.js';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import { initDayJs } from '../init-dayjs.js';
|
|
5
|
+
// 引入 logging 库会自动初始化 dayjs
|
|
6
|
+
initDayJs();
|
|
7
|
+
export var LogLevel;
|
|
8
|
+
(function (LogLevel) {
|
|
9
|
+
LogLevel[LogLevel["Debug"] = 1] = "Debug";
|
|
10
|
+
LogLevel[LogLevel["Info"] = 2] = "Info";
|
|
11
|
+
LogLevel[LogLevel["Warning"] = 3] = "Warning";
|
|
12
|
+
LogLevel[LogLevel["Error"] = 4] = "Error";
|
|
13
|
+
})(LogLevel || (LogLevel = {}));
|
|
14
|
+
export class LogHandler {
|
|
15
|
+
log(info) { } // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
16
|
+
}
|
|
17
|
+
export class Logger {
|
|
18
|
+
name;
|
|
19
|
+
base;
|
|
20
|
+
level = LogLevel.Info;
|
|
21
|
+
handlers = new Set();
|
|
22
|
+
constructor(name = '', base = null) {
|
|
23
|
+
this.name = name;
|
|
24
|
+
this.base = base;
|
|
25
|
+
}
|
|
26
|
+
setLevel(level) {
|
|
27
|
+
this.level = level;
|
|
28
|
+
}
|
|
29
|
+
addHandler(handler) {
|
|
30
|
+
this.handlers.add(handler);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 创建一个以当前 logger 为 base 的 child logger
|
|
34
|
+
*/
|
|
35
|
+
getChild(name) {
|
|
36
|
+
const fullname = this.name ? `${this.name}/${name}` : name;
|
|
37
|
+
// 这里加上 `as this` 才能让 TypeScript 判定,对继承了 Logger 的类调用此方法时,返回的是那个类而不是原始的 Logger 类的实例
|
|
38
|
+
return new this.constructor(fullname, this);
|
|
39
|
+
}
|
|
40
|
+
log(level, args) {
|
|
41
|
+
this.logByInfo({ logger: this.name, level, time: dayjs(), args });
|
|
42
|
+
}
|
|
43
|
+
logByInfo(info) {
|
|
44
|
+
if (this.base)
|
|
45
|
+
this.base.logByInfo(info);
|
|
46
|
+
if (this.level > info.level)
|
|
47
|
+
return;
|
|
48
|
+
for (const handler of this.handlers) {
|
|
49
|
+
handler.log(info);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
debug(...args) {
|
|
53
|
+
this.log(LogLevel.Debug, args);
|
|
54
|
+
}
|
|
55
|
+
info(...args) {
|
|
56
|
+
this.log(LogLevel.Info, args);
|
|
57
|
+
}
|
|
58
|
+
warn(...args) {
|
|
59
|
+
this.log(LogLevel.Warning, args);
|
|
60
|
+
}
|
|
61
|
+
error(...args) {
|
|
62
|
+
this.log(LogLevel.Error, args);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 提供一套默认配置好的 logger
|
|
67
|
+
*/
|
|
68
|
+
const defaultLogger = new Logger();
|
|
69
|
+
export { defaultLogger as logger };
|
|
70
|
+
export function getLogger(name) {
|
|
71
|
+
return defaultLogger.getChild(name);
|
|
72
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@anjianshi/utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Common JavaScript Utils",
|
|
5
|
+
"homepage": "https://github.com/anjianshi/js-utils",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/anjianshi/js-utils/issues",
|
|
8
|
+
"email": "anjianshi@gmail.com"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"author": "anjianshi <anjianshi@gmail.com>",
|
|
12
|
+
"repository": "github:anjianshi/js-utils",
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"registry": "https://registry.npmjs.org/",
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"watch": "npm run clear && tsc --watch",
|
|
20
|
+
"build": "npm run clear && tsc",
|
|
21
|
+
"clear": "rm -rf dist",
|
|
22
|
+
"prepublishOnly": "npm run build && cp -R dist/* ./",
|
|
23
|
+
"postpublish": "node ./build-cleanup.cjs",
|
|
24
|
+
"lint": "tsc --noEmit && eslint './src/**/*'"
|
|
25
|
+
},
|
|
26
|
+
"main": "index.js",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"dayjs": "^1.11.10",
|
|
29
|
+
"lodash": "^4.17.21"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@anjianshi/presets": "1.3.0",
|
|
33
|
+
"@types/debug": "^4.1.9",
|
|
34
|
+
"@types/lodash": "^4.14.199",
|
|
35
|
+
"@types/node": "^20.8.6",
|
|
36
|
+
"eslint": "^8.51.0",
|
|
37
|
+
"prettier-not-stubborn": "^3.0.3",
|
|
38
|
+
"typescript": "^5.2.2",
|
|
39
|
+
"vconsole": "^3.15.1"
|
|
40
|
+
},
|
|
41
|
+
"eslintIgnore": [],
|
|
42
|
+
"prettier": "@anjianshi/presets/prettierrc"
|
|
43
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自动计算最合适的 rem 大小,应用到页面样式上。
|
|
3
|
+
*
|
|
4
|
+
* rem 的意义是对于大屏幕,适当地同步放大界面元素,以提供更饱满的显示效果。
|
|
5
|
+
* 不过也不是所有地方都原封不动地大就好了,所以还要配合 media query 来实现最佳效果。
|
|
6
|
+
*/
|
|
7
|
+
export function autoRem() {
|
|
8
|
+
const resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 计算规则:
|
|
12
|
+
* - 微信标准的 375px 宽度下,1rem 为 50px(这个比例的来由是这样的:移动端通常像素比 >= 2,这个 375px 的实际渲染像素是 750px,也就是实际渲染 750px 的情况下,1rem 为 100px)。
|
|
13
|
+
* - 在平板等特大屏幕下,限制 rem 的最大值为 75px,再大就夸张了。
|
|
14
|
+
* - 竖屏时取宽度来适配 rem,横屏时取高度来适配,以保证屏幕朝向不同时,界面元素的放大比例是一致的(这应该更符合人的认知:对于一个同样大小的屏幕,无论什么朝向,上面的文字应该是一样大的)。
|
|
15
|
+
* 注意:此计算方式仅适合“不能任意调整浏览器大小的”移动设备,对于浏览器窗口可能任意尺寸、比例的桌面端,还是应该始终按照宽度来适配。
|
|
16
|
+
*
|
|
17
|
+
* 从 px 到 rem 的换算:
|
|
18
|
+
* 原 px 单位的值换算成 rem,只要除以 50 即可。例如 12px 变成 0.24rem。
|
|
19
|
+
*/
|
|
20
|
+
function updateRem() {
|
|
21
|
+
const { clientWidth, clientHeight } = document.documentElement
|
|
22
|
+
const refSize = Math.min(clientWidth, clientHeight)
|
|
23
|
+
const rem = Math.min((refSize / 750) * 100, 75)
|
|
24
|
+
document.documentElement.style.fontSize = `${rem}px`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
window.addEventListener(resizeEvt, updateRem, false)
|
|
28
|
+
document.addEventListener('DOMContentLoaded', updateRem, false)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 当前设备是否是全面屏
|
|
33
|
+
* 页面刚加载时可能取不到 offsetHeight,因此通过一个函数而不是常量来提供此值
|
|
34
|
+
*/
|
|
35
|
+
export function isFullScreen() {
|
|
36
|
+
const rate = document.documentElement.offsetHeight / document.documentElement.offsetWidth
|
|
37
|
+
const limit = window.screen.height === window.screen.availHeight ? 1.8 : 1.65 // 临界判断值
|
|
38
|
+
return rate > limit
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 当前是否是 iOS 设备
|
|
43
|
+
*/
|
|
44
|
+
export const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 当前是否是移动设备
|
|
48
|
+
*/
|
|
49
|
+
export const isMobile =
|
|
50
|
+
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
|
|
51
|
+
navigator.userAgent || navigator.vendor
|
|
52
|
+
) ||
|
|
53
|
+
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
|
|
54
|
+
(navigator.userAgent || navigator.vendor).substr(0, 4)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 当前是否是微信内部浏览器
|
|
59
|
+
*/
|
|
60
|
+
export const isWechat = /micromessenger/i.test(navigator.userAgent)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通过此模块注册全局变量以便于调试
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
declare global {
|
|
6
|
+
interface Window {
|
|
7
|
+
app: { [key: string]: unknown }
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// web-worker 环境下不存在 window 变量,无法执行注册
|
|
12
|
+
let hasWindow = false
|
|
13
|
+
try {
|
|
14
|
+
window.app = {}
|
|
15
|
+
hasWindow = true
|
|
16
|
+
} catch (e) {} // eslint-disable-line no-empty
|
|
17
|
+
|
|
18
|
+
export default function registerGlobal(key: string, content: unknown) {
|
|
19
|
+
if (hasWindow) window.app[key] = content
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 加载脚本文件
|
|
3
|
+
* 返回 Promise,成功则 resolve,失败 reject
|
|
4
|
+
*/
|
|
5
|
+
export default async function loadScript(url: string): Promise<void> {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const script = document.createElement('script')
|
|
8
|
+
script.src = url
|
|
9
|
+
script.onload = () => resolve()
|
|
10
|
+
script.onerror = err => reject(err)
|
|
11
|
+
window.document.head.appendChild(script)
|
|
12
|
+
})
|
|
13
|
+
}
|