/** * 工具函数示例代码 * 展示项目中工具函数的编写规范和最佳实践 */ /** * 格式化日期 * @param date 日期(Date | string | number) * @param format 格式化模式(默认 'YYYY-MM-DD HH:mm:ss') * @returns 格式化后的日期字符串 */ export function formatDate(date: Date | string | number, format = 'YYYY-MM-DD HH:mm:ss'): string { // 如果传入的是字符串或数字,先转换为 Date 对象 const dateObj = typeof date === 'string' || typeof date === 'number' ? new Date(date) : date; // 验证日期有效性 if (isNaN(dateObj.getTime())) { throw new Error('无效的日期格式'); } const year = dateObj.getFullYear(); const month = String(dateObj.getMonth() + 1).padStart(2, '0'); const day = String(dateObj.getDate()).padStart(2, '0'); const hours = String(dateObj.getHours()).padStart(2, '0'); const minutes = String(dateObj.getMinutes()).padStart(2, '0'); const seconds = String(dateObj.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); } /** * 相对时间格式化 * @param date 日期 * @returns 相对时间字符串(如:2分钟前、3小时前) */ export function formatRelativeTime(date: Date | string | number): string { const now = new Date(); const targetDate = typeof date === 'string' || typeof date === 'number' ? new Date(date) : date; const diff = now.getTime() - targetDate.getTime(); const minute = 60 * 1000; const hour = 60 * minute; const day = 24 * hour; const week = 7 * day; const month = 30 * day; const year = 365 * day; if (diff < minute) { return '刚刚'; } else if (diff < hour) { return `${Math.floor(diff / minute)}分钟前`; } else if (diff < day) { return `${Math.floor(diff / hour)}小时前`; } else if (diff < week) { return `${Math.floor(diff / day)}天前`; } else if (diff < month) { return `${Math.floor(diff / week)}周前`; } else if (diff < year) { return `${Math.floor(diff / month)}月前`; } else { return `${Math.floor(diff / year)}年前`; } } /** * 防抖函数 * @param func 要防抖的函数 * @param delay 延迟时间(毫秒) * @returns 防抖后的函数 */ export function debounce any>( func: T, delay: number ): (...args: Parameters) => void { let timerId: NodeJS.Timeout | null = null; return function (this: any, ...args: Parameters) { // 清除之前的定时器 if (timerId) { clearTimeout(timerId); } // 设置新的定时器 timerId = setTimeout(() => { func.apply(this, args); }, delay); }; } /** * 节流函数 * @param func 要节流的函数 * @param delay 间隔时间(毫秒) * @returns 节流后的函数 */ export function throttle any>( func: T, delay: number ): (...args: Parameters) => void { let lastExecTime = 0; return function (this: any, ...args: Parameters) { const currentTime = Date.now(); // 如果间隔时间未到,不执行函数 if (currentTime - lastExecTime < delay) { return; } // 更新最后执行时间 lastExecTime = currentTime; // 执行函数 func.apply(this, args); }; } /** * 深拷贝 * @param obj 要拷贝的对象 * @returns 深拷贝后的对象 */ export function deepClone(obj: T): T { // 处理基本类型和 null if (obj === null || typeof obj !== 'object') { return obj; } // 处理 Date 类型 if (obj instanceof Date) { return new Date(obj.getTime()) as unknown as T; } // 处理 Array 类型 if (Array.isArray(obj)) { return obj.map(item => deepClone(item)) as unknown as T; } // 处理对象类型 const clonedObj = {} as T; for (const key in obj) { if (obj.hasOwnProperty(key)) { clonedObj[key] = deepClone(obj[key]); } } return clonedObj; } /** * 生成唯一ID * @param prefix 前缀(可选) * @returns 唯一ID字符串 */ export function generateId(prefix = 'id'): string { const timestamp = Date.now().toString(36); const randomStr = Math.random().toString(36).substring(2, 8); return `${prefix}_${timestamp}_${randomStr}`; } /** * 数字格式化(添加千位分隔符) * @param num 数字 * @param precision 小数位数(默认2) * @returns 格式化后的数字字符串 */ export function formatNumber(num: number, precision = 2): string { return Number(num).toLocaleString('zh-CN', { minimumFractionDigits: precision, maximumFractionDigits: precision, }); } /** * 价格格式化 * @param price 价格 * @returns 格式化后的价格字符串(如:¥100.00) */ export function formatPrice(price: number): string { return `¥${formatNumber(price, 2)}`; } /** * 验证手机号 * @param phone 手机号 * @returns 是否为有效的手机号 */ export function validatePhone(phone: string): boolean { const phoneRegex = /^1[3-9]\d{9}$/; return phoneRegex.test(phone); } /** * 验证邮箱 * @param email 邮箱 * @returns 是否为有效的邮箱 */ export function validateEmail(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * 验证身份证号 * @param idCard 身份证号 * @returns 是否为有效的身份证号 */ export function validateIdCard(idCard: string): boolean { const idCardRegex = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/; return idCardRegex.test(idCard); } /** * 获取图片尺寸(异步) * @param src 图片地址 * @returns Promise<{width: number, height: number}> */ export function getImageSize(src: string): Promise<{ width: number; height: number }> { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { resolve({ width: img.width, height: img.height, }); }; img.onerror = reject; img.src = src; }); } /** * 下载文件 * @param url 文件地址 * @param filename 文件名(可选) */ export function downloadFile(url: string, filename?: string): void { const link = document.createElement('a'); link.href = url; if (filename) { link.download = filename; } document.body.appendChild(link); link.click(); document.body.removeChild(link); } /** * 复制到剪贴板 * @param text 要复制的文本 */ export function copyToClipboard(text: string): Promise { if (navigator.clipboard) { return navigator.clipboard.writeText(text); } // 兼容旧版浏览器 return new Promise((resolve, reject) => { const textarea = document.createElement('textarea'); textarea.value = text; document.body.appendChild(textarea); textarea.select(); try { document.execCommand('copy'); resolve(); } catch (err) { reject(err); } finally { document.body.removeChild(textarea); } }); } /** * 获取URL参数 * @param url URL地址(可选,默认当前页面的URL) * @returns 参数对象 */ export function getUrlParams(url?: string): Record { const urlObj = new URL(url || window.location.href); const params: Record = {}; urlObj.searchParams.forEach((value, key) => { params[key] = value; }); return params; } /** * 设置URL参数 * @param params 参数对象 * @param replace 是否替换当前历史记录(默认false) */ export function setUrlParams(params: Record, replace = false): void { const url = new URL(window.location.href); Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { url.searchParams.set(key, String(value)); } else { url.searchParams.delete(key); } }); if (replace) { window.history.replaceState({}, '', url.toString()); } else { window.history.pushState({}, '', url.toString()); } } /** * 数组分组 * @param array 数组 * @param size 每组大小 * @returns 分组后的二维数组 */ export function chunk(array: T[], size: number): T[][] { const chunks: T[][] = []; for (let i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); } return chunks; } /** * 数组去重 * @param array 数组 * @param key 去重的键(可选) * @returns 去重后的数组 */ export function unique(array: T[], key?: keyof T): T[] { if (!key) { return Array.from(new Set(array)); } const seen = new Set(); return array.filter(item => { const value = item[key]; if (seen.has(value)) { return false; } seen.add(value); return true; }); } /** * 数组排序 * @param array 数组 * @param key 排序键 * @param order 排序方向('asc' | 'desc',默认'asc') * @returns 排序后的数组 */ export function sortBy(array: T[], key: keyof T, order: 'asc' | 'desc' = 'asc'): T[] { return [...array].sort((a, b) => { const aVal = a[key]; const bVal = b[key]; if (aVal < bVal) { return order === 'asc' ? -1 : 1; } if (aVal > bVal) { return order === 'asc' ? 1 : -1; } return 0; }); } /** * 树形结构转换 * @param data 源数据 * @param options 配置 * @returns 树形结构数据 */ export function arrayToTree( data: T[], options: { id?: keyof T; parentId?: keyof T; children?: string; } ): T[] { const { id = 'id', parentId = 'parentId', children = 'children' } = options; const tree: T[] = []; const map = new Map(); // 建立索引 data.forEach(item => { map.set(String(item[id]), { ...item, [children]: [] }); }); // 构建树 data.forEach(item => { const node = map.get(String(item[id]))!; const parentIdValue = item[parentId]; if (parentIdValue && map.has(String(parentIdValue))) { map.get(String(parentIdValue))![children].push(node); } else { tree.push(node as unknown as T); } }); return tree; } /** * 延迟执行(Promise) * @param ms 延迟时间(毫秒) * @returns Promise */ export function delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } /** * 重试函数 * @param fn 要执行的函数 * @param retries 重试次数(默认3) * @param delay 重试间隔(默认1000ms) * @returns Promise */ export async function retry( fn: () => Promise, retries = 3, delayMs = 1000 ): Promise { let lastError: Error; for (let i = 0; i <= retries; i++) { try { return await fn(); } catch (err) { lastError = err as Error; if (i === retries) { throw lastError; } await delay(delayMs); } } throw lastError!; } /** * 获取随机颜色 * @returns 十六进制颜色值 */ export function getRandomColor(): string { return `#${Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0')}`; } /** * 计算两点距离 * @param point1 点1 {x, y} * @param point2 点2 {x, y} * @returns 距离 */ export function getDistance( point1: { x: number; y: number }, point2: { x: number; y: number } ): number { const dx = point2.x - point1.x; const dy = point2.y - point1.y; return Math.sqrt(dx * dx + dy * dy); }