You've already forked DataMate
* feat: Update site name to DataMate and refine text for AI data processing * feat: Refactor settings page and implement model access functionality - Created a new ModelAccess component for managing model configurations. - Removed the old Settings component and replaced it with a new SettingsPage component that integrates ModelAccess, SystemConfig, and WebhookConfig. - Added SystemConfig component for managing system settings. - Implemented WebhookConfig component for managing webhook configurations. - Updated API functions for model management in settings.apis.ts. - Adjusted routing to point to the new SettingsPage component. * feat: Implement Data Collection Page with Task Management and Execution Log - Created DataCollectionPage component to manage data collection tasks. - Added TaskManagement and ExecutionLog components for task handling and logging. - Integrated task operations including start, stop, edit, and delete functionalities. - Implemented filtering and searching capabilities in task management. - Introduced SimpleCronScheduler for scheduling tasks with cron expressions. - Updated CreateTask component to utilize new scheduling and template features. - Enhanced BasicInformation component to conditionally render fields based on visibility settings. - Refactored ImportConfiguration component to remove NAS import section. * feat: Update task creation API endpoint and enhance task creation form with new fields and validation * Refactor file upload and operator management components - Removed unnecessary console logs from file download and export functions. - Added size property to TaskItem interface for better task management. - Simplified TaskUpload component by utilizing useFileSliceUpload hook for file upload logic. - Enhanced OperatorPluginCreate component to handle file uploads and parsing more efficiently. - Updated ConfigureStep component to use Ant Design Form for better data handling and validation. - Improved PreviewStep component to navigate back to the operator market. - Added support for additional file types in UploadStep component. - Implemented delete operator functionality in OperatorMarketPage with confirmation prompts. - Cleaned up unused API functions in operator.api.ts to streamline the codebase. - Fixed number formatting utility to handle zero values correctly.
298 lines
8.4 KiB
TypeScript
298 lines
8.4 KiB
TypeScript
// 字节数转换为更大单位的方法
|
|
export const formatBytes = (bytes: number): string => {
|
|
if (!bytes) return "0 B";
|
|
|
|
const units = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
const k = 1024;
|
|
const decimals = 3;
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
const value = bytes / Math.pow(k, i);
|
|
|
|
// 如果是整数则不显示小数点,否则最多显示3位小数并去除末尾的0
|
|
const formattedValue =
|
|
value % 1 === 0
|
|
? value.toString()
|
|
: parseFloat(value.toFixed(decimals)).toString();
|
|
|
|
return `${formattedValue} ${units[i]}`;
|
|
};
|
|
|
|
export const formatDateTime = (dateString: string): string => {
|
|
if (!dateString) return "";
|
|
const date = new Date(dateString);
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
const hours = String(date.getHours()).padStart(2, "0");
|
|
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
};
|
|
|
|
export const formatDate = (dateString: string): string => {
|
|
if (!dateString) return "";
|
|
const date = new Date(dateString);
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
|
|
return `${year}-${month}-${day}`;
|
|
};
|
|
|
|
export const formatTime = (dateString: string): string => {
|
|
if (!dateString) return "";
|
|
const date = new Date(dateString);
|
|
const hours = String(date.getHours()).padStart(2, "0");
|
|
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
|
|
return `${hours}:${minutes}:${seconds}`;
|
|
};
|
|
|
|
export function formatExecutionDuration(
|
|
startTime: string,
|
|
endTime: string
|
|
): string {
|
|
if (!startTime || !endTime) return "--";
|
|
|
|
const start = new Date(startTime).getTime();
|
|
const end = new Date(endTime).getTime();
|
|
const durationInSeconds = Math.floor((end - start) / 1000);
|
|
return formatDuration(durationInSeconds);
|
|
}
|
|
|
|
export const formatDuration = (seconds: number): string => {
|
|
if (seconds < 0) return "--";
|
|
if (seconds < 60) {
|
|
return `${seconds} 秒`;
|
|
} else if (seconds < 3600) {
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
return secs === 0 ? `${mins} 分钟` : `${mins} 分钟 ${secs} 秒`;
|
|
} else {
|
|
const hrs = Math.floor(seconds / 3600);
|
|
const mins = Math.floor((seconds % 3600) / 60);
|
|
return mins === 0 ? `${hrs} 小时` : `${hrs} 小时 ${mins} 分钟`;
|
|
}
|
|
};
|
|
|
|
export const formatNumber = (num: number): string => {
|
|
if (!num && num !== 0) return "0";
|
|
if (num >= 1e9) {
|
|
return (num / 1e9).toFixed(2).replace(/\.?0+$/, "") + "B";
|
|
} else if (num >= 1e6) {
|
|
return (num / 1e6).toFixed(2).replace(/\.?0+$/, "") + "M";
|
|
} else if (num >= 1e3) {
|
|
return (num / 1e3).toFixed(2).replace(/\.?0+$/, "") + "K";
|
|
} else {
|
|
return num.toString();
|
|
}
|
|
};
|
|
|
|
export const formatPercentage = (num: number): string => {
|
|
return (num * 100).toFixed(2).replace(/\.?0+$/, "") + "%";
|
|
};
|
|
|
|
export const truncateString = (str: string, maxLength: number): string => {
|
|
if (str.length <= maxLength) return str;
|
|
return str.slice(0, maxLength) + "...";
|
|
};
|
|
|
|
export const capitalizeFirstLetter = (str: string): string => {
|
|
if (!str) return str;
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
};
|
|
|
|
export const lowercaseFirstLetter = (str: string): string => {
|
|
if (!str) return str;
|
|
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
};
|
|
|
|
export const slugify = (str: string): string => {
|
|
return str
|
|
.toLowerCase()
|
|
.trim()
|
|
.replace(/[\s\W-]+/g, "-")
|
|
.replace(/^-+|-+$/g, "");
|
|
};
|
|
|
|
export const unslugify = (str: string): string => {
|
|
return str.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
};
|
|
|
|
export const isValidEmail = (email: string): boolean => {
|
|
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return re.test(email.toLowerCase());
|
|
};
|
|
|
|
export const isValidURL = (url: string): boolean => {
|
|
try {
|
|
new URL(url);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
export const isValidPhoneNumber = (phone: string): boolean => {
|
|
const re = /^\+?[1-9]\d{1,14}$/; // E.164 format
|
|
return re.test(phone);
|
|
};
|
|
|
|
export const generateRandomString = (length: number): string => {
|
|
const chars =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
let result = "";
|
|
for (let i = 0; i < length; i++) {
|
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
}
|
|
return result;
|
|
};
|
|
|
|
export const generateUUID = (): string => {
|
|
// 简单的UUID生成方法
|
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
const r = (Math.random() * 16) | 0;
|
|
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
return v.toString(16);
|
|
});
|
|
};
|
|
|
|
export const sleep = (ms: number): Promise<void> => {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
};
|
|
|
|
export const debounce = <F extends (...args: any[]) => any>(
|
|
func: F,
|
|
wait: number
|
|
): F => {
|
|
let timeout: NodeJS.Timeout;
|
|
return function (this: any, ...args: any[]) {
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
} as F;
|
|
};
|
|
|
|
export const throttle = <F extends (...args: any[]) => any>(
|
|
func: F,
|
|
limit: number
|
|
): F => {
|
|
let inThrottle: boolean;
|
|
return function (this: any, ...args: any[]) {
|
|
if (!inThrottle) {
|
|
func.apply(this, args);
|
|
inThrottle = true;
|
|
setTimeout(() => (inThrottle = false), limit);
|
|
}
|
|
} as F;
|
|
};
|
|
|
|
export const deepClone = <T>(obj: T): T => {
|
|
return JSON.parse(JSON.stringify(obj));
|
|
};
|
|
|
|
export const mergeObjects = <T, U>(obj1: T, obj2: U): T & U => {
|
|
return { ...obj1, ...obj2 };
|
|
};
|
|
|
|
export const pick = <T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> => {
|
|
const result = {} as Pick<T, K>;
|
|
keys.forEach((key) => {
|
|
if (key in obj) {
|
|
result[key] = obj[key];
|
|
}
|
|
});
|
|
return result;
|
|
};
|
|
|
|
export const omit = <T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> => {
|
|
const result = { ...obj } as T;
|
|
keys.forEach((key) => {
|
|
if (key in result) {
|
|
delete result[key];
|
|
}
|
|
});
|
|
return result;
|
|
};
|
|
|
|
export const groupBy = <T, K extends keyof T>(
|
|
array: T[],
|
|
key: K
|
|
): Record<string, T[]> => {
|
|
return array.reduce((result, currentItem) => {
|
|
const groupKey = String(currentItem[key]);
|
|
if (!result[groupKey]) {
|
|
result[groupKey] = [];
|
|
}
|
|
result[groupKey].push(currentItem);
|
|
return result;
|
|
}, {} as Record<string, T[]>);
|
|
};
|
|
|
|
export const uniqueBy = <T, K extends keyof T>(array: T[], key: K): T[] => {
|
|
const seen = new Set();
|
|
return array.filter((item) => {
|
|
const k = item[key];
|
|
return seen.has(k) ? false : seen.add(k);
|
|
});
|
|
};
|
|
|
|
export const sortBy = <T, K extends keyof T>(
|
|
array: T[],
|
|
key: K,
|
|
ascending = true
|
|
): T[] => {
|
|
return [...array].sort((a, b) => {
|
|
if (a[key] < b[key]) return ascending ? -1 : 1;
|
|
if (a[key] > b[key]) return ascending ? 1 : -1;
|
|
return 0;
|
|
});
|
|
};
|
|
export const chunkArray = <T>(array: T[], size: number): T[][] => {
|
|
const result: T[][] = [];
|
|
for (let i = 0; i < array.length; i += size) {
|
|
result.push(array.slice(i, i + size));
|
|
}
|
|
return result;
|
|
};
|
|
export const arrayDifference = <T>(arr1: T[], arr2: T[]): T[] => {
|
|
const set2 = new Set(arr2);
|
|
return arr1.filter((item) => !set2.has(item));
|
|
};
|
|
|
|
export const arrayIntersection = <T>(arr1: T[], arr2: T[]): T[] => {
|
|
const set2 = new Set(arr2);
|
|
return arr1.filter((item) => set2.has(item));
|
|
};
|
|
|
|
export const arrayUnion = <T>(arr1: T[], arr2: T[]): T[] => {
|
|
return Array.from(new Set([...arr1, ...arr2]));
|
|
};
|
|
|
|
export const flattenArray = <T>(array: T[][]): T[] => {
|
|
return array.reduce((acc, val) => acc.concat(val), []);
|
|
};
|
|
|
|
export const copyToClipboard = (text: string) => {
|
|
navigator.clipboard.writeText(text);
|
|
// 这里可以添加提示消息
|
|
};
|
|
|
|
// 示例用法
|
|
// console.log(formatBytes(1024)); // "1 KB"
|
|
// console.log(formatDateTime("2023-10-01T12:34:56Z")); // "2023-10-01 12:34:56"
|
|
// console.log(isValidEmail("test@example.com")); // true
|
|
// console.log(generateUUID()); // "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
|
|
|
|
// 你可以根据需要添加更多的实用函数
|
|
|
|
// 例如:深拷贝对象、合并对象、数组去重、节流、防抖等
|
|
|
|
// 这些函数可以根据你的项目需求进行调整和扩展
|
|
|
|
// 记得添加适当的类型注解以提高代码的可读性和可维护性
|
|
|
|
// 以及编写单元测试以确保函数的正确性
|