278 lines
7.5 KiB
TypeScript
278 lines
7.5 KiB
TypeScript
export default class Toolkit {
|
||
|
||
public static isFunction = (val: any) => typeof val === "function";
|
||
public static isArray = Array.isArray;
|
||
public static isString = (val: any) => typeof val === "string";
|
||
public static isSymbol = (val: any) => typeof val === "symbol";
|
||
public static isObject = (val: any) => val !== null && typeof val === "object";
|
||
|
||
public static guid() {
|
||
const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
|
||
return `${s4() + s4()}-${s4()}-${s4()}-${s4()}-${s4() + s4() + s4()}`;
|
||
}
|
||
|
||
public static className(...args: any[]) {
|
||
const classes = [];
|
||
for (let i = 0; i < args.length; i++) {
|
||
const value = args[i];
|
||
if (!value) continue;
|
||
if (this.isString(value)) {
|
||
classes.push(value);
|
||
} else if (this.isArray(value)) {
|
||
for (let i = 0; i < value.length; i++) {
|
||
const inner: any = this.className(value[i]);
|
||
if (inner) {
|
||
classes.push(inner);
|
||
}
|
||
}
|
||
} else if (this.isObject(value)) {
|
||
for (const name in value) {
|
||
if (value[name]) {
|
||
classes.push(name);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return classes.join(" ");
|
||
}
|
||
|
||
public static isEmpty(obj: any): boolean {
|
||
if (this.isString(obj)) {
|
||
return (obj as string).trim().length === 0;
|
||
}
|
||
if (this.isArray(obj)) {
|
||
return (obj as []).length === 0;
|
||
}
|
||
if (this.isFunction(obj)) {
|
||
return this.isEmpty(obj());
|
||
}
|
||
if (this.isObject(obj)) {
|
||
return obj === undefined || obj === null;
|
||
}
|
||
return obj === undefined || obj === null;
|
||
}
|
||
|
||
public static isNotEmpty(obj: any): boolean {
|
||
return !this.isEmpty(obj);
|
||
}
|
||
|
||
/**
|
||
* ### 延时执行
|
||
*
|
||
* ```js
|
||
* await sleep(1E3)
|
||
* ```
|
||
*
|
||
* @param ms 延时毫秒
|
||
*/
|
||
public static async sleep(ms: number): Promise<unknown> {
|
||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||
}
|
||
|
||
/**
|
||
* 转为数字
|
||
*
|
||
* @param string 字符串
|
||
* @param fallback 如果失败,返回该值
|
||
* @returns 转换后或转换失败数据
|
||
*/
|
||
public static toNumber(string: string, fallback?: number): number {
|
||
if (!string) return fallback as number;
|
||
const number = Number(string);
|
||
return isFinite(number) ? number : fallback as number;
|
||
}
|
||
|
||
/**
|
||
* 异步执行
|
||
*
|
||
* @param event 函数
|
||
*/
|
||
public static async(event: (...args: any[]) => any) {
|
||
setTimeout(event, 0);
|
||
}
|
||
|
||
/**
|
||
* 生成随机数
|
||
*
|
||
* @param min 最小值
|
||
* @param max 最大值
|
||
*/
|
||
public static random(min = 0, max = 100): number {
|
||
return Math.floor(Math.random() * (max + 1 - min)) + min;
|
||
}
|
||
|
||
/**
|
||
* 深克隆对象
|
||
*
|
||
* @param origin 源对象
|
||
* @param target 递归对象
|
||
* @returns 克隆对象
|
||
*/
|
||
public static deepClone(origin: any, target = {} as any) {
|
||
const toString = Object.prototype.toString;
|
||
const arrType = "[object Array]";
|
||
for (const key in origin) {
|
||
if (Object.prototype.hasOwnProperty.call(origin, key)) {
|
||
if (typeof origin[key] === "object" && origin[key] !== null) {
|
||
target[key] = toString.call(origin[key]) === arrType ? [] : {};
|
||
this.deepClone(origin[key], target[key]);
|
||
} else {
|
||
target[key] = origin[key];
|
||
}
|
||
}
|
||
}
|
||
return target;
|
||
}
|
||
|
||
public static keyValueString(obj: object, assign: string, split: string): string {
|
||
let result = "";
|
||
Object.entries(obj).forEach(([k, v]) => result += k + assign + v + split);
|
||
return result.substring(0, result.length - split.length);
|
||
}
|
||
|
||
public static toURLArgs(obj?: { [key: string]: any }): string {
|
||
if (!obj) {
|
||
return "";
|
||
}
|
||
const args: string[] = [];
|
||
for (const key in obj) {
|
||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||
const value = obj[key];
|
||
if (Array.isArray(value)) {
|
||
value.forEach((item) => {
|
||
args.push(`${encodeURIComponent(key)}=${encodeURIComponent(item)}`);
|
||
});
|
||
} else {
|
||
args.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.toString())}`);
|
||
}
|
||
}
|
||
}
|
||
return args.join("&");
|
||
}
|
||
|
||
public static toObject(map: Map<any, any>): object {
|
||
return Array.from(map.entries()).reduce((acc, [key, value]) => {
|
||
acc[key] = value ?? null;
|
||
return acc;
|
||
}, {} as { [key: string]: string | undefined });
|
||
}
|
||
|
||
public static uuid(): string {
|
||
const char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||
const length = [8, 4, 4, 4, 12];
|
||
let result = "";
|
||
for (let i = 0; i < length.length; i++) {
|
||
for (let j = 0; j < length[i]; j++) {
|
||
result += char[this.random(0, char.length - 1)];
|
||
}
|
||
result += "-";
|
||
}
|
||
return result.substring(0, result.length - 1);
|
||
}
|
||
|
||
public static keyFromValue(e: any, value: any): string {
|
||
return Object.keys(e)[Object.values(e).indexOf(value)];
|
||
}
|
||
|
||
// 防抖
|
||
// eslint-disable-next-line
|
||
public static debounce<T extends (...args: any[]) => any>(callback: T, defaultImmediate = true, delay = 600): T & {
|
||
cancel(): void
|
||
} {
|
||
let timerId: ReturnType<typeof setTimeout> | null = null; // 存储定时器
|
||
let immediate = defaultImmediate;
|
||
// 定义一个 cancel 办法,用于勾销防抖
|
||
const cancel = (): void => {
|
||
if (timerId) {
|
||
clearTimeout(timerId);
|
||
timerId = null;
|
||
}
|
||
};
|
||
|
||
const debounced = function (this: ThisParameterType<T>, ...args: Parameters<T>): void {
|
||
const context = this;
|
||
if (timerId) {
|
||
cancel();
|
||
}
|
||
if (immediate) {
|
||
callback.apply(context, args);
|
||
immediate = false;
|
||
timerId = setTimeout(() => {
|
||
immediate = defaultImmediate;
|
||
}, delay);
|
||
} else {
|
||
// 设置定时器,在延迟时间后执行指标函数
|
||
timerId = setTimeout(() => {
|
||
callback.apply(context, args);
|
||
immediate = defaultImmediate;
|
||
}, delay);
|
||
}
|
||
};
|
||
// 将 cancel 方法附加到 debounced 函数上
|
||
(debounced as any).cancel = cancel;
|
||
return debounced as T & { cancel(): void };
|
||
}
|
||
|
||
public static format(template: string, variables: { [key: string]: any }): string {
|
||
return template.replace(/\$\{(\w+)}/g, (_, key) => variables[key]);
|
||
}
|
||
|
||
public static symmetricDiff(arr1: any[], arr2: any[]) {
|
||
const set1 = new Set(arr1);
|
||
const set2 = new Set(arr2);
|
||
|
||
return [
|
||
...arr1.filter(item => !set2.has(item)),
|
||
...arr2.filter(item => !set1.has(item))
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 将数组元素分配到指定数量的列中
|
||
*
|
||
* - 如果提供 getHeight 函数:使用贪心算法,每次将元素放到当前高度最小的列(适合瀑布流布局)
|
||
* - 如果不提供 getHeight:使用轮询方式分配(第 1、4、7... 个到列 1,第 2、5、8... 个到列 2)
|
||
*
|
||
* @param items 原始数组
|
||
* @param columnCount 列数,默认 3
|
||
* @param getHeight 可选的高度获取函数,用于计算每个元素的高度
|
||
* @returns 分列后的二维数组
|
||
*
|
||
* @example
|
||
* // 简单轮询分配
|
||
* splitItemsIntoColumns([1, 2, 3, 4, 5, 6], 3)
|
||
* // => [[1, 4], [2, 5], [3, 6]]
|
||
*
|
||
* @example
|
||
* // 基于高度的贪心分配
|
||
* const items = [{ h: 100 }, { h: 200 }, { h: 150 }]
|
||
* splitItemsIntoColumns(items, 2, item => item.h)
|
||
* // => [[ { h: 100 }, { h: 150 }], [{ h: 200 }]]
|
||
*/
|
||
public static splitItemsIntoColumns<T>(
|
||
items: T[],
|
||
columnCount = 3,
|
||
getHeight?: (item: T) => number
|
||
): T[][] {
|
||
const columns: T[][] = Array.from({ length: columnCount }, () => []);
|
||
|
||
if (!getHeight) {
|
||
// 降级为轮询分配
|
||
items.forEach((item, index) => {
|
||
columns[index % columnCount].push(item);
|
||
});
|
||
} else {
|
||
// 使用贪心算法:总是放到当前高度最小的列
|
||
const columnHeights = Array(columnCount).fill(0);
|
||
items.forEach((item) => {
|
||
// 找到高度最小的列
|
||
const minHeightIndex = columnHeights.indexOf(Math.min(...columnHeights));
|
||
columns[minHeightIndex].push(item);
|
||
columnHeights[minHeightIndex] += getHeight(item);
|
||
});
|
||
}
|
||
|
||
return columns;
|
||
}
|
||
}
|