前端常用工具函数

30 阅读5分钟

后续会持续记录。

1.获取指定cookie

/**
 * @description 从cookie中获取指定值
 * @param {string} name cookie名称
 * @returns {string | null} cookie值,如果不存在则返回null
 */
export const getCookie = (name: string): string | null => {
    if (typeof document === 'undefined') return null;

    const cookies = document.cookie.split(';');
    for (const cookie of cookies) {
        const [cookieName, cookieValue] = cookie.trim().split('=');
        if (cookieName === name) {
            return decodeURIComponent(cookieValue || '');
        }
    }
    return null;
};

2.存储cookie值

/**
 * @description 设置cookie值
 * @param {string} name cookie名称
 * @param {string} value cookie值
 * @param {object} options 可选配置项
 * @param {Date | number} options.expires 过期时间
 * @param {string} options.path 路径
 * @param {string} options.domain 域名
 * @param {boolean} options.secure 是否只在HTTPS下传输
 * @param {string} options.sameSite 同站策略
 */
export const setCookie = (
    name: string,
    value: string,
    options: {
        /** 过期时间 */
        expires?: Date | number;
        /** 路径 */
        path?: string;
        /** 域名 */
        domain?: string;
        /** 是否只在HTTPS下传输 */
        secure?: boolean;
        /** 同站策略 */
        sameSite?: 'strict' | 'lax' | 'none';
    } = {},
): void => {
    if (typeof document === 'undefined') return;

    const { expires, path = '/', domain, secure = false, sameSite = 'lax' } = options || {};

    let cookieString = `${name}=${encodeURIComponent(value)}`;

    if (expires) {
        if (typeof expires === 'number') {
            const date = new Date();
            const days = expires * 24 * 60 * 60 * 1000;
            date.setTime(date.getTime() + days);
            cookieString += `; expires=${date.toUTCString()}`;
        } else {
            cookieString += `; expires=${expires.toUTCString()}`;
        }
    }

    cookieString += `; path=${path}`;

    if (domain) {
        cookieString += `; domain=${domain}`;
    }

    if (secure) {
        cookieString += '; secure';
    }

    cookieString += `; sameSite=${sameSite}`;

    document.cookie = cookieString;
};

3.获取两个日期间隔多少天

/**
 * @description 计算两个日期之间相差的天数
 * @param {Date | string | number} date1 第一个日期
 * @param {Date | string | number} date2 第二个日期
 * @param {boolean} absolute 是否返回绝对值,默认true
 * @returns 相差的天数
 */
export const getDaysDifference = (date1: Date | string | number, date2: Date | string | number, absolute = true): number => {
    const d1 = new Date(date1);
    const d2 = new Date(date2);

    if (isNaN(d1.getTime()) || isNaN(d2.getTime())) {
        throw new Error('Invalid date provided');
    }

    const timeDiff = d2.getTime() - d1.getTime();
    const daysDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24));

    return absolute ? Math.abs(daysDiff) : daysDiff;
};

4.获取url地址上的参数值

/**
 * @description 获取URL参数值
 * @param {string} paramName 参数名,不传则返回所有参数
 * @param {string} url 目标URL,不传则使用当前页面URL
 * @returns {string | Record<string, string> | null} 指定参数值或所有参数对象
 */
export const getUrlParams = (paramName?: string, url?: string): string | Record<string, string> | null => {
    if (!url && typeof window === 'undefined') return null;

    const targetUrl = url || window.location.href;

    // 提取查询字符串部分
    const queryIndex = targetUrl.indexOf('?');
    if (queryIndex === -1) {
        return paramName ? null : {};
    }

    const queryString = targetUrl.substring(queryIndex + 1);
    if (!queryString) {
        return paramName ? null : {};
    }

    // 解析查询参数
    const params: Record<string, string> = {};
    const pairs = queryString.split('&');

    for (const pair of pairs) {
        const equalIndex = pair.indexOf('=');
        if (equalIndex === -1) {
            // 没有值的参数,如 ?flag
            const key = decodeURIComponent(pair);
            params[key] = '';
        } else {
            const key = decodeURIComponent(pair.substring(0, equalIndex));
            const value = decodeURIComponent(pair.substring(equalIndex + 1));
            params[key] = value;
        }
    }

    // 如果指定了参数名,返回对应值
    if (paramName) {
        return params[paramName] || null;
    }

    // 返回所有参数
    return params;
};

5.数组去重

/**
 * @description 数组对象根据指定key的值做去重
 * @param {T[]} array 目标数组
 * @param {K} key 去重的key
 * @returns {T[]} 去重后的数组
 */
export const uniqueByKey = <T extends Record<string, string | number | null | undefined>, K extends keyof T>(array: T[], key: K): T[] => {
    if (!Array.isArray(array) || array.length === 0) {
        return [];
    }

    const seen = new Set();
    return array.filter((item) => {
        const value = item[key];
        if (seen.has(value)) {
            return false;
        }
        seen.add(value);
        return true;
    });
};

6.数组排序

/**
 * @description 数组对象根据指定key的值做排序
 * @param {T[]} array 目标数组
 * @param {K} key 排序的key
 * @param {'asc' | 'desc'} order 排序方式,'asc'为正序,'desc'为倒序,默认'asc'
 * @returns {T[]} 排序后的数组
 */
export const sortByKey = <T extends Record<string, string | number | null | undefined>, K extends keyof T>(
    array: T[],
    key: K,
    order: 'asc' | 'desc' = 'asc',
): T[] => {
    if (!Array.isArray(array) || array.length === 0) {
        return [];
    }

    return [...array].sort((a, b) => {
        const aValue = a[key];
        const bValue = b[key];

        // 处理null/undefined值
        if (aValue == null && bValue == null) return 0;
        if (aValue == null) return order === 'asc' ? -1 : 1;
        if (bValue == null) return order === 'asc' ? 1 : -1;

        // 数字类型比较
        if (typeof aValue === 'number' && typeof bValue === 'number') {
            return order === 'asc' ? aValue - bValue : bValue - aValue;
        }

        // 字符串类型比较
        if (typeof aValue === 'string' && typeof bValue === 'string') {
            return order === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
        }

        // 其他情况转换为字符串比较
        const aStr = String(aValue);
        const bStr = String(bValue);
        return order === 'asc' ? aStr.localeCompare(bStr) : bStr.localeCompare(aStr);
    });
};

7.树接口平铺

/**
 * @description 树结构转换配置
 */
interface TreeConfig<T, R = T> {
    /** 父级ID字段名 */
    parentId: keyof T;
    /** 自身ID字段名 */
    id: keyof T;
    /** 子节点字段名 */
    children: keyof T;
    /** 格式化函数,用于处理每个节点 */
    format?: (item: T) => R;
}
/**
 * @description 将树结构数组平铺为一维数组
 * @param {T[]} treeList 树结构数组
 * @param {TreeConfig<T>} config 配置项
 * @returns {R[]} 平铺后的一维数组
 */
export const flattenTree = <T extends Record<string, any>, R = T>(treeList: T[], config: TreeConfig<T, R>): R[] => {
    if (!Array.isArray(treeList) || treeList.length === 0) {
        return [];
    }

    const { children, format } = config;
    const result: R[] = [];

    const traverse = (nodes: T[]) => {
        for (const node of nodes) {
            // 处理当前节点
            const processedNode = format ? format(node) : (node as unknown as R);
            result.push(processedNode);

            // 递归处理子节点
            const childNodes = node[children];
            if (Array.isArray(childNodes) && childNodes.length > 0) {
                traverse(childNodes);
            }
        }
    };

    traverse(treeList);
    return result;
};

8.一级list列表转为树结构列表

/**
 * @description 将平铺的一维数组转换为树结构
 * @param {T[]} flatList 平铺的一维数组
 * @param {TreeConfig<T>} config 配置项
 * @returns {any[]} 树结构数组
 */
export const buildTree = <T extends Record<string, any>, R = T>(flatList: T[], config: TreeConfig<T, R>): R[] => {
    if (!Array.isArray(flatList) || flatList.length === 0) {
        return [];
    }

    const { parentId, id, children, format } = config;
    const map = new Map<any, R>();
    const roots: R[] = [];

    // 创建节点映射
    for (const item of flatList) {
        const processedItem = format ? format(item) : (item as unknown as R);
        const nodeId = item[id];
        const nodeParentId = item[parentId];

        // 初始化节点
        if (!map.has(nodeId)) {
            const newNode = { ...processedItem, [children]: [] } as R;
            map.set(nodeId, newNode);
        } else {
            // 合并已存在的节点
            const existingNode = map.get(nodeId);
            if (!existingNode) continue;
            const mergedNode = { ...existingNode, ...processedItem } as R;
            map.set(nodeId, mergedNode);
        }

        const node = map.get(nodeId);
        if (!node) continue;

        // 处理父子关系
        if (nodeParentId == null || nodeParentId === '' || nodeParentId === 0) {
            // 根节点
            roots.push(node);
        } else {
            // 子节点
            if (!map.has(nodeParentId)) {
                const parentNode = { [children]: [] } as R;
                map.set(nodeParentId, parentNode);
            }
            const parentNode = map.get(nodeParentId);
            if (!parentNode) continue;
            (parentNode as any)[children].push(node);
        }
    }

    return roots;
};

9.单位换算

/**
 * @description 单位区间配置接口
 */
interface UnitRange {
    /** 最小值(包含) */
    min: number;
    /** 最大值(不包含) */
    max: number;
    /** 单位 */
    unit: string;
    /** 除数,用于转换数值 */
    divisor: number;
    /** 小数位数 */
    decimals?: number;
}

/**
 * @description 单位转换结果接口
 */
interface UnitConvertResult {
    /** 原始值 */
    originalValue: number;
    /** 转换后的单位 */
    unit: string;
    /** 转换后的数值 */
    convertedValue: number;
}

/**
 * @description 单位转换函数
 * @param {number} value 数值
 * @param {UnitRange[]} unitRanges 单位对应数值区间配置
 * @param {string} defaultUnit 默认单位
 * @returns {UnitConvertResult} 转换结果
 */
export const convertUnit = (value: number, unitRanges: UnitRange[], defaultUnit: string): UnitConvertResult => {
    if (!Array.isArray(unitRanges) || unitRanges.length === 0) {
        throw new Error('unitRanges must be a non-empty array');
    }

    const absValue = Math.abs(value);
    const sign = value < 0 ? -1 : 1;

    // 查找匹配的单位区间
    let matchedRange: UnitRange | null = null;

    for (const range of unitRanges) {
        if (absValue >= range.min && absValue < range.max) {
            matchedRange = range;
            break;
        }
    }

    // 如果没有找到匹配的区间,使用默认单位
    if (!matchedRange) {
        return {
            originalValue: value,
            unit: defaultUnit,
            convertedValue: value,
        };
    }

    // 计算转换后的数值
    const convertedAbsValue = absValue / matchedRange.divisor;
    const decimals = matchedRange.decimals ?? 2;
    const formattedValue = Number(convertedAbsValue.toFixed(decimals));
    const finalValue = formattedValue * sign;

    return {
        originalValue: value,
        unit: matchedRange.unit,
        convertedValue: finalValue,
    };
};


// 示例
const storageRanges: UnitRange[] = [
    { min: 0, max: 1024, unit: 'B', divisor: 1, decimals: 0 },
    { min: 1024, max: 1048576, unit: 'KB', divisor: 1024, decimals: 1 },
    { min: 1048576, max: 1073741824, unit: 'MB', divisor: 1048576, decimals: 2 },
    { min: 1073741824, max: Infinity, unit: 'GB', divisor: 1073741824, decimals: 2 }
];

console.log(convertUnit(512, storageRanges, 'B'));
// { originalValue: 512, unit: 'B', convertedValue: 512 }

console.log(convertUnit(2048, storageRanges, 'B'));
// { originalValue: 2048, unit: 'KB', convertedValue: 2 }

console.log(convertUnit(5242880, storageRanges, 'B'));
// { originalValue: 5242880, unit: 'MB', convertedValue: 5 }