/** * 工具函数库 */ /** * 格式化日期时间 * @param {String|Date} dateTime 日期时间 * @param {String} format 格式,默认 'YYYY-MM-DD HH:mm:ss' * @returns {String} 格式化后的字符串 */ export const formatDateTime = (dateTime, format = 'YYYY-MM-DD HH:mm:ss') => { if (!dateTime) return '' const date = parseDate(dateTime) const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') const hour = String(date.getHours()).padStart(2, '0') const minute = String(date.getMinutes()).padStart(2, '0') const second = String(date.getSeconds()).padStart(2, '0') return format .replace('YYYY', year) .replace('MM', month) .replace('DD', day) .replace('HH', hour) .replace('mm', minute) .replace('ss', second) } /** * 格式化时间(月日 时:分) * @param {String|Date} dateTime 日期时间 * @returns {String} 格式化后的字符串,如 "10月18日 14:00" */ export const formatTime = (dateTime) => { if (!dateTime) return '' const date = parseDate(dateTime) const month = date.getMonth() + 1 const day = date.getDate() const hour = date.getHours() const minute = date.getMinutes() return `${month}月${day}日 ${hour}:${minute < 10 ? '0' + minute : minute}` } /** * 计算倒计时 * @param {String|Date} endTime 结束时间 * @returns {Object} { days, hours, minutes, seconds, isExpired } */ export const calculateCountdown = (endTime) => { if (!endTime) return { isExpired: true } const end = parseDate(endTime).getTime() const now = new Date().getTime() const diff = end - now if (diff <= 0) { return { days: 0, hours: 0, minutes: 0, seconds: 0, isExpired: true } } const days = Math.floor(diff / (1000 * 60 * 60 * 24)) const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)) const seconds = Math.floor((diff % (1000 * 60)) / 1000) return { days, hours, minutes, seconds, isExpired: false } } /** * 格式化倒计时文本 * @param {String|Date} endTime 结束时间 * @returns {String} 倒计时文本,如 "2天5小时"、"5小时30分钟"、"30分钟" */ export const formatCountdown = (endTime) => { const { days, hours, minutes, isExpired } = calculateCountdown(endTime) if (isExpired) return '已截止' if (days > 0) { return `${days}天${hours}小时` } else if (hours > 0) { return `${hours}小时${minutes}分钟` } else { return `${minutes}分钟` } } /** * 格式化价格 * @param {Number} price 价格 * @returns {String} 格式化后的价格,如 "99.00" */ export const formatPrice = (price) => { if (price === null || price === undefined) return '0.00' return Number(price).toFixed(2) } /** * 脱敏手机号 * @param {String} phone 手机号 * @returns {String} 脱敏后的手机号,如 "138****8888" */ export const maskPhone = (phone) => { if (!phone) return '' return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') } /** * 脱敏姓名 * @param {String} name 姓名 * @returns {String} 脱敏后的姓名,如 "张*"、"欧阳**" */ export const maskName = (name) => { if (!name) return '' if (name.length === 2) { return name[0] + '*' } else if (name.length > 2) { return name[0] + '*'.repeat(name.length - 1) } return name } /** * 节流函数 * @param {Function} func 要执行的函数 * @param {Number} delay 延迟时间(毫秒) * @returns {Function} 节流后的函数 */ export const throttle = (func, delay = 500) => { let timer = null return function(...args) { if (!timer) { timer = setTimeout(() => { func.apply(this, args) timer = null }, delay) } } } /** * 防抖函数 * @param {Function} func 要执行的函数 * @param {Number} delay 延迟时间(毫秒) * @returns {Function} 防抖后的函数 */ export const debounce = (func, delay = 500) => { let timer = null return function(...args) { if (timer) clearTimeout(timer) timer = setTimeout(() => { func.apply(this, args) }, delay) } } /** * 判断是否登录 * @returns {Boolean} 是否已登录 */ export const isLoggedIn = () => { const token = uni.getStorageSync('token') const userInfo = uni.getStorageSync('userInfo') return !!(token && userInfo) } /** * 跳转登录页 * @param {String} redirectUrl 登录后要跳转的页面 */ export const goToLogin = (redirectUrl = '') => { let url = '/pages/page3/page3' if (redirectUrl) { url += `?redirect=${encodeURIComponent(redirectUrl)}` } uni.navigateTo({ url }) } /** * 检查登录状态(未登录则跳转登录页) * @param {String} redirectUrl 登录后要跳转的页面 * @returns {Boolean} 是否已登录 */ export const checkLogin = (redirectUrl = '') => { if (!isLoggedIn()) { goToLogin(redirectUrl) return false } return true } /** * 获取年龄 * @param {String} birthday 生日,格式 'YYYY-MM-DD' * @returns {Number} 年龄 */ export const getAge = (birthday) => { if (!birthday) return 0 const birthDate = parseDate(birthday) const today = new Date() let age = today.getFullYear() - birthDate.getFullYear() const monthDiff = today.getMonth() - birthDate.getMonth() if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { age-- } return age } /** * 获取星座 * @param {String} birthday 生日,格式 'YYYY-MM-DD' * @returns {String} 星座名称 */ export const getConstellation = (birthday) => { if (!birthday) return '' const date = parseDate(birthday) const month = date.getMonth() + 1 const day = date.getDate() const constellations = [ { name: '摩羯座', start: [12, 22], end: [1, 19] }, { name: '水瓶座', start: [1, 20], end: [2, 18] }, { name: '双鱼座', start: [2, 19], end: [3, 20] }, { name: '白羊座', start: [3, 21], end: [4, 19] }, { name: '金牛座', start: [4, 20], end: [5, 20] }, { name: '双子座', start: [5, 21], end: [6, 21] }, { name: '巨蟹座', start: [6, 22], end: [7, 22] }, { name: '狮子座', start: [7, 23], end: [8, 22] }, { name: '处女座', start: [8, 23], end: [9, 22] }, { name: '天秤座', start: [9, 23], end: [10, 23] }, { name: '天蝎座', start: [10, 24], end: [11, 22] }, { name: '射手座', start: [11, 23], end: [12, 21] } ] for (const constellation of constellations) { const [startMonth, startDay] = constellation.start const [endMonth, endDay] = constellation.end if (startMonth === endMonth) { if (month === startMonth && day >= startDay && day <= endDay) { return constellation.name } } else { if ((month === startMonth && day >= startDay) || (month === endMonth && day <= endDay)) { return constellation.name } } } return '' } /** * 图片预览 * @param {String|Array} urls 图片地址(单张或数组) * @param {Number} current 当前显示图片的索引,默认为0 */ export const previewImage = (urls, current = 0) => { const imageUrls = Array.isArray(urls) ? urls : [urls] uni.previewImage({ urls: imageUrls, current: current }) } /** * 分享功能 * @param {Object} options 分享选项 { title, path, imageUrl } */ export const share = (options = {}) => { return { title: options.title || '遇见对的人,从这里开始', path: options.path || '/pages/index/index', imageUrl: options.imageUrl || '/static/share-default.jpg' } } /** * 复制到剪贴板 * @param {String} text 要复制的文本 * @param {String} successMsg 成功提示文本 */ export const copyToClipboard = (text, successMsg = '复制成功') => { uni.setClipboardData({ data: text, success: () => { uni.showToast({ title: successMsg, icon: 'success' }) } }) } /** * 拨打电话 * @param {String} phoneNumber 电话号码 */ export const makePhoneCall = (phoneNumber) => { uni.makePhoneCall({ phoneNumber: phoneNumber }) } export default { formatDateTime, formatTime, calculateCountdown, formatCountdown, formatPrice, maskPhone, maskName, throttle, debounce, isLoggedIn, goToLogin, checkLogin, getAge, getConstellation, previewImage, share, copyToClipboard, makePhoneCall } /** * 统一的跨端日期解析(兼容 iOS) * - 将 'YYYY-MM-DD HH:mm:ss' 或 'YYYY-MM-DD' 转为 'YYYY/MM/DD HH:mm:ss' * - 直接返回 Date 时不处理 */ export function parseDate(dateTime) { if (!dateTime) return new Date(NaN) if (dateTime instanceof Date) return dateTime let s = String(dateTime).trim() // 若是纯数字时间戳 if (/^\d{10,13}$/.test(s)) { const ts = s.length === 10 ? Number(s) * 1000 : Number(s) return new Date(ts) } // iOS 兼容:横杠替换为斜杠 s = s.replace(/-/g, '/').replace('T', ' ').replace(/\.\d{3}Z?$/, '') return new Date(s) }