/** * 通用工具函数 */ /** * 格式化时间 * @param {Date|string|number} timestamp 时间戳或日期字符串 * @param {string} format 格式,默认:yyyy-MM-dd HH:mm:ss * @returns {string} 格式化后的时间字符串 */ export const formatTime = (timestamp, format = 'yyyy-MM-dd HH:mm:ss') => { if (!timestamp) return '' const date = new Date(timestamp) if (isNaN(date.getTime())) return '' const map = { 'M+': date.getMonth() + 1, 'd+': date.getDate(), 'H+': date.getHours(), 'm+': date.getMinutes(), 's+': date.getSeconds(), 'q+': Math.floor((date.getMonth() + 3) / 3), 'S': date.getMilliseconds() } if (/(y+)/.test(format)) { format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) } for (const k in map) { if (new RegExp('(' + k + ')').test(format)) { format = format.replace(RegExp.$1, RegExp.$1.length === 1 ? map[k] : ('00' + map[k]).substr(('' + map[k]).length)) } } return format } /** * 格式化相对时间(如:3分钟前) * @param {Date|string|number} timestamp 时间戳或日期字符串 * @returns {string} 相对时间字符串 */ export const formatRelativeTime = (timestamp) => { if (!timestamp) return '' const date = new Date(timestamp) if (isNaN(date.getTime())) return '' const now = new Date() const diff = now.getTime() - date.getTime() const minute = 60 * 1000 const hour = 60 * minute const day = 24 * hour 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 < month) { return `${Math.floor(diff / day)}天前` } else if (diff < year) { return `${Math.floor(diff / month)}个月前` } else { return `${Math.floor(diff / year)}年前` } } /** * 防抖函数 * @param {Function} func 要执行的函数 * @param {number} wait 等待时间(毫秒) * @param {boolean} immediate 是否立即执行 * @returns {Function} 防抖后的函数 */ export const debounce = (func, wait = 300, immediate = false) => { let timeout return function executedFunction(...args) { const context = this const later = () => { timeout = null if (!immediate) func.apply(context, args) } const callNow = immediate && !timeout clearTimeout(timeout) timeout = setTimeout(later, wait) if (callNow) func.apply(context, args) } } /** * 节流函数 * @param {Function} func 要执行的函数 * @param {number} limit 限制时间(毫秒) * @returns {Function} 节流后的函数 */ export const throttle = (func, limit = 300) => { let inThrottle let lastFunc let lastRan return function executedFunction(...args) { const context = this if (!inThrottle) { func.apply(context, args) lastRan = Date.now() inThrottle = true } else { clearTimeout(lastFunc) lastFunc = setTimeout(() => { if (Date.now() - lastRan >= limit) { func.apply(context, args) lastRan = Date.now() } }, limit - (Date.now() - lastRan)) } } } /** * 深度克隆对象 * @param {any} obj 要克隆的对象 * @returns {any} 克隆后的对象 */ export const deepClone = (obj) => { if (obj === null || typeof obj !== 'object') return obj if (obj instanceof Date) return new Date(obj.getTime()) if (obj instanceof RegExp) return new RegExp(obj) if (Array.isArray(obj)) { const arrCopy = [] for (let i = 0; i < obj.length; i++) { arrCopy[i] = deepClone(obj[i]) } return arrCopy } if (typeof obj === 'object') { const objCopy = {} for (const key in obj) { if (obj.hasOwnProperty(key)) { objCopy[key] = deepClone(obj[key]) } } return objCopy } return obj } /** * 生成随机ID * @param {number} length ID长度 * @returns {string} 随机ID */ export const generateId = (length = 8) => { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' let result = '' for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)) } return result } /** * 检查是否为空(null、undefined、空字符串、空数组、空对象) * @param {any} value 要检查的值 * @returns {boolean} 是否为空 */ export const isEmpty = (value) => { if (value === null || value === undefined) return true if (typeof value === 'string' && value.trim() === '') return true if (Array.isArray(value) && value.length === 0) return true if (typeof value === 'object' && Object.keys(value).length === 0) return true return false } /** * 安全获取嵌套对象属性 * @param {Object} obj 对象 * @param {string} path 属性路径,如:'a.b.c' * @param {any} defaultValue 默认值 * @returns {any} 属性值或默认值 */ export const getSafe = (obj, path, defaultValue = null) => { if (!obj || typeof obj !== 'object') return defaultValue const keys = path.split('.') let result = obj for (const key of keys) { if (result === null || result === undefined || typeof result !== 'object') { return defaultValue } result = result[key] } return result === undefined ? defaultValue : result } /** * 格式化文件大小 * @param {number} bytes 字节数 * @returns {string} 格式化后的文件大小 */ export const 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 {string} text 要复制的文本 * @returns {Promise} 复制结果 */ export const copyToClipboard = (text) => { return new Promise((resolve, reject) => { if (uni.setClipboardData) { uni.setClipboardData({ data: text, success: () => resolve(true), fail: (err) => reject(err) }) } else { // 兼容H5环境 const textarea = document.createElement('textarea') textarea.value = text textarea.style.position = 'fixed' textarea.style.opacity = '0' document.body.appendChild(textarea) textarea.select() try { const successful = document.execCommand('copy') document.body.removeChild(textarea) successful ? resolve(true) : reject(new Error('复制失败')) } catch (err) { document.body.removeChild(textarea) reject(err) } } }) }