Files
lingji-work-fe/src/utils/tools.ts

342 lines
9.8 KiB
TypeScript
Raw Normal View History

2025-06-27 18:37:42 +08:00
/*
* @Author: RenXiaoDong
* @Date: 2025-06-27 17:36:31
*/
import dayjs from 'dayjs';
2025-07-16 10:14:04 +08:00
2025-06-27 18:37:42 +08:00
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) => {
2025-07-02 17:55:20 +08:00
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 || ''}`;
}
2025-07-21 12:01:32 +08:00
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);
}
2025-07-17 17:23:40 +08:00
2025-07-21 15:10:26 +08:00
export function genRandomId() {
return `id_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
2025-07-31 18:18:50 +08:00
}
export function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 Bytes';
2025-08-01 11:49:15 +08:00
2025-07-31 18:18:50 +08:00
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
2025-08-01 11:49:15 +08:00
2025-07-31 18:18:50 +08:00
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
2025-08-01 11:49:15 +08:00
// 修改函数以同时获取视频时长和首帧
export function getVideoInfo(file: File): Promise<{ duration: number; firstFrame: string }> {
2025-07-31 18:18:50 +08:00
return new Promise((resolve) => {
const video = document.createElement('video');
video.preload = 'metadata';
2025-08-01 11:49:15 +08:00
video.crossOrigin = 'anonymous'; // 避免跨域问题
video.muted = true; // 静音,避免意外播放声音
video.style.position = 'fixed'; // 确保视频元素在DOM中
video.style.top = '-1000px'; // 但不可见
document.body.appendChild(video); // 添加到DOM
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
let hasResolved = false;
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
// 先获取元数据(时长)
video.onloadedmetadata = function () {
// 视频时长
const duration = video.duration;
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
// 尝试将视频定位到非常小的时间点,确保有帧可捕获
if (duration > 0) {
video.currentTime = Math.min(0.1, duration / 2);
}
};
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
// 当视频定位完成后尝试捕获首帧
video.onseeked = function () {
if (hasResolved) return;
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
}
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
// 清理
2025-07-31 18:18:50 +08:00
window.URL.revokeObjectURL(video.src);
2025-08-01 11:49:15 +08:00
document.body.removeChild(video);
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
// 返回结果
hasResolved = true;
resolve({
duration: video.duration,
2025-08-07 18:05:27 +08:00
firstFrame: canvas.toDataURL('image/jpeg', 0.9), // 提高质量
2025-08-01 11:49:15 +08:00
});
};
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
// 作为备选方案监听loadeddata事件
video.onloadeddata = function () {
if (hasResolved) return;
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
// 尝试捕获帧
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
}
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
// 检查是否捕获到有效帧(非全黑)
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) {
2025-08-07 18:05:27 +08:00
if (imageData.data[i] > 10 || imageData.data[i + 1] > 10 || imageData.data[i + 2] > 10) {
2025-08-01 11:49:15 +08:00
isAllBlack = false;
break;
}
}
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
if (!isAllBlack) {
// 清理
window.URL.revokeObjectURL(video.src);
document.body.removeChild(video);
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
// 返回结果
hasResolved = true;
resolve({
duration: video.duration,
2025-08-07 18:05:27 +08:00
firstFrame: canvas.toDataURL('image/jpeg', 0.9),
2025-08-01 11:49:15 +08:00
});
return;
}
}
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
// 如果是全黑帧尝试定位到0.1秒
if (video.duration > 0) {
video.currentTime = 0.1;
}
2025-07-31 18:18:50 +08:00
};
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
// 设置视频源以触发加载
2025-07-31 18:18:50 +08:00
video.src = URL.createObjectURL(file);
2025-08-07 18:05:27 +08:00
2025-08-01 11:49:15 +08:00
// 设置超时,防止长时间无响应
setTimeout(() => {
if (!hasResolved) {
document.body.removeChild(video);
resolve({
duration: 0,
2025-08-07 18:05:27 +08:00
firstFrame: '',
2025-08-01 11:49:15 +08:00
});
}
}, 5000); // 5秒超时
2025-07-31 18:18:50 +08:00
});
2025-08-01 11:49:15 +08:00
}
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`;
}
};
2025-08-01 15:03:16 +08:00
2025-08-07 18:05:27 +08:00
export function convertVideoUrlToCoverUrl(videoUrl: string): string {
2025-08-01 15:03:16 +08:00
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';
}
2025-08-07 18:05:27 +08:00
/**
* URL
*/
export const generateFullUrl = (pathTemplate: string, params: Record<string, string | number> = {}): 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}`;
};
2025-08-11 22:40:54 +08:00
/** 图片朝向类型 */
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<ImageOrientation> => {
const { width, height } = await getImageNaturalSize(url);
return getImageOrientationBySize(width, height);
};