feat(utils): add common utility functions and validation constants
This commit is contained in:
51
src/utils/dom.ts
Normal file
51
src/utils/dom.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* DOM 操作相关工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 检查元素是否包含指定 class
|
||||
* @param ele HTML 元素
|
||||
* @param cls class 名称
|
||||
* @returns 是否包含
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const hasActiveClass = hasClass(element, 'active');
|
||||
* ```
|
||||
*/
|
||||
export function hasClass(ele: HTMLElement, cls: string): boolean {
|
||||
return !!ele.className.match(new RegExp("(\\s|^)" + cls + "(\\s|$)"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 为元素添加 class
|
||||
* @param ele HTML 元素
|
||||
* @param cls class 名称
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* addClass(element, 'active');
|
||||
* ```
|
||||
*/
|
||||
export function addClass(ele: HTMLElement, cls: string): void {
|
||||
if (!hasClass(ele, cls)) {
|
||||
ele.className += " " + cls;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从元素移除 class
|
||||
* @param ele HTML 元素
|
||||
* @param cls class 名称
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* removeClass(element, 'active');
|
||||
* ```
|
||||
*/
|
||||
export function removeClass(ele: HTMLElement, cls: string): void {
|
||||
if (hasClass(ele, cls)) {
|
||||
const reg = new RegExp("(\\s|^)" + cls + "(\\s|$)");
|
||||
ele.className = ele.className.replace(reg, " ");
|
||||
}
|
||||
}
|
||||
73
src/utils/download.ts
Normal file
73
src/utils/download.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 文件下载工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 从响应头中提取文件名
|
||||
* @param contentDisposition Content-Disposition 响应头
|
||||
* @returns 解码后的文件名
|
||||
*/
|
||||
function extractFileName(contentDisposition: string): string {
|
||||
if (!contentDisposition) {
|
||||
return `download_${Date.now()}`;
|
||||
}
|
||||
|
||||
// 尝试从 filename*=UTF-8'' 格式中提取
|
||||
const filenameRegex = /filename\*=UTF-8''(.+)/;
|
||||
const matches = filenameRegex.exec(contentDisposition);
|
||||
if (matches && matches[1]) {
|
||||
return decodeURIComponent(matches[1]);
|
||||
}
|
||||
|
||||
// 尝试从 filename= 格式中提取
|
||||
const fallbackRegex = /filename=([^;]+)/;
|
||||
const fallbackMatches = fallbackRegex.exec(contentDisposition);
|
||||
if (fallbackMatches && fallbackMatches[1]) {
|
||||
return decodeURI(fallbackMatches[1].replace(/"/g, ""));
|
||||
}
|
||||
|
||||
return `download_${Date.now()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param response Axios 响应对象
|
||||
* @param customFileName 自定义文件名(可选)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // 基础用法
|
||||
* const response = await UserAPI.export(queryParams);
|
||||
* downloadFile(response);
|
||||
*
|
||||
* // 自定义文件名
|
||||
* downloadFile(response, "用户列表.xlsx");
|
||||
* ```
|
||||
*/
|
||||
export function downloadFile(response: any, customFileName?: string): void {
|
||||
try {
|
||||
const fileData = response.data;
|
||||
const contentDisposition = response.headers["content-disposition"];
|
||||
const fileName = customFileName || extractFileName(contentDisposition);
|
||||
|
||||
// 创建 Blob 对象
|
||||
const blob = new Blob([fileData]);
|
||||
|
||||
// 创建下载链接
|
||||
const downloadUrl = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = downloadUrl;
|
||||
link.download = fileName;
|
||||
|
||||
// 触发下载
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
// 清理
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
} catch (error) {
|
||||
console.error("文件下载失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
86
src/utils/format.ts
Normal file
86
src/utils/format.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 数据格式化相关工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 格式化增长率
|
||||
* 保留两位小数,去掉末尾的 0,取绝对值
|
||||
*
|
||||
* @param growthRate 增长率(小数形式,如 0.15 表示 15%)
|
||||
* @returns 格式化后的增长率字符串
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* formatGrowthRate(0.1234); // "12.34%"
|
||||
* formatGrowthRate(0.1000); // "10%"
|
||||
* formatGrowthRate(0); // "-"
|
||||
* formatGrowthRate(-0.05); // "5%"(取绝对值)
|
||||
* ```
|
||||
*/
|
||||
export function formatGrowthRate(growthRate: number): string {
|
||||
if (growthRate === 0) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
const formattedRate = Math.abs(growthRate * 100)
|
||||
.toFixed(2)
|
||||
.replace(/\.?0+$/, "");
|
||||
|
||||
return formattedRate + "%";
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
* @param bytes 字节数
|
||||
* @param decimals 保留小数位数,默认 2
|
||||
* @returns 格式化后的文件大小字符串
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* formatFileSize(1024); // "1 KB"
|
||||
* formatFileSize(1048576); // "1 MB"
|
||||
* formatFileSize(1234567); // "1.18 MB"
|
||||
* ```
|
||||
*/
|
||||
export function formatFileSize(bytes: number, decimals: number = 2): string {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + " " + sizes[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字,添加千分位分隔符
|
||||
* @param num 数字
|
||||
* @returns 格式化后的字符串
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* formatNumber(1234567); // "1,234,567"
|
||||
* formatNumber(1234567.89); // "1,234,567.89"
|
||||
* ```
|
||||
*/
|
||||
export function formatNumber(num: number): string {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化金额(人民币)
|
||||
* @param amount 金额
|
||||
* @param decimals 保留小数位数,默认 2
|
||||
* @returns 格式化后的金额字符串
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* formatCurrency(1234567); // "¥1,234,567.00"
|
||||
* formatCurrency(1234567.8); // "¥1,234,567.80"
|
||||
* formatCurrency(1234567, 0); // "¥1,234,567"
|
||||
* ```
|
||||
*/
|
||||
export function formatCurrency(amount: number, decimals: number = 2): string {
|
||||
const formatted = amount.toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
return "¥" + formatted;
|
||||
}
|
||||
@@ -1,58 +1,26 @@
|
||||
/**
|
||||
* Check if an element has a class
|
||||
* @param {HTMLElement} ele
|
||||
* @param {string} cls
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function hasClass(ele: HTMLElement, cls: string) {
|
||||
return !!ele.className.match(new RegExp("(\\s|^)" + cls + "(\\s|$)"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add class to element
|
||||
* @param {HTMLElement} ele
|
||||
* @param {string} cls
|
||||
*/
|
||||
export function addClass(ele: HTMLElement, cls: string) {
|
||||
if (!hasClass(ele, cls)) ele.className += " " + cls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove class from element
|
||||
* @param {HTMLElement} ele
|
||||
* @param {string} cls
|
||||
*/
|
||||
export function removeClass(ele: HTMLElement, cls: string) {
|
||||
if (hasClass(ele, cls)) {
|
||||
const reg = new RegExp("(\\s|^)" + cls + "(\\s|$)");
|
||||
ele.className = ele.className.replace(reg, " ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是外部链接
|
||||
* 工具函数统一导出
|
||||
*
|
||||
* @param {string} path
|
||||
* @returns {Boolean}
|
||||
* 本文件作为 barrel export,统一管理所有工具函数的导出
|
||||
* 各类工具函数按功能分类存放在不同文件中:
|
||||
* - dom.ts: DOM 操作相关
|
||||
* - validate.ts: 数据验证相关
|
||||
* - format.ts: 数据格式化相关
|
||||
* - download.ts: 文件下载相关
|
||||
* - auth.ts: 权限认证相关
|
||||
* - storage.ts: 本地存储相关
|
||||
* - request.ts: 网络请求相关
|
||||
* - theme.ts: 主题相关
|
||||
*/
|
||||
export function isExternal(path: string) {
|
||||
const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path);
|
||||
return isExternal;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化增长率,保留两位小数 ,并且去掉末尾的0 取绝对值
|
||||
*
|
||||
* @param growthRate
|
||||
* @returns
|
||||
*/
|
||||
export function formatGrowthRate(growthRate: number) {
|
||||
if (growthRate === 0) {
|
||||
return "-";
|
||||
}
|
||||
// DOM 操作
|
||||
export { hasClass, addClass, removeClass } from "./dom";
|
||||
|
||||
const formattedRate = Math.abs(growthRate * 100)
|
||||
.toFixed(2)
|
||||
.replace(/\.?0+$/, "");
|
||||
return formattedRate + "%";
|
||||
}
|
||||
// 数据验证
|
||||
export { isExternal, isValidURL, isEmail, isMobile } from "./validate";
|
||||
|
||||
// 数据格式化
|
||||
export { formatGrowthRate, formatFileSize, formatNumber, formatCurrency } from "./format";
|
||||
|
||||
// 文件下载
|
||||
export { downloadFile } from "./download";
|
||||
|
||||
59
src/utils/validate.ts
Normal file
59
src/utils/validate.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 数据验证相关工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 判断是否是外部链接
|
||||
* @param path 路径字符串
|
||||
* @returns 是否是外部链接
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* isExternal('https://example.com'); // true
|
||||
* isExternal('/dashboard'); // false
|
||||
* isExternal('mailto:admin@example.com'); // true
|
||||
* ```
|
||||
*/
|
||||
export function isExternal(path: string): boolean {
|
||||
return /^(https?:|http?:|mailto:|tel:)/.test(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是有效的 URL
|
||||
* @param url URL 字符串
|
||||
* @returns 是否是有效 URL
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* isValidURL('https://example.com'); // true
|
||||
* isValidURL('not a url'); // false
|
||||
* ```
|
||||
*/
|
||||
export function isValidURL(url: string): boolean {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是邮箱地址
|
||||
* @param email 邮箱字符串
|
||||
* @returns 是否是有效邮箱
|
||||
*/
|
||||
export function isEmail(email: string): boolean {
|
||||
const pattern = /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/;
|
||||
return pattern.test(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是手机号码(中国大陆)
|
||||
* @param mobile 手机号字符串
|
||||
* @returns 是否是有效手机号
|
||||
*/
|
||||
export function isMobile(mobile: string): boolean {
|
||||
const pattern = /^1[3|4|5|6|7|8|9][0-9]\d{8}$/;
|
||||
return pattern.test(mobile);
|
||||
}
|
||||
Reference in New Issue
Block a user