ai-course/app/backend/src/utils/index.ts

411 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import moment from 'moment';
export function getToken(): string {
return window.localStorage.getItem('playedu-backend-token-' + getTenant()) || '';
}
export function setToken(token: string) {
window.localStorage.setItem('playedu-backend-token-' + getTenant(), token);
}
export function getTenant(): string {
return window.localStorage.getItem('tenantId') || '-1';
}
export function getTenantCode(): string {
return window.localStorage.getItem('tenantCode') || 'platform';
}
export function setTenant(tenantId: string, tenantCode: string) {
window.localStorage.setItem('tenantId', tenantId);
window.localStorage.setItem('tenantCode', tenantCode);
}
export function clearToken() {
window.localStorage.removeItem('playedu-backend-token-' + getTenant());
}
export function getLanguage(): string {
return window.localStorage.getItem('playedu-backend-language') || 'zh-CN';
}
export function setLanguage(text: string) {
window.localStorage.setItem('playedu-backend-language', text);
}
export function clearLanguage() {
window.localStorage.removeItem('playedu-backend-language');
}
export function dateFormat(dateStr: string) {
if (!dateStr) {
return '-';
}
return moment(dateStr).format('YYYY-MM-DD HH:mm');
}
export function dateFormatNoTime(dateStr: string) {
if (!dateStr) {
return '-';
}
return moment(dateStr).format('YYYY-MM-DD');
}
export function generateUUID(): string {
let guid = '';
for (let i = 1; i <= 32; i++) {
const n = Math.floor(Math.random() * 16.0).toString(16);
guid += n;
if (i === 8 || i === 12 || i === 16 || i === 20) guid += '-';
}
return guid;
}
export function transformBase64ToBlob(base64: string, mime: string, filename: string): File {
const arr = base64.split(',');
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
}
export function parseVideo(file: File): Promise<VideoParseInfo> {
return new Promise((resolve, reject) => {
const video = document.createElement('video');
video.muted = true;
video.setAttribute('src', URL.createObjectURL(file));
video.setAttribute('autoplay', 'autoplay');
video.setAttribute('crossOrigin', 'anonymous'); //设置跨域 否则toDataURL导出图片失败
video.setAttribute('width', '400'); //设置大小如果不设置下面的canvas就要按需设置
video.setAttribute('height', '300');
video.currentTime = 7; //视频时长,一定要设置,不然大概率白屏
video.addEventListener('loadeddata', function () {
const canvas = document.createElement('canvas'),
width = video.width, //canvas的尺寸和图片一样
height = video.height;
canvas.width = width; //画布大小,默认为视频宽高
canvas.height = height;
const ctx = canvas.getContext('2d');
if (!ctx) {
return reject('无法捕获视频帧');
}
ctx.drawImage(video, 0, 0, width, height); //绘制canvas
const dataURL = canvas.toDataURL('image/png'); //转换为base64
video.remove();
const info: VideoParseInfo = {
poster: dataURL,
duration: parseInt(video.duration + ''),
};
return resolve(info);
});
});
}
export function parseAudio(file: File): Promise<AudioParseInfo> {
return new Promise((resolve, reject) => {
const audio = document.createElement('audio');
audio.muted = true;
audio.setAttribute('src', URL.createObjectURL(file));
audio.setAttribute('autoplay', 'true');
audio.setAttribute('controls', 'true'); //设置设置
audio.currentTime = 7;
audio.addEventListener('loadeddata', function () {
const info: AudioParseInfo = {
duration: parseInt(audio.duration + ''),
};
audio.remove();
return resolve(info);
});
});
}
export function getHost() {
return window.location.protocol + '//' + window.location.host + '/';
}
export function inStrArray(array: string[], value: string): boolean {
for (let i = 0; i < array.length; i++) {
if (array[i] === value) {
return true;
}
}
return false;
}
export function ValidataCredentials(value: any) {
const regIdCard =
/^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/;
if (regIdCard.test(value)) {
if (value.length === 18) {
return true;
}
}
}
export function checkUrl(value: any) {
let url = value;
const str = url.substr(url.length - 1, 1);
if (str !== '/') {
url = url + '/';
}
return url;
}
export function dateWholeFormat(dateStr: string) {
if (!dateStr) {
return '';
}
return moment(dateStr).format('YYYY-MM-DD HH:mm:ss');
}
export function transUtcTime(value: string) {
const specifiedTime = value;
// 创建一个新的Date对象传入指定时间
const specifiedDate = new Date(specifiedTime);
//将指定时间转换为UTC+0时间
const utcTime = specifiedDate.toISOString();
return utcTime;
}
export function transformPlayUrls(playUrl: any[]) {
const playDATA: any[] = [];
const userToken = encodeURIComponent(getToken());
playUrl.forEach((item) => {
playDATA.push({
name: item.name,
definition: item.name,
type: item.type,
url: item.url + '&token=' + userToken,
});
});
return playDATA;
}
export function returnAppUrl(url: string): string {
if (url.substring(0, 7) === 'base64:') {
const data: any = JSON.parse(atob(url.replace('base64:', '')));
return data.url as string;
}
return url;
}
// 验证是否为blob格式
export function blobValidate(data: any) {
return data.type !== 'application/json';
}
/**
* 从视频文件提取第一帧作为封面
* @param file 视频文件
* @param time 截取的时间点默认0.1秒
* @param quality 封面质量0-1默认0.8
* @returns Promise<string> base64格式的图片
*/
export const extractVideoThumbnail = (
file: File,
time: number = 0.1,
quality: number = 0.8
): Promise<string> => {
return new Promise((resolve, reject) => {
// 验证文件类型
if (!file.type.startsWith('video/')) {
reject(new Error('文件不是视频格式'));
return;
}
// 创建视频元素
const video = document.createElement('video');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (!context) {
reject(new Error('无法创建canvas上下文'));
return;
}
// 创建对象URL
const url = URL.createObjectURL(file);
// 配置视频
video.src = url;
video.muted = true; // 静音以避免自动播放限制
video.crossOrigin = 'anonymous';
video.preload = 'metadata';
// 事件监听器
const handleError = (error?: Error) => {
cleanup();
reject(error || new Error('无法读取视频文件'));
};
const handleSuccess = () => {
cleanup();
};
const cleanup = () => {
video.removeEventListener('loadeddata', onLoaded);
video.removeEventListener('seeked', onSeeked);
video.removeEventListener('error', onError);
URL.revokeObjectURL(url);
};
const onError = () => {
handleError(new Error('视频加载失败'));
};
const onLoaded = () => {
// 检查视频尺寸
if (video.videoWidth === 0 || video.videoHeight === 0) {
handleError(new Error('视频尺寸无效'));
return;
}
// 设置canvas尺寸
// 限制最大尺寸,避免生成过大的图片
const maxWidth = 800;
const maxHeight = 450;
let width = video.videoWidth;
let height = video.videoHeight;
// 按比例缩放
if (width > maxWidth || height > maxHeight) {
const ratio = Math.min(maxWidth / width, maxHeight / height);
width = Math.floor(width * ratio);
height = Math.floor(height * ratio);
}
canvas.width = width;
canvas.height = height;
// 尝试截取指定时间点的帧
video.currentTime = Math.min(time, video.duration || 1);
};
const onSeeked = () => {
try {
// 绘制视频帧到canvas
context.drawImage(video, 0, 0, canvas.width, canvas.height);
// 将canvas转为base64
const thumbnail = canvas.toDataURL('image/jpeg', quality);
// 检查生成的图片是否有效
if (!thumbnail || thumbnail.length < 100) {
handleError(new Error('生成封面失败'));
return;
}
resolve(thumbnail);
handleSuccess();
} catch (error) {
handleError(error instanceof Error ? error : new Error('生成封面失败'));
}
};
// 绑定事件
video.addEventListener('loadeddata', onLoaded);
video.addEventListener('seeked', onSeeked);
video.addEventListener('error', onError);
// 设置超时处理
const timeout = setTimeout(() => {
handleError(new Error('生成封面超时'));
}, 10000); // 10秒超时
// 清理超时
const originalResolve = resolve;
const originalReject = reject;
resolve = ((...args) => {
clearTimeout(timeout);
originalResolve(...args);
}) as typeof resolve;
reject = ((...args) => {
clearTimeout(timeout);
originalReject(...args);
}) as typeof reject;
// 开始加载视频
video.load();
});
};
/**
* 生成默认的视频封面(当提取失败时使用)
* @param width 封面宽度
* @param height 封面高度
* @param text 封面文字
* @returns base64格式的SVG图片
*/
export const generateDefaultVideoPoster = (
width: number = 800,
height: number = 450,
text: string = '视频预览'
): string => {
const colors = [
{ bg: '#1890ff', text: '#ffffff' }, // 蓝色
{ bg: '#52c41a', text: '#ffffff' }, // 绿色
{ bg: '#fa8c16', text: '#ffffff' }, // 橙色
{ bg: '#f5222d', text: '#ffffff' }, // 红色
{ bg: '#722ed1', text: '#ffffff' }, // 紫色
];
const color = colors[Math.floor(Math.random() * colors.length)];
const svg = `
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="${color.bg}"/>
<path d="M${width / 2 - 40} ${height / 2 - 40} L${width / 2 + 40} ${height / 2} L${width / 2 - 40} ${height / 2 + 40} Z"
fill="${color.text}" stroke="${color.text}" stroke-width="2"/>
<text x="${width / 2}" y="${height / 2 + 80}"
text-anchor="middle"
font-family="Arial, sans-serif"
font-size="18"
font-weight="bold"
fill="${color.text}">
${text}
</text>
<text x="${width / 2}" y="${height - 20}"
text-anchor="middle"
font-family="Arial, sans-serif"
font-size="12"
fill="rgba(255,255,255,0.8)">
点击播放
</text>
</svg>
`;
return `data:image/svg+xml;base64,${btoa(svg)}`;
};
/**
* base64转Blob对象
*/
export const dataURLtoBlob = (dataurl: string): Blob => {
const arr = dataurl.split(',');
const mimeMatch = arr[0].match(/:(.*?);/);
const mime = mimeMatch ? mimeMatch[1] : 'image/jpeg';
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
};
/**
* 获取文件扩展名
*/
export const getFileExtension = (filename: string): string => {
return filename.slice(((filename.lastIndexOf('.') - 1) >>> 0) + 2);
};