260 lines
7.0 KiB
TypeScript
260 lines
7.0 KiB
TypeScript
import { type ClassValue, clsx } from 'clsx';
|
|
|
|
// 合并 CSS 类名
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return clsx(inputs);
|
|
}
|
|
|
|
// 格式化货币
|
|
export function formatCurrency(amount: number, currency: 'CNY' | 'USD' = 'CNY') {
|
|
return new Intl.NumberFormat('zh-CN', {
|
|
style: 'currency',
|
|
currency: currency,
|
|
}).format(amount);
|
|
}
|
|
|
|
// 格式化时间
|
|
export function formatTime(date: string | Date) {
|
|
return new Intl.DateTimeFormat('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
}).format(new Date(date));
|
|
}
|
|
|
|
// 格式化相对时间
|
|
export function formatRelativeTime(date: string | Date) {
|
|
const now = new Date();
|
|
const target = new Date(date);
|
|
const diffInSeconds = Math.floor((now.getTime() - target.getTime()) / 1000);
|
|
|
|
if (diffInSeconds < 60) {
|
|
return `${diffInSeconds}秒前`;
|
|
} else if (diffInSeconds < 3600) {
|
|
return `${Math.floor(diffInSeconds / 60)}分钟前`;
|
|
} else if (diffInSeconds < 86400) {
|
|
return `${Math.floor(diffInSeconds / 3600)}小时前`;
|
|
} else if (diffInSeconds < 2592000) {
|
|
return `${Math.floor(diffInSeconds / 86400)}天前`;
|
|
} else {
|
|
return formatTime(date);
|
|
}
|
|
}
|
|
|
|
// 格式化通话时长
|
|
export function formatDuration(seconds: number) {
|
|
const hours = Math.floor(seconds / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
const remainingSeconds = seconds % 60;
|
|
|
|
if (hours > 0) {
|
|
return `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
|
|
} else {
|
|
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
|
}
|
|
}
|
|
|
|
// 格式化文件大小
|
|
export function formatFileSize(bytes: number) {
|
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
if (bytes === 0) return '0 B';
|
|
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
|
}
|
|
|
|
// 生成随机ID
|
|
export function generateId() {
|
|
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
}
|
|
|
|
// 防抖函数
|
|
export function debounce<T extends (...args: any[]) => any>(
|
|
func: T,
|
|
wait: number
|
|
): (...args: Parameters<T>) => void {
|
|
let timeout: NodeJS.Timeout;
|
|
return (...args: Parameters<T>) => {
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(() => func(...args), wait);
|
|
};
|
|
}
|
|
|
|
// 节流函数
|
|
export function throttle<T extends (...args: any[]) => any>(
|
|
func: T,
|
|
limit: number
|
|
): (...args: Parameters<T>) => void {
|
|
let inThrottle: boolean;
|
|
return (...args: Parameters<T>) => {
|
|
if (!inThrottle) {
|
|
func(...args);
|
|
inThrottle = true;
|
|
setTimeout(() => (inThrottle = false), limit);
|
|
}
|
|
};
|
|
}
|
|
|
|
// 深拷贝
|
|
export function deepClone<T>(obj: T): T {
|
|
if (obj === null || typeof obj !== 'object') return obj;
|
|
if (obj instanceof Date) return new Date(obj.getTime()) as any;
|
|
if (obj instanceof Array) return obj.map(item => deepClone(item)) as any;
|
|
if (typeof obj === 'object') {
|
|
const clonedObj = {} as any;
|
|
for (const key in obj) {
|
|
if (obj.hasOwnProperty(key)) {
|
|
clonedObj[key] = deepClone(obj[key]);
|
|
}
|
|
}
|
|
return clonedObj;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
// 验证邮箱
|
|
export function isValidEmail(email: string): boolean {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return emailRegex.test(email);
|
|
}
|
|
|
|
// 验证手机号
|
|
export function isValidPhone(phone: string): boolean {
|
|
const phoneRegex = /^1[3-9]\d{9}$/;
|
|
return phoneRegex.test(phone);
|
|
}
|
|
|
|
// 获取通话状态的中文描述
|
|
export function getCallStatusText(status: string): string {
|
|
const statusMap: Record<string, string> = {
|
|
pending: '等待中',
|
|
active: '通话中',
|
|
ended: '已结束',
|
|
cancelled: '已取消',
|
|
failed: '失败',
|
|
};
|
|
return statusMap[status] || status;
|
|
}
|
|
|
|
// 获取通话模式的中文描述
|
|
export function getCallModeText(mode: string): string {
|
|
const modeMap: Record<string, string> = {
|
|
ai_voice: 'AI语音',
|
|
ai_video: 'AI视频',
|
|
sign_language: '手语翻译',
|
|
human_interpreter: '真人翻译',
|
|
};
|
|
return modeMap[mode] || mode;
|
|
}
|
|
|
|
// 获取用户类型的中文描述
|
|
export function getUserTypeText(type: string): string {
|
|
const typeMap: Record<string, string> = {
|
|
individual: '个人用户',
|
|
enterprise: '企业用户',
|
|
};
|
|
return typeMap[type] || type;
|
|
}
|
|
|
|
// 获取订单状态的中文描述
|
|
export function getOrderStatusText(status: string): string {
|
|
const statusMap: Record<string, string> = {
|
|
pending: '待处理',
|
|
completed: '已完成',
|
|
cancelled: '已取消',
|
|
refunded: '已退款',
|
|
};
|
|
return statusMap[status] || status;
|
|
}
|
|
|
|
// 获取文档翻译状态的中文描述
|
|
export function getDocumentStatusText(status: string): string {
|
|
const statusMap: Record<string, string> = {
|
|
uploaded: '已上传',
|
|
processing: '处理中',
|
|
completed: '已完成',
|
|
failed: '失败',
|
|
};
|
|
return statusMap[status] || status;
|
|
}
|
|
|
|
// 计算通话费用(按分钟向上取整)
|
|
export function calculateCallCost(durationInSeconds: number, ratePerMinute: number): number {
|
|
const minutes = Math.ceil(durationInSeconds / 60);
|
|
return minutes * ratePerMinute;
|
|
}
|
|
|
|
// 检查余额是否足够
|
|
export function checkBalanceSufficient(balance: number, requiredAmount: number): boolean {
|
|
return balance >= requiredAmount;
|
|
}
|
|
|
|
// 获取颜色类名基于状态
|
|
export function getStatusColor(status: string): string {
|
|
const colorMap: Record<string, string> = {
|
|
active: 'text-green-600 bg-green-100',
|
|
pending: 'text-yellow-600 bg-yellow-100',
|
|
ended: 'text-gray-600 bg-gray-100',
|
|
cancelled: 'text-red-600 bg-red-100',
|
|
failed: 'text-red-600 bg-red-100',
|
|
completed: 'text-green-600 bg-green-100',
|
|
processing: 'text-blue-600 bg-blue-100',
|
|
uploaded: 'text-purple-600 bg-purple-100',
|
|
};
|
|
return colorMap[status] || 'text-gray-600 bg-gray-100';
|
|
}
|
|
|
|
// 安全地解析JSON
|
|
export function safeJsonParse<T>(str: string, fallback: T): T {
|
|
try {
|
|
return JSON.parse(str);
|
|
} catch (error) {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
// 生成头像URL
|
|
export function generateAvatarUrl(name: string): string {
|
|
const firstChar = name.charAt(0).toUpperCase();
|
|
return `https://ui-avatars.com/api/?name=${encodeURIComponent(firstChar)}&background=random&color=fff&size=40`;
|
|
}
|
|
|
|
// 导出所有状态文本映射
|
|
export const STATUS_TEXTS = {
|
|
CALL_STATUS: {
|
|
pending: '等待中',
|
|
active: '通话中',
|
|
ended: '已结束',
|
|
cancelled: '已取消',
|
|
failed: '失败',
|
|
},
|
|
CALL_MODE: {
|
|
ai_voice: 'AI语音',
|
|
ai_video: 'AI视频',
|
|
sign_language: '手语翻译',
|
|
human_interpreter: '真人翻译',
|
|
},
|
|
USER_TYPE: {
|
|
individual: '个人用户',
|
|
enterprise: '企业用户',
|
|
},
|
|
ORDER_STATUS: {
|
|
pending: '待处理',
|
|
completed: '已完成',
|
|
cancelled: '已取消',
|
|
refunded: '已退款',
|
|
},
|
|
DOCUMENT_STATUS: {
|
|
uploaded: '已上传',
|
|
processing: '处理中',
|
|
completed: '已完成',
|
|
failed: '失败',
|
|
},
|
|
NOTIFICATION_TYPE: {
|
|
info: '信息',
|
|
warning: '警告',
|
|
error: '错误',
|
|
success: '成功',
|
|
},
|
|
} as const;
|