// 通用工具函数 /** * 格式化时间 * @param {Date} date - 日期对象 * @returns {string} 格式化后的时间字符串 */ function formatTime(date) { const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() const hour = date.getHours() const minute = date.getMinutes() const second = date.getSeconds() return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') } /** * 格式化数字,补零 * @param {number} n - 数字 * @returns {string} 格式化后的数字字符串 */ function formatNumber(n) { n = n.toString() return n[1] ? n : '0' + n } /** * 格式化相对时间 * @param {number} timestamp - 时间戳 * @returns {string} 相对时间描述 */ function formatRelativeTime(timestamp) { const now = Date.now() const diff = now - timestamp const minute = 60 * 1000 const hour = 60 * minute const day = 24 * hour const week = 7 * day const month = 30 * 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 { return formatTime(new Date(timestamp)) } } /** * 防抖函数 * @param {Function} func - 要执行的函数 * @param {number} wait - 等待时间(毫秒) * @returns {Function} 防抖后的函数 */ function debounce(func, wait) { let timeout return function executedFunction(...args) { const later = () => { clearTimeout(timeout) func(...args) } clearTimeout(timeout) timeout = setTimeout(later, wait) } } /** * 节流函数 * @param {Function} func - 要执行的函数 * @param {number} limit - 限制时间(毫秒) * @returns {Function} 节流后的函数 */ function throttle(func, limit) { let inThrottle return function() { const args = arguments const context = this if (!inThrottle) { func.apply(context, args) inThrottle = true setTimeout(() => inThrottle = false, limit) } } } /** * 深拷贝对象 * @param {Object} obj - 要拷贝的对象 * @returns {Object} 拷贝后的对象 */ function deepClone(obj) { if (obj === null || typeof obj !== 'object') return obj if (obj instanceof Date) return new Date(obj.getTime()) if (obj instanceof Array) return obj.map(item => deepClone(item)) if (typeof obj === 'object') { const cloned = {} for (const key in obj) { if (obj.hasOwnProperty(key)) { cloned[key] = deepClone(obj[key]) } } return cloned } } /** * 判断对象是否为空 * @param {Object} obj - 要判断的对象 * @returns {boolean} 是否为空 */ function isEmpty(obj) { if (obj == null) return true if (obj.length > 0) return false if (obj.length === 0) return true if (typeof obj !== 'object') return true for (const key in obj) { if (obj.hasOwnProperty(key)) return false } return true } /** * 生成唯一ID * @returns {string} 唯一ID */ function generateUniqueId() { return Date.now().toString(36) + Math.random().toString(36).substr(2) } /** * 验证手机号 * @param {string} phone - 手机号 * @returns {boolean} 是否有效 */ function validatePhone(phone) { return /^1[3-9]\d{9}$/.test(phone) } /** * 验证邮箱 * @param {string} email - 邮箱 * @returns {boolean} 是否有效 */ function validateEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) } /** * 验证URL * @param {string} url - URL * @returns {boolean} 是否有效 */ function validateUrl(url) { try { new URL(url) return true } catch { return false } } /** * 获取文件扩展名 * @param {string} filename - 文件名 * @returns {string} 扩展名 */ function getFileExtension(filename) { return filename.split('.').pop().toLowerCase() } /** * 格式化文件大小 * @param {number} bytes - 字节数 * @returns {string} 格式化后的文件大小 */ function formatFileSize(bytes) { 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] } /** * 随机生成字符串 * @param {number} length - 字符串长度 * @returns {string} 随机字符串 */ function randomString(length) { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' let result = '' for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)) } return result } /** * 首字母大写 * @param {string} str - 字符串 * @returns {string} 首字母大写的字符串 */ function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1) } /** * 驼峰命名转下划线 * @param {string} str - 驼峰命名字符串 * @returns {string} 下划线命名字符串 */ function camelToSnake(str) { return str.replace(/([A-Z])/g, '_$1').toLowerCase() } /** * 下划线命名转驼峰 * @param {string} str - 下划线命名字符串 * @returns {string} 驼峰命名字符串 */ function snakeToCamel(str) { return str.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase()) } /** * 数组去重 * @param {Array} arr - 数组 * @returns {Array} 去重后的数组 */ function uniqueArray(arr) { return [...new Set(arr)] } /** * 数组分组 * @param {Array} arr - 数组 * @param {Function} keyFn - 分组键函数 * @returns {Object} 分组后的对象 */ function groupBy(arr, keyFn) { return arr.reduce((groups, item) => { const key = keyFn(item) if (!groups[key]) { groups[key] = [] } groups[key].push(item) return groups }, {}) } /** * 获取URL参数 * @param {string} url - URL字符串 * @returns {Object} URL参数对象 */ function getUrlParams(url) { const params = {} const urlObj = new URL(url) for (const [key, value] of urlObj.searchParams) { params[key] = value } return params } /** * 构建URL参数 * @param {Object} params - 参数对象 * @returns {string} URL参数字符串 */ function buildUrlParams(params) { return Object.keys(params) .filter(key => params[key] !== null && params[key] !== undefined) .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`) .join('&') } module.exports = { formatTime, formatNumber, formatRelativeTime, debounce, throttle, deepClone, isEmpty, generateUniqueId, validatePhone, validateEmail, validateUrl, getFileExtension, formatFileSize, randomString, capitalize, camelToSnake, snakeToCamel, uniqueArray, groupBy, getUrlParams, buildUrlParams }