262 lines
6.6 KiB
JavaScript
262 lines
6.6 KiB
JavaScript
/**
|
|
* 通用工具函数
|
|
*/
|
|
|
|
/**
|
|
* 格式化时间
|
|
* @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)
|
|
}
|
|
}
|
|
})
|
|
} |