rwadurian/frontend/admin-web/src/utils/helpers.ts

212 lines
4.7 KiB
TypeScript

import { clsx, type ClassValue } from 'clsx';
/**
* 合并 className
*/
export function cn(...inputs: ClassValue[]): string {
return clsx(inputs);
}
/**
* 延迟函数
*/
export function delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* 防抖函数
*/
export function debounce<T extends (...args: Parameters<T>) => ReturnType<T>>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeoutId: ReturnType<typeof setTimeout> | null = null;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
/**
* 节流函数
*/
export function throttle<T extends (...args: Parameters<T>) => ReturnType<T>>(
func: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle: boolean;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
/**
* 深拷贝
*/
export function deepClone<T>(obj: T): T {
if (obj === null || typeof obj !== 'object') {
return obj;
}
return JSON.parse(JSON.stringify(obj));
}
/**
* 生成唯一 ID
*/
export function generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
}
/**
* 获取对象的嵌套值
*/
export function getNestedValue<T>(obj: Record<string, unknown>, path: string): T | undefined {
const keys = path.split('.');
let result: unknown = obj;
for (const key of keys) {
if (result === null || result === undefined) {
return undefined;
}
result = (result as Record<string, unknown>)[key];
}
return result as T;
}
/**
* 将对象转为查询字符串
*/
export function objectToQueryString(obj: Record<string, unknown>): string {
const params = new URLSearchParams();
Object.entries(obj).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
params.append(key, String(value));
}
});
return params.toString();
}
/**
* 从查询字符串解析对象
*/
export function queryStringToObject(query: string): Record<string, string> {
const params = new URLSearchParams(query);
const result: Record<string, string> = {};
params.forEach((value, key) => {
result[key] = value;
});
return result;
}
/**
* 数组去重
*/
export function uniqueArray<T>(arr: T[], key?: keyof T): T[] {
if (key) {
const seen = new Set();
return arr.filter((item) => {
const k = item[key];
if (seen.has(k)) {
return false;
}
seen.add(k);
return true;
});
}
return [...new Set(arr)];
}
/**
* 数组分组
*/
export function groupBy<T>(arr: T[], key: keyof T): Record<string, T[]> {
return arr.reduce(
(groups, item) => {
const groupKey = String(item[key]);
if (!groups[groupKey]) {
groups[groupKey] = [];
}
groups[groupKey].push(item);
return groups;
},
{} as Record<string, T[]>
);
}
/**
* 复制文本到剪贴板
*/
export async function copyToClipboard(text: string): Promise<boolean> {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {
// 降级方案
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
const success = document.execCommand('copy');
document.body.removeChild(textarea);
return success;
}
}
/**
* 下载 Blob 文件
*/
export function downloadBlob(blob: Blob, filename: string): void {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
/**
* 检查是否为移动设备
*/
export function isMobile(): boolean {
if (typeof window === 'undefined') return false;
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
/**
* 获取颜色列表(用于图表)
*/
export function getChartColors(): string[] {
return [
'#1565C0',
'#4CAF50',
'#F5A623',
'#E53935',
'#9C27B0',
'#00BCD4',
'#FF9800',
'#795548',
'#607D8B',
'#3F51B5',
];
}