/* * @Author: RenXiaoDong * @Date: 2025-06-27 17:36:31 */ import dayjs from 'dayjs'; export function toFixed(num: number | string, n: number): number { return parseFloat(parseFloat(num.toString()).toFixed(n)); } export function isNotData(n: number): boolean { if (n === undefined) { return true; } return n === -2147483648; } export function splitNumber(num: number): string | number { if (!num) { return num; } const parts = num.toString().split('.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); return parts.join('.'); } export function formatNumberShow(...args: any[]): string | number { const [_args] = args; const { value, len = 2, split = true, showExactValue = false } = typeof _args === 'object' ? _args : { value: _args }; const getNumber = (value: number) => { return split ? splitNumber(value) : value; }; if (isNotData(value)) { return '-'; } if (value < 0) { return `-${formatNumberShow({ value: -value, len, split, showExactValue, })}`; } if (showExactValue) { return getNumber(toFixed(value, len)); } if (value < 10000) { return getNumber(toFixed(value, len)); } else if (value < 100000000) { const _n = Math.round((value / 10000) * 100) / 100; return split ? `${splitNumber(_n)}w` : `${toFixed(_n, len)}w`; } else { const _n = Math.round((value / 100000000) * 100) / 100; return split ? `${splitNumber(_n)}亿` : `${toFixed(_n, len)}亿`; } } export function formatTableField(fieldItem: any, rowValue: any, showExactValue = false) { // 获取嵌套属性值的函数 const getNestedValue = (obj: any, path: string) => { if (!obj || !path) return undefined; // 如果路径包含点号,说明是链式取值 if (path.includes('.')) { return path.split('.').reduce((current, key) => { return current && current[key] !== undefined ? current[key] : undefined; }, obj); } // 普通属性取值 return obj[path]; }; const _getValue = (value: any) => { if (!isNumber(value)) return value || '-'; return formatNumberShow({ value, showExactValue }); }; // 使用链式取值获取数据 const rawValue = getNestedValue(rowValue, fieldItem.dataIndex); const value = _getValue(rawValue ?? '-'); return `${fieldItem.prefix || ''}${value}${fieldItem.suffix || ''}`; } export function exactFormatTime(val: number, curYearFmt = 'MM-DD HH:mm:ss', otherYearFmt = 'YYYY-MM-DD HH:mm:ss') { if (!val) return '-'; const year = dayjs(val * 1000).year(); const currYear = dayjs().year(); const diff = year - currYear; const fmt = diff === 0 ? curYearFmt : otherYearFmt; return dayjs(val * 1000).format(fmt); } // 导出文件 export function downloadByUrl(url: string, filename?: string) { const a = document.createElement('a'); a.href = url; if (filename) { a.download = filename; } a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); } export function genRandomId() { return `id_${Date.now()}_${Math.floor(Math.random() * 10000)}`; } export function formatFileSize(bytes: number): string { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', '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]; } // 修改函数以同时获取视频时长和首帧 export function getVideoInfo(file: File): Promise<{ duration: number; firstFrame: string }> { return new Promise((resolve) => { const video = document.createElement('video'); video.preload = 'metadata'; video.crossOrigin = 'anonymous'; // 避免跨域问题 video.muted = true; // 静音,避免意外播放声音 video.style.position = 'fixed'; // 确保视频元素在DOM中 video.style.top = '-1000px'; // 但不可见 document.body.appendChild(video); // 添加到DOM let hasResolved = false; // 先获取元数据(时长) video.onloadedmetadata = function () { // 视频时长 const duration = video.duration; // 尝试将视频定位到非常小的时间点,确保有帧可捕获 if (duration > 0) { video.currentTime = Math.min(0.1, duration / 2); } }; // 当视频定位完成后尝试捕获首帧 video.onseeked = function () { if (hasResolved) return; const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext('2d'); if (ctx) { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); } // 清理 window.URL.revokeObjectURL(video.src); document.body.removeChild(video); // 返回结果 hasResolved = true; resolve({ duration: video.duration, firstFrame: canvas.toDataURL('image/jpeg', 0.9), // 提高质量 }); }; // 作为备选方案,监听loadeddata事件 video.onloadeddata = function () { if (hasResolved) return; // 尝试捕获帧 const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext('2d'); if (ctx) { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); } // 检查是否捕获到有效帧(非全黑) const imageData = ctx?.getImageData(0, 0, canvas.width, canvas.height); if (imageData) { let isAllBlack = true; for (let i = 0; i < imageData.data.length; i += 4) { if (imageData.data[i] > 10 || imageData.data[i + 1] > 10 || imageData.data[i + 2] > 10) { isAllBlack = false; break; } } if (!isAllBlack) { // 清理 window.URL.revokeObjectURL(video.src); document.body.removeChild(video); // 返回结果 hasResolved = true; resolve({ duration: video.duration, firstFrame: canvas.toDataURL('image/jpeg', 0.9), }); return; } } // 如果是全黑帧,尝试定位到0.1秒 if (video.duration > 0) { video.currentTime = 0.1; } }; // 设置视频源以触发加载 video.src = URL.createObjectURL(file); // 设置超时,防止长时间无响应 setTimeout(() => { if (!hasResolved) { document.body.removeChild(video); resolve({ duration: 0, firstFrame: '', }); } }, 5000); // 5秒超时 }); } export const formatDuration = (seconds: number) => { const hours = Math.floor(seconds / 3600); const remainingSecondsAfterHours = seconds % 3600; const minutes = Math.floor(remainingSecondsAfterHours / 60); const remainingSeconds = Math.floor(remainingSecondsAfterHours % 60); if (hours > 0) { if (remainingSecondsAfterHours === 0) { return `${hours}小时`; } if (remainingSeconds === 0) { return `${hours}小时${minutes}分`; } return `${hours}小时${minutes}分${remainingSeconds}秒`; } else if (minutes > 0) { if (remainingSeconds === 0) { return `${minutes}分`; } return `${minutes}分${remainingSeconds}秒`; } else { return `${remainingSeconds}秒`; } }; export const formatUploadSpeed = (bytesPerSecond: number): string => { if (bytesPerSecond < 1024) { return `${bytesPerSecond.toFixed(2)} B/s`; } else if (bytesPerSecond < 1024 * 1024) { return `${(bytesPerSecond / 1024).toFixed(2)} KB/s`; } else { return `${(bytesPerSecond / (1024 * 1024)).toFixed(2)} MB/s`; } }; export function convertVideoUrlToCoverUrl(videoUrl: string): string { if (!videoUrl || typeof videoUrl !== 'string') { console.error('Invalid video URL'); return ''; } const urlWithCovers = videoUrl.replace('/videos/', '/covers/'); const lastDotIndex = urlWithCovers.lastIndexOf('.'); if (lastDotIndex !== -1) { return urlWithCovers.substring(0, lastDotIndex) + '.jpg'; } return urlWithCovers + '.jpg'; } /** * 生成包含协议、域名和参数的完整URL */ export const generateFullUrl = (pathTemplate: string, params: Record = {}): string => { const protocol = window.location.protocol; const hostname = window.location.hostname; const port = window.location.port ? `:${window.location.port}` : ''; const baseUrl = `${protocol}//${hostname}${port}`; let path = pathTemplate; Object.entries(params).forEach(([key, value]) => { path = path.replace(`:${key}`, String(value)); }); return `${baseUrl}${path}`; }; /** 图片朝向类型 */ export type ImageOrientation = 'landscape' | 'portrait' | 'square'; /** * 根据宽高判断图片是横图/竖图/正方形 */ export const getImageOrientationBySize = (width: number, height: number): ImageOrientation => { if (!width || !height) return 'square'; if (width > height) return 'landscape'; if (height > width) return 'portrait'; return 'square'; }; /** * 加载图片自然尺寸(跨域下需服务端允许匿名访问) */ export const getImageNaturalSize = (url: string): Promise<{ width: number; height: number }> => { return new Promise((resolve) => { const img = new Image(); img.onload = () => { resolve({ width: img.naturalWidth, height: img.naturalHeight }); }; img.onerror = () => { resolve({ width: 0, height: 0 }); }; // 若是跨域资源并允许匿名访问,可尝试设置 try { img.crossOrigin = 'anonymous'; } catch (_) {} img.src = url; }); }; /** * 基于图片 URL 判断图片横竖 */ export const getImageOrientationByUrl = async (url: string): Promise => { const { width, height } = await getImageNaturalSize(url); return getImageOrientationBySize(width, height); };