Claw 项目完整结构提交
This commit is contained in:
310
Claw/client/wechat_app/utils/util.js
Normal file
310
Claw/client/wechat_app/utils/util.js
Normal file
@@ -0,0 +1,310 @@
|
||||
// 通用工具函数
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
* @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
|
||||
}
|
||||
Reference in New Issue
Block a user