543 lines
11 KiB
JavaScript
543 lines
11 KiB
JavaScript
/**
|
|
* 通用工具函数
|
|
*/
|
|
|
|
/**
|
|
* 格式化时间
|
|
* @param {Date|String|Number} date 日期
|
|
* @param {String} format 格式化字符串
|
|
* @returns {String} 格式化后的时间字符串
|
|
*/
|
|
function formatTime(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 hour = String(d.getHours()).padStart(2, '0');
|
|
const minute = String(d.getMinutes()).padStart(2, '0');
|
|
const second = String(d.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 {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 seconds = Math.floor(diff / 1000);
|
|
const minutes = Math.floor(seconds / 60);
|
|
const hours = Math.floor(minutes / 60);
|
|
const days = Math.floor(hours / 24);
|
|
|
|
if (seconds < 60) {
|
|
return '刚刚';
|
|
} else if (minutes < 60) {
|
|
return `${minutes}分钟前`;
|
|
} else if (hours < 24) {
|
|
return `${hours}小时前`;
|
|
} else if (days < 7) {
|
|
return `${days}天前`;
|
|
} else {
|
|
return formatTime(date, 'MM-DD');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 防抖函数
|
|
* @param {Function} func 要防抖的函数
|
|
* @param {Number} delay 延迟时间
|
|
* @returns {Function} 防抖后的函数
|
|
*/
|
|
function debounce(func, delay = 300) {
|
|
let timer = null;
|
|
return function (...args) {
|
|
if (timer) clearTimeout(timer);
|
|
timer = setTimeout(() => {
|
|
func.apply(this, args);
|
|
}, delay);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 节流函数
|
|
* @param {Function} func 要节流的函数
|
|
* @param {Number} delay 延迟时间
|
|
* @returns {Function} 节流后的函数
|
|
*/
|
|
function throttle(func, delay = 300) {
|
|
let timer = null;
|
|
return function (...args) {
|
|
if (!timer) {
|
|
timer = setTimeout(() => {
|
|
func.apply(this, args);
|
|
timer = null;
|
|
}, delay);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 深拷贝
|
|
* @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 cloned = {};
|
|
for (const key in obj) {
|
|
if (obj.hasOwnProperty(key)) {
|
|
cloned[key] = deepClone(obj[key]);
|
|
}
|
|
}
|
|
return cloned;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
* 生成唯一ID
|
|
* @param {String} prefix 前缀
|
|
* @returns {String} 唯一ID
|
|
*/
|
|
function generateId(prefix = '') {
|
|
const timestamp = Date.now().toString(36);
|
|
const random = Math.random().toString(36).substr(2, 5);
|
|
return `${prefix}${timestamp}${random}`;
|
|
}
|
|
|
|
/**
|
|
* 验证手机号
|
|
* @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} amount 金额
|
|
* @param {Number} decimals 小数位数
|
|
* @returns {String} 格式化后的金额
|
|
*/
|
|
function formatAmount(amount, decimals = 2) {
|
|
if (isNaN(amount)) return '0.00';
|
|
return Number(amount).toFixed(decimals);
|
|
}
|
|
|
|
/**
|
|
* 格式化文件大小
|
|
* @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];
|
|
}
|
|
|
|
/**
|
|
* 获取URL参数
|
|
* @param {String} url URL地址
|
|
* @returns {Object} 参数对象
|
|
*/
|
|
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} 参数字符串
|
|
*/
|
|
function buildUrlParams(params) {
|
|
return Object.keys(params)
|
|
.filter(key => params[key] !== undefined && params[key] !== null)
|
|
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
|
|
.join('&');
|
|
}
|
|
|
|
/**
|
|
* 存储数据到本地
|
|
* @param {String} key 键名
|
|
* @param {Any} data 数据
|
|
* @param {Number} expire 过期时间(毫秒)
|
|
*/
|
|
function setStorage(key, data, expire = 0) {
|
|
const item = {
|
|
data,
|
|
timestamp: Date.now(),
|
|
expire
|
|
};
|
|
|
|
try {
|
|
wx.setStorageSync(key, JSON.stringify(item));
|
|
} catch (error) {
|
|
console.error('Storage set error:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 从本地获取数据
|
|
* @param {String} key 键名
|
|
* @returns {Any} 数据
|
|
*/
|
|
function getStorage(key) {
|
|
try {
|
|
const item = wx.getStorageSync(key);
|
|
if (!item) return null;
|
|
|
|
const parsed = JSON.parse(item);
|
|
const { data, timestamp, expire } = parsed;
|
|
|
|
// 检查是否过期
|
|
if (expire > 0 && Date.now() - timestamp > expire) {
|
|
wx.removeStorageSync(key);
|
|
return null;
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Storage get error:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除本地存储数据
|
|
* @param {String} key 键名
|
|
*/
|
|
function removeStorage(key) {
|
|
try {
|
|
wx.removeStorageSync(key);
|
|
} catch (error) {
|
|
console.error('Storage remove error:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 清空本地存储
|
|
*/
|
|
function clearStorage() {
|
|
try {
|
|
wx.clearStorageSync();
|
|
} catch (error) {
|
|
console.error('Storage clear error:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 显示成功提示
|
|
* @param {String} title 提示内容
|
|
* @param {Number} duration 显示时长
|
|
*/
|
|
function showSuccess(title, duration = 2000) {
|
|
wx.showToast({
|
|
title,
|
|
icon: 'success',
|
|
duration
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 显示错误提示
|
|
* @param {String} title 提示内容
|
|
* @param {Number} duration 显示时长
|
|
*/
|
|
function showError(title, duration = 2000) {
|
|
wx.showToast({
|
|
title,
|
|
icon: 'error',
|
|
duration
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 显示普通提示
|
|
* @param {String} title 提示内容
|
|
* @param {Number} duration 显示时长
|
|
*/
|
|
function showToast(title, duration = 2000) {
|
|
wx.showToast({
|
|
title,
|
|
icon: 'none',
|
|
duration
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 显示加载提示
|
|
* @param {String} title 提示内容
|
|
*/
|
|
function showLoading(title = '加载中...') {
|
|
wx.showLoading({
|
|
title,
|
|
mask: true
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 隐藏加载提示
|
|
*/
|
|
function hideLoading() {
|
|
wx.hideLoading();
|
|
}
|
|
|
|
/**
|
|
* 显示确认对话框
|
|
* @param {String} content 内容
|
|
* @param {String} title 标题
|
|
* @returns {Promise<Boolean>} 用户选择结果
|
|
*/
|
|
function showConfirm(content, title = '提示') {
|
|
return new Promise((resolve) => {
|
|
wx.showModal({
|
|
title,
|
|
content,
|
|
success: (res) => {
|
|
resolve(res.confirm);
|
|
},
|
|
fail: () => {
|
|
resolve(false);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 获取系统信息
|
|
* @returns {Object} 系统信息
|
|
*/
|
|
function getSystemInfo() {
|
|
try {
|
|
return wx.getSystemInfoSync();
|
|
} catch (error) {
|
|
console.error('Get system info error:', error);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查网络状态
|
|
* @returns {Promise<Object>} 网络状态
|
|
*/
|
|
function getNetworkType() {
|
|
return new Promise((resolve, reject) => {
|
|
wx.getNetworkType({
|
|
success: resolve,
|
|
fail: reject
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 复制到剪贴板
|
|
* @param {String} data 要复制的内容
|
|
* @returns {Promise<Boolean>} 复制结果
|
|
*/
|
|
function copyToClipboard(data) {
|
|
return new Promise((resolve) => {
|
|
wx.setClipboardData({
|
|
data,
|
|
success: () => {
|
|
showSuccess('复制成功');
|
|
resolve(true);
|
|
},
|
|
fail: () => {
|
|
showError('复制失败');
|
|
resolve(false);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 预览图片
|
|
* @param {String} current 当前图片URL
|
|
* @param {Array} urls 图片URL列表
|
|
*/
|
|
function previewImage(current, urls = []) {
|
|
wx.previewImage({
|
|
current,
|
|
urls: urls.length > 0 ? urls : [current]
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 拨打电话
|
|
* @param {String} phoneNumber 电话号码
|
|
*/
|
|
function makePhoneCall(phoneNumber) {
|
|
wx.makePhoneCall({
|
|
phoneNumber,
|
|
fail: () => {
|
|
showError('拨打电话失败');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 获取会员等级文本
|
|
* @param {String} level 会员等级
|
|
* @returns {String} 等级文本
|
|
*/
|
|
function getMemberLevelText(level) {
|
|
const levelMap = {
|
|
'free': '普通用户',
|
|
'vip': 'VIP会员',
|
|
'svip': 'SVIP会员'
|
|
};
|
|
return levelMap[level] || '未知等级';
|
|
}
|
|
|
|
/**
|
|
* 获取会员等级颜色
|
|
* @param {String} level 会员等级
|
|
* @returns {String} 颜色值
|
|
*/
|
|
function getMemberLevelColor(level) {
|
|
const colorMap = {
|
|
'free': '#6C757D',
|
|
'vip': '#FFD700',
|
|
'svip': '#FF6B35'
|
|
};
|
|
return colorMap[level] || '#6C757D';
|
|
}
|
|
|
|
/**
|
|
* 计算会员到期时间
|
|
* @param {String} expireTime 到期时间
|
|
* @returns {Object} 时间信息
|
|
*/
|
|
function calculateMemberExpire(expireTime) {
|
|
if (!expireTime) {
|
|
return {
|
|
isExpired: true,
|
|
daysLeft: 0,
|
|
text: '已过期'
|
|
};
|
|
}
|
|
|
|
const expire = new Date(expireTime);
|
|
const now = new Date();
|
|
const diff = expire.getTime() - now.getTime();
|
|
const daysLeft = Math.ceil(diff / (1000 * 60 * 60 * 24));
|
|
|
|
if (daysLeft <= 0) {
|
|
return {
|
|
isExpired: true,
|
|
daysLeft: 0,
|
|
text: '已过期'
|
|
};
|
|
} else if (daysLeft <= 7) {
|
|
return {
|
|
isExpired: false,
|
|
daysLeft,
|
|
text: `${daysLeft}天后到期`
|
|
};
|
|
} else {
|
|
return {
|
|
isExpired: false,
|
|
daysLeft,
|
|
text: formatTime(expireTime, 'YYYY-MM-DD') + ' 到期'
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
formatTime,
|
|
formatRelativeTime,
|
|
debounce,
|
|
throttle,
|
|
deepClone,
|
|
generateId,
|
|
validatePhone,
|
|
validateEmail,
|
|
validateIdCard,
|
|
formatAmount,
|
|
formatFileSize,
|
|
getUrlParams,
|
|
buildUrlParams,
|
|
setStorage,
|
|
getStorage,
|
|
removeStorage,
|
|
clearStorage,
|
|
showSuccess,
|
|
showError,
|
|
showToast,
|
|
showLoading,
|
|
hideLoading,
|
|
showConfirm,
|
|
getSystemInfo,
|
|
getNetworkType,
|
|
copyToClipboard,
|
|
previewImage,
|
|
makePhoneCall,
|
|
getMemberLevelText,
|
|
getMemberLevelColor,
|
|
calculateMemberExpire
|
|
}; |