/** * 文件定义:数据处理方法 * 包含:1、数据类型检查。2、数据转换。3、日期处理。4、数据校验。5、数据过滤与重组。6、特殊场景处理 */ import * as R from "ramda"; // =============== 数据转换 =============== /** * 数字格式化 * @param num 数字 * @param decimals 小数位数 * @param thousandsSeparator 千位分隔符 * @param decimalSeparator 小数分隔符 */ export const formatNumber = ( num: number | string, decimals = 0, thousandsSeparator = ",", decimalSeparator = "." ): string => { const number = Number(num); if (isNaN(number)) return ""; const fixed = number.toFixed(decimals); const parts = fixed.split("."); // 添加千位分隔符 if (parts[0]) { parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparator); } return parts.join(decimalSeparator); }; /** * 货币格式化 * @param amount 金额 * @param currency 货币符号 * @param decimals 小数位数 */ export const formatCurrency = ( amount: number | string, currency = "¥", decimals = 2 ): string => { const formatted = formatNumber(amount, decimals); return `${currency}${formatted}`; }; /** * 将对象的所有值转换为字符串 * @param {Record} obj - 要转换的对象 * @returns {Record} 转换后的对象 */ export const objectToString = R.map(String); /** * 将数组转换为对象,使用指定的 key * @param {string} key - 要转换的 key * @param {Record[]} array - 要转换的数组 * @returns {Record>} 转换后的对象 */ export const arrayToObject = R.curry( (key: string, array: Record[]) => R.indexBy(R.prop(key), array) ) as >( key: string, array: T[] ) => Record; /** * 深度扁平化对象(建议深度嵌套的对象使用) * @param {Record} obj - 要扁平化的对象 * @returns {Record} 扁平化后的对象 */ export const flattenObject = ( obj: Record ): Record => { const result: Record = {}; const flatten = (obj: Record, prefix: string = "") => { for (const key in obj) { const value = obj[key]; const newKey = prefix ? `${prefix}.${key}` : key; if (value && typeof value === "object" && !Array.isArray(value)) { flatten(value, newKey); } else { result[newKey] = value; } } }; flatten(obj); return result; }; /** * 验证字符串是否符合正则表达式 * @param {RegExp} pattern - 要验证的正则表达式 * @param {string} str - 要验证的字符串 * @returns {boolean} 如果字符串符合正则表达式,则返回 true,否则返回 false */ export const matchesPattern = R.curry((pattern: RegExp, str: string) => R.test(pattern, str) ) as (pattern: T, str: string) => boolean; /** * 验证对象是否包含所有必需的键 * @param {Record} obj - 要验证的对象 * @param {string[]} requiredKeys - 要验证的键 * @returns {boolean} 如果对象包含所有必需的键,则返回 true,否则返回 false */ export const hasRequiredKeys = R.curry( (obj: Record, requiredKeys: string[]) => R.all(R.flip(R.has)(obj), requiredKeys) ) as { (obj: Record): (requiredKeys: string[]) => boolean; (obj: Record, requiredKeys: string[]): boolean; }; // ... existing code ... /** * 验证值是否在指定范围内 * @param {number} min - 最小值 * @param {number} max - 最大值 * @param {number} value - 要验证的值 * @returns {boolean} 如果值在指定范围内,则返回 true,否则返回 false */ export const isInRange = R.curry((min: number, max: number, value: number) => R.both(R.gte(R.__, min), R.lte(R.__, max))(value) ) as (min: T, max: T, value: T) => boolean; // =============== 数据过滤与重组 =============== /** * 根据条件过滤对象的属性 * @param {Function} predicate - 要过滤的条件 * @param {Record} obj - 要过滤的对象 * @returns {Record} 过滤后的对象 */ export const filterObject = R.curry( >( predicate: (value: T[keyof T]) => boolean, obj: T ) => Object.fromEntries( Object.entries(obj).filter(([_, value]) => predicate(value)) ) ) as { >(predicate: (value: T[keyof T]) => boolean): ( obj: T ) => Partial; >( predicate: (value: T[keyof T]) => boolean, obj: T ): Partial; }; /** * 按照指定的键对数组进行分组 * @param {string} key - 要分组的键 * @param {Record[]} array - 要分组的数组 * @returns {Record[]>} 分组后的对象 */ export const groupByKey = R.curry( >(key: string, array: T[]) => R.groupBy(R.prop(key), array) ) as >( key: string, array: T[] ) => Record; /** * 从对象数组中提取指定的键值 * @param {string[]} path - 要提取的键 * @param {Record[]} list - 要提取的对象数组 * @returns {Record[]} 提取后的对象数组 */ export const pluckDeep = R.curry((path: string[], list: T[]) => R.map(R.path(path), list) ) as >(path: string[], list: T[]) => T[]; /** * 对嵌套数组进行扁平化和去重 * @param {any[]} array - 要扁平化和去重的数组 * @returns {any[]} 扁平化和去重后的数组 */ export const flattenAndUniq = R.pipe(R.flatten, R.uniq) as ( array: T[] ) => T[]; // =============== 数据映射 =============== type MapperOption = { inherit?: string[]; // 继承字段 deep?: boolean; // 深度映射 ignore?: string[]; // 忽略字段 }; type MapperType = [string, string][] | Record; type DataType = Record | Record[]; /** * 对象/数组映射,根据映射表,将数组或对象映射为新的对象和数组 * 支持继承/过滤,通过参数继承/过滤,选取自己需要的数据 * 增加异常处理,如果值不存在,则抛出异常。 * 返回新的对象/数组 */ export const mapData = ( mapper: MapperType, data: DataType, options: MapperOption = { deep: true } ): DataType => { const { inherit, deep, ignore } = options; // 验证 inherit 和 ignore 不能同时使用 if (inherit && ignore) { throw new Error("inherit 和 ignore 选项不能同时使用"); } // 将 mapper 转换为对象形式 const mapperObj = Array.isArray(mapper) ? mapper.reduce>( (acc, [key, value]) => ({ ...acc, [key]: value }), {} ) : mapper; // 处理数组 if (Array.isArray(data)) { return data.map( (item) => mapData(mapperObj, item, options) as Record ); } // 处理对象 if (typeof data === "object" && data !== null) { // 根据选项过滤 mapper let finalMapper = { ...mapperObj }; if (inherit) { finalMapper = Object.entries(mapperObj) .filter(([key]) => inherit.includes(key)) .reduce>( (acc, [key, value]) => ({ ...acc, [key]: value }), {} ); } else if (ignore) { finalMapper = Object.entries(mapperObj) .filter(([key]) => !ignore.includes(key)) .reduce>( (acc, [key, value]) => ({ ...acc, [key]: value }), {} ); } return Object.entries(finalMapper).reduce>( (result, [sourceKey, targetKey]) => { // 处理嵌套路径 const value = sourceKey.split(".").reduce((obj, key) => { if (obj === undefined || obj === null) { throw new Error(`映射键 "${sourceKey}" 不存在于源数据中`); } return (obj as Record)[key]; }, data); // 处理值不存在的情况 if (value === undefined) { throw new Error(`映射键 "${sourceKey}" 的值不存在`); } // 处理深度映射 if (deep && typeof value === "object" && value !== null) { const nestedMapper = Object.entries(mapperObj) .filter(([key]) => key.startsWith(`${sourceKey}.`)) .reduce>( (acc, [key, val]) => ({ ...acc, [key.slice(sourceKey.length + 1)]: val, }), {} ); if (Object.keys(nestedMapper).length > 0) { return { ...result, [targetKey]: mapData( nestedMapper, value as Record, options ), }; } } // 处理嵌套目标路径 const targetPath = (targetKey as string).split("."); const finalKey = targetPath.pop()!; const targetObj = targetPath.reduce>( (obj, key) => { if (!(key in obj)) { obj[key] = {}; } return obj[key] as Record; }, result ); if (finalKey && targetObj) { targetObj[finalKey] = value; } return result; }, {} ); } return data; }; /** * @description 生成映射表,将所有字段转换为小驼峰 * @param {Record} obj - 要转换的对象 * @returns {Record} 转换后的对象 */ export const generateMapper = (obj: Record) => { return Object.entries(obj).map(([key, value]) => [ key.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()), value, ]); }; /** * 将对象转换为查询字符串 * @param {Record} obj - 要转换的对象 * @returns {string} 转换后的查询字符串 */ export const objectToQueryString = (obj: Record) => { return Object.entries(obj) .map( ([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}` ) .join("&"); }; /** * 深度合并两个对象 * @param {Record} target - 目标对象 * @param {Record} source - 源对象 * @returns {Record} 合并后的对象 */ export const deepMerge = >( target: T, source: T, isMergeArray: boolean = true ): T => { const result = { ...target } as T; for (const key in source) { if (source.hasOwnProperty(key)) { const sourceValue = source[key]; const targetValue = target[key]; if (Array.isArray(sourceValue) && Array.isArray(targetValue)) { // 如果是数组,则合并数组 result[key] = ( isMergeArray ? [...targetValue, ...sourceValue] : sourceValue ) as T[Extract]; } else if (isObject(sourceValue) && isObject(targetValue)) { // 如果是对象,则递归合并 result[key] = deepMerge(targetValue, sourceValue) as T[Extract< keyof T, string >]; } else { // 其他情况直接覆盖 result[key] = sourceValue as T[Extract]; } } } return result; }; /** * 判断是否为对象 * @param {any} value - 要判断的值 * @returns {boolean} 是否为对象 */ const isObject = (value: any): boolean => { return value !== null && typeof value === "object" && !Array.isArray(value); }; /** * @description 清理对象前后字符串 * @param {Record} obj - 要清理的对象 * @returns {Record} 清理后的对象 */ export const trimObject = (obj: Record) => { return Object.entries(obj).reduce>( (acc, [key, value]) => { acc[key.trim()] = value.trim(); return acc; }, {} ); }; /** * 深拷贝对象(简单版) * @param {any} obj - 要拷贝的对象 * @returns {any} 拷贝后的对象 */ export const deepClone = (obj: T): T => { return JSON.parse(JSON.stringify(obj)); };