// 工具函数库 /** * 格式化日期 * @param {Date|string|number} date 日期 * @param {string} format 格式化字符串 * @returns {string} 格式化后的日期字符串 */ function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') { 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', year) .replace('MM', month) .replace('DD', day) .replace('HH', hours) .replace('mm', minutes) .replace('ss', seconds) } /** * 格式化相对时间 * @param {Date|string|number} date 日期 * @returns {string} 相对时间字符串 */ function formatRelativeTime(date) { if (!date) return '' const d = new Date(date) if (isNaN(d.getTime())) return '' const now = new Date() const diff = now.getTime() - d.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 {function} func 要防抖的函数 * @param {number} wait 等待时间 * @returns {function} 防抖后的函数 */ function debounce(func, wait = 300) { let timeout return function executedFunction(...args) { const later = () => { clearTimeout(timeout) func.apply(this, args) } clearTimeout(timeout) timeout = setTimeout(later, wait) } } /** * 节流函数 * @param {function} func 要节流的函数 * @param {number} limit 时间间隔 * @returns {function} 节流后的函数 */ function throttle(func, limit = 300) { let inThrottle return function executedFunction(...args) { if (!inThrottle) { func.apply(this, args) inThrottle = true setTimeout(() => inThrottle = false, limit) } } } /** * 深拷贝 * @param {any} obj 要拷贝的对象 * @returns {any} 拷贝后的对象 */ 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 clonedObj = {} for (const key in obj) { if (obj.hasOwnProperty(key)) { clonedObj[key] = deepClone(obj[key]) } } return clonedObj } } /** * 生成随机字符串 * @param {number} length 字符串长度 * @returns {string} 随机字符串 */ function randomString(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 } /** * 验证手机号 * @param {string} phone 手机号 * @returns {boolean} 是否有效 */ function validatePhone(phone) { const phoneRegex = /^1[3-9]\d{9}$/ return phoneRegex.test(phone) } /** * 验证邮箱 * @param {string} email 邮箱 * @returns {boolean} 是否有效 */ function validateEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ return emailRegex.test(email) } /** * 验证身份证号 * @param {string} idCard 身份证号 * @returns {boolean} 是否有效 */ function validateIdCard(idCard) { const idCardRegex = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/ return idCardRegex.test(idCard) } /** * 格式化文件大小 * @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 {string} filename 文件名 * @returns {string} 扩展名 */ function getFileExtension(filename) { return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2) } /** * 判断是否为图片文件 * @param {string} filename 文件名 * @returns {boolean} 是否为图片 */ function isImageFile(filename) { const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'] const extension = getFileExtension(filename).toLowerCase() return imageExtensions.includes(extension) } /** * 判断是否为视频文件 * @param {string} filename 文件名 * @returns {boolean} 是否为视频 */ function isVideoFile(filename) { const videoExtensions = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv'] const extension = getFileExtension(filename).toLowerCase() return videoExtensions.includes(extension) } /** * 显示加载提示 * @param {string} title 提示文字 * @param {boolean} mask 是否显示透明蒙层 */ function showLoading(title = '加载中...', mask = true) { wx.showLoading({ title: title, mask: mask }) } /** * 隐藏加载提示 */ function hideLoading() { wx.hideLoading() } /** * 显示成功提示 * @param {string} title 提示文字 * @param {number} duration 显示时长 */ function showSuccess(title, duration = 2000) { wx.showToast({ title: title, icon: 'success', duration: duration }) } /** * 显示错误提示 * @param {string} title 提示文字 * @param {number} duration 显示时长 */ function showError(title, duration = 2000) { wx.showToast({ title: title, icon: 'error', duration: duration }) } /** * 显示普通提示 * @param {string} title 提示文字 * @param {number} duration 显示时长 */ function showToast(title, duration = 2000) { wx.showToast({ title: title, icon: 'none', duration: duration }) } /** * 确认对话框 * @param {string} content 内容 * @param {string} title 标题 * @returns {Promise} 用户选择结果 */ function confirm(content, title = '提示') { return new Promise((resolve) => { wx.showModal({ title: title, content: content, success: (res) => { resolve(res.confirm) } }) }) } /** * 页面跳转 * @param {string} url 页面路径 * @param {object} params 参数 */ function navigateTo(url, params = {}) { let fullUrl = url if (Object.keys(params).length > 0) { const queryString = Object.keys(params) .map(key => `${key}=${encodeURIComponent(params[key])}`) .join('&') fullUrl += (url.includes('?') ? '&' : '?') + queryString } wx.navigateTo({ url: fullUrl }) } /** * 页面重定向 * @param {string} url 页面路径 * @param {object} params 参数 */ function redirectTo(url, params = {}) { let fullUrl = url if (Object.keys(params).length > 0) { const queryString = Object.keys(params) .map(key => `${key}=${encodeURIComponent(params[key])}`) .join('&') fullUrl += (url.includes('?') ? '&' : '?') + queryString } wx.redirectTo({ url: fullUrl }) } /** * 返回上一页 * @param {number} delta 返回的页面数 */ function navigateBack(delta = 1) { wx.navigateBack({ delta: delta }) } /** * GraphQL请求封装 * @param {string} query GraphQL查询语句 * @param {object} variables 查询变量 * @returns {Promise} 请求结果 */ function graphqlRequest(query, variables = {}) { const app = getApp() return app.graphqlRequest(query, variables) } module.exports = { formatDate, formatRelativeTime, debounce, throttle, deepClone, randomString, validatePhone, validateEmail, validateIdCard, formatFileSize, getFileExtension, isImageFile, isVideoFile, showLoading, hideLoading, showSuccess, showError, showToast, confirm, navigateTo, redirectTo, navigateBack, graphqlRequest }