import { DATE_FORMATS } from '@/constants'; // 日期格式化 export const formatDate = ( date: string | Date, format: string = DATE_FORMATS.DISPLAY_DATETIME ): string => { if (!date) return ''; const d = new Date(date); if (isNaN(d.getTime())) return ''; const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); const hours = String(d.getHours()).padStart(2, '0'); const minutes = String(d.getMinutes()).padStart(2, '0'); const seconds = String(d.getSeconds()).padStart(2, '0'); return format .replace('YYYY', String(year)) .replace('MM', month) .replace('DD', day) .replace('HH', hours) .replace('mm', minutes) .replace('ss', seconds) .replace('年', '年') .replace('月', '月') .replace('日', '日'); }; // 日期时间格式化(formatDate 的别名,用于向后兼容) export const formatDateTime = formatDate; // 相对时间格式化 export const formatRelativeTime = (date: string | Date): string => { if (!date) return ''; const d = new Date(date); if (isNaN(d.getTime())) return ''; const now = new Date(); const diff = now.getTime() - d.getTime(); const seconds = Math.floor(diff / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (seconds < 60) return '刚刚'; if (minutes < 60) return `${minutes}分钟前`; if (hours < 24) return `${hours}小时前`; if (days < 7) return `${days}天前`; return formatDate(date, DATE_FORMATS.DISPLAY_DATE); }; // 货币格式化 export const formatCurrency = ( amount: number, currency: string = 'USD', locale: string = 'zh-CN' ): string => { if (typeof amount !== 'number' || isNaN(amount)) return '¥0.00'; const formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: currency, minimumFractionDigits: 2, maximumFractionDigits: 2 }); return formatter.format(amount); }; // 数字格式化 export const formatNumber = ( num: number, locale: string = 'zh-CN' ): string => { if (typeof num !== 'number' || isNaN(num)) return '0'; return new Intl.NumberFormat(locale).format(num); }; // 百分比格式化 export const formatPercentage = ( value: number, decimals: number = 1 ): string => { if (typeof value !== 'number' || isNaN(value)) return '0%'; return `${value.toFixed(decimals)}%`; }; // 文件大小格式化 export const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; }; // 时长格式化(秒转换为时分秒) export const formatDuration = (seconds: number): string => { if (typeof seconds !== 'number' || isNaN(seconds) || seconds < 0) return '0秒'; const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const remainingSeconds = seconds % 60; if (hours > 0) { return `${hours}小时${minutes}分钟${remainingSeconds}秒`; } else if (minutes > 0) { return `${minutes}分钟${remainingSeconds}秒`; } else { return `${remainingSeconds}秒`; } }; // 字符串截断 export const truncateText = ( text: string, maxLength: number = 50, suffix: string = '...' ): string => { if (!text || typeof text !== 'string') return ''; if (text.length <= maxLength) return text; return text.substring(0, maxLength - suffix.length) + suffix; }; // 邮箱验证 export const isValidEmail = (email: string): boolean => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; // 手机号验证(中国大陆) export const isValidPhone = (phone: string): boolean => { const phoneRegex = /^1[3-9]\d{9}$/; return phoneRegex.test(phone); }; // URL验证 export const isValidUrl = (url: string): boolean => { try { new URL(url); return true; } catch { return false; } }; // 密码强度验证 export const getPasswordStrength = (password: string): { score: number; level: 'weak' | 'medium' | 'strong'; feedback: string[]; } => { const feedback: string[] = []; let score = 0; if (password.length >= 8) { score += 1; } else { feedback.push('密码长度至少8位'); } if (/[a-z]/.test(password)) { score += 1; } else { feedback.push('包含小写字母'); } if (/[A-Z]/.test(password)) { score += 1; } else { feedback.push('包含大写字母'); } if (/\d/.test(password)) { score += 1; } else { feedback.push('包含数字'); } if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) { score += 1; } else { feedback.push('包含特殊字符'); } let level: 'weak' | 'medium' | 'strong'; if (score <= 2) { level = 'weak'; } else if (score <= 3) { level = 'medium'; } else { level = 'strong'; } return { score, level, feedback }; }; // 深拷贝 export const deepClone = (obj: T): T => { if (obj === null || typeof obj !== 'object') return obj; if (obj instanceof Date) return new Date(obj.getTime()) as unknown as T; if (obj instanceof Array) return obj.map(item => deepClone(item)) as unknown as T; if (typeof obj === 'object') { const clonedObj = {} as { [key: string]: any }; for (const key in obj) { if (obj.hasOwnProperty(key)) { clonedObj[key] = deepClone((obj as { [key: string]: any })[key]); } } return clonedObj as T; } return obj; }; // 防抖函数 export const debounce = any>( func: T, wait: number ): ((...args: Parameters) => void) => { let timeout: NodeJS.Timeout; return (...args: Parameters) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); }; }; // 节流函数 export const throttle = any>( func: T, wait: number ): ((...args: Parameters) => void) => { let inThrottle = false; return (...args: Parameters) => { if (!inThrottle) { func(...args); inThrottle = true; setTimeout(() => inThrottle = false, wait); } }; }; // 生成随机字符串 export const generateRandomString = (length: number = 8): string => { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; }; // 生成UUID export const generateUUID = (): string => { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }; // 数组去重 export const uniqueArray = (array: T[], key?: keyof T): T[] => { if (!key) { return [...new Set(array)]; } const seen = new Set(); return array.filter(item => { const keyValue = item[key]; if (seen.has(keyValue)) { return false; } seen.add(keyValue); return true; }); }; // 数组分组 export const groupBy = ( array: T[], key: keyof T | ((item: T) => string | number) ): Record => { return array.reduce((groups, item) => { const groupKey = typeof key === 'function' ? key(item) : item[key]; const keyStr = String(groupKey); if (!groups[keyStr]) { groups[keyStr] = []; } groups[keyStr].push(item); return groups; }, {} as Record); }; // 对象转查询字符串 export const objectToQueryString = (obj: Record): string => { const params = new URLSearchParams(); Object.entries(obj).forEach(([key, value]) => { if (value !== null && value !== undefined && value !== '') { if (Array.isArray(value)) { value.forEach(item => params.append(key, String(item))); } else { params.append(key, String(value)); } } }); return params.toString(); }; // 查询字符串转对象 export const queryStringToObject = (queryString: string): Record => { const params = new URLSearchParams(queryString); const result: Record = {}; for (const [key, value] of params.entries()) { if (result[key]) { if (Array.isArray(result[key])) { result[key].push(value); } else { result[key] = [result[key], value]; } } else { result[key] = value; } } return result; }; // 本地存储封装 export const storage = { get: (key: string, defaultValue?: T): T | null => { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : defaultValue || null; } catch { return defaultValue || null; } }, set: (key: string, value: any): void => { try { localStorage.setItem(key, JSON.stringify(value)); } catch (error) { console.error('Storage set error:', error); } }, remove: (key: string): void => { try { localStorage.removeItem(key); } catch (error) { console.error('Storage remove error:', error); } }, clear: (): void => { try { localStorage.clear(); } catch (error) { console.error('Storage clear error:', error); } } }; // 颜色工具 export const colorUtils = { // 十六进制转RGB hexToRgb: (hex: string): { r: number; g: number; b: number } | null => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; }, // RGB转十六进制 rgbToHex: (r: number, g: number, b: number): string => { return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; }, // 获取对比色 getContrastColor: (hex: string): string => { const rgb = colorUtils.hexToRgb(hex); if (!rgb) return '#000000'; const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; return brightness > 128 ? '#000000' : '#ffffff'; } }; // 设备检测 export const deviceUtils = { isMobile: (): boolean => { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); }, isTablet: (): boolean => { return /iPad|Android/i.test(navigator.userAgent) && !deviceUtils.isMobile(); }, isDesktop: (): boolean => { return !deviceUtils.isMobile() && !deviceUtils.isTablet(); }, getScreenSize: (): 'xs' | 'sm' | 'md' | 'lg' | 'xl' => { const width = window.innerWidth; if (width < 576) return 'xs'; if (width < 768) return 'sm'; if (width < 992) return 'md'; if (width < 1200) return 'lg'; return 'xl'; } }; // 错误处理 export const handleError = (error: any): string => { if (typeof error === 'string') return error; if (error?.message) return error.message; if (error?.response?.data?.message) return error.response.data.message; return '操作失败,请稍后重试'; }; // 成功提示 export const handleSuccess = (message?: string): string => { return message || '操作成功'; };