mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-18 04:02:02 +08:00
【新增】私有证书
This commit is contained in:
@@ -1,130 +1,142 @@
|
||||
{
|
||||
"name": "@baota/utils",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "./dist/browser.cjs",
|
||||
"module": "./dist/browser.mjs",
|
||||
"types": "./dist/browser.d.ts",
|
||||
"files": [
|
||||
"dist/**",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite build --watch",
|
||||
"test": "vitest"
|
||||
},
|
||||
"exports": {
|
||||
"./browser": {
|
||||
"development": {
|
||||
"types": "./src/browser.ts",
|
||||
"import": "./src/browser.ts",
|
||||
"require": "./src/browser.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/browser.d.ts",
|
||||
"import": "./dist/browser.mjs",
|
||||
"require": "./dist/browser.cjs"
|
||||
}
|
||||
},
|
||||
"./business": {
|
||||
"development": {
|
||||
"types": "./src/business.ts",
|
||||
"import": "./src/business.ts",
|
||||
"require": "./src/business.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/business.d.ts",
|
||||
"import": "./dist/business.mjs",
|
||||
"require": "./dist/business.cjs"
|
||||
}
|
||||
},
|
||||
"./data": {
|
||||
"development": {
|
||||
"types": "./src/data.ts",
|
||||
"import": "./src/data.ts",
|
||||
"require": "./src/data.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/data.d.ts",
|
||||
"import": "./dist/data.mjs",
|
||||
"require": "./dist/data.cjs"
|
||||
}
|
||||
},
|
||||
"./date": {
|
||||
"development": {
|
||||
"types": "./src/date.ts",
|
||||
"import": "./src/date.ts",
|
||||
"require": "./src/date.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/date.d.ts",
|
||||
"import": "./dist/date.mjs",
|
||||
"require": "./dist/date.cjs"
|
||||
}
|
||||
},
|
||||
"./encipher": {
|
||||
"development": {
|
||||
"types": "./src/encipher.ts",
|
||||
"import": "./src/encipher.ts",
|
||||
"require": "./src/encipher.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/encipher.d.ts",
|
||||
"import": "./dist/encipher.mjs",
|
||||
"require": "./dist/encipher.cjs"
|
||||
}
|
||||
},
|
||||
"./random": {
|
||||
"development": {
|
||||
"types": "./src/random.ts",
|
||||
"import": "./src/random.ts",
|
||||
"require": "./src/random.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/random.d.ts",
|
||||
"import": "./dist/random.mjs",
|
||||
"require": "./dist/random.cjs"
|
||||
}
|
||||
},
|
||||
"./string": {
|
||||
"development": {
|
||||
"types": "./src/string.ts",
|
||||
"import": "./src/string.ts",
|
||||
"require": "./src/string.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/string.d.ts",
|
||||
"import": "./dist/string.mjs",
|
||||
"require": "./dist/string.cjs"
|
||||
}
|
||||
},
|
||||
"./type": {
|
||||
"development": {
|
||||
"types": "./src/type.ts",
|
||||
"import": "./src/type.ts",
|
||||
"require": "./src/type.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/type.d.ts",
|
||||
"import": "./dist/type.mjs",
|
||||
"require": "./dist/type.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"jsencrypt": "^3.3.2",
|
||||
"md5": "^2.3.0",
|
||||
"ramda": "^0.30.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@baota/eslint": "workspace:*",
|
||||
"@baota/typescript": "workspace:*",
|
||||
"@baota/prettier": "workspace:*",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-dts": "^3.0.0"
|
||||
}
|
||||
"name": "@baota/utils",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "./dist/browser.cjs",
|
||||
"module": "./dist/browser.mjs",
|
||||
"types": "./dist/browser.d.ts",
|
||||
"files": [
|
||||
"dist/**",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite build --watch",
|
||||
"test": "vitest"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"development": {
|
||||
"types": "./src/index.ts",
|
||||
"import": "./src/index.ts",
|
||||
"require": "./src/index.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"./browser": {
|
||||
"development": {
|
||||
"types": "./src/browser.ts",
|
||||
"import": "./src/browser.ts",
|
||||
"require": "./src/browser.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/browser.d.ts",
|
||||
"import": "./dist/browser.mjs",
|
||||
"require": "./dist/browser.cjs"
|
||||
}
|
||||
},
|
||||
"./business": {
|
||||
"development": {
|
||||
"types": "./src/business.ts",
|
||||
"import": "./src/business.ts",
|
||||
"require": "./src/business.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/business.d.ts",
|
||||
"import": "./dist/business.mjs",
|
||||
"require": "./dist/business.cjs"
|
||||
}
|
||||
},
|
||||
"./data": {
|
||||
"development": {
|
||||
"types": "./src/data.ts",
|
||||
"import": "./src/data.ts",
|
||||
"require": "./src/data.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/data.d.ts",
|
||||
"import": "./dist/data.mjs",
|
||||
"require": "./dist/data.cjs"
|
||||
}
|
||||
},
|
||||
"./date": {
|
||||
"development": {
|
||||
"types": "./src/date.ts",
|
||||
"import": "./src/date.ts",
|
||||
"require": "./src/date.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/date.d.ts",
|
||||
"import": "./dist/date.mjs",
|
||||
"require": "./dist/date.cjs"
|
||||
}
|
||||
},
|
||||
"./encipher": {
|
||||
"development": {
|
||||
"types": "./src/encipher.ts",
|
||||
"import": "./src/encipher.ts",
|
||||
"require": "./src/encipher.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/encipher.d.ts",
|
||||
"import": "./dist/encipher.mjs",
|
||||
"require": "./dist/encipher.cjs"
|
||||
}
|
||||
},
|
||||
"./random": {
|
||||
"development": {
|
||||
"types": "./src/random.ts",
|
||||
"import": "./src/random.ts",
|
||||
"require": "./src/random.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/random.d.ts",
|
||||
"import": "./dist/random.mjs",
|
||||
"require": "./dist/random.cjs"
|
||||
}
|
||||
},
|
||||
"./string": {
|
||||
"development": {
|
||||
"types": "./src/string.ts",
|
||||
"import": "./src/string.ts",
|
||||
"require": "./src/string.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/string.d.ts",
|
||||
"import": "./dist/string.mjs",
|
||||
"require": "./dist/string.cjs"
|
||||
}
|
||||
},
|
||||
"./type": {
|
||||
"development": {
|
||||
"types": "./src/type.ts",
|
||||
"import": "./src/type.ts",
|
||||
"require": "./src/type.ts"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/type.d.ts",
|
||||
"import": "./dist/type.mjs",
|
||||
"require": "./dist/type.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"jsencrypt": "^3.3.2",
|
||||
"md5": "^2.3.0",
|
||||
"ramda": "^0.30.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@baota/eslint": "workspace:*",
|
||||
"@baota/typescript": "workspace:*",
|
||||
"@baota/prettier": "workspace:*",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-dts": "^3.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,16 +3,58 @@
|
||||
* 包含:1、数据类型检查。2、数据转换。3、日期处理。4、数据校验。5、数据过滤与重组。6、特殊场景处理
|
||||
*/
|
||||
|
||||
import * as R from 'ramda'
|
||||
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<string, any>} obj - 要转换的对象
|
||||
* @returns {Record<string, string>} 转换后的对象
|
||||
*/
|
||||
export const objectToString = R.map(String)
|
||||
export const objectToString = R.map(String);
|
||||
|
||||
/**
|
||||
* 将数组转换为对象,使用指定的 key
|
||||
@@ -20,36 +62,38 @@ export const objectToString = R.map(String)
|
||||
* @param {Record<string, any>[]} array - 要转换的数组
|
||||
* @returns {Record<string, Record<string, any>>} 转换后的对象
|
||||
*/
|
||||
export const arrayToObject = R.curry((key: string, array: Record<string, any>[]) => R.indexBy(R.prop(key), array)) as <
|
||||
T extends Record<string, any>,
|
||||
>(
|
||||
key: string,
|
||||
array: T[],
|
||||
) => Record<string, T>
|
||||
export const arrayToObject = R.curry(
|
||||
(key: string, array: Record<string, any>[]) => R.indexBy(R.prop(key), array)
|
||||
) as <T extends Record<string, any>>(
|
||||
key: string,
|
||||
array: T[]
|
||||
) => Record<string, T>;
|
||||
|
||||
/**
|
||||
* 深度扁平化对象(建议深度嵌套的对象使用)
|
||||
* @param {Record<string, any>} obj - 要扁平化的对象
|
||||
* @returns {Record<string, any>} 扁平化后的对象
|
||||
*/
|
||||
export const flattenObject = (obj: Record<string, unknown>): Record<string, unknown> => {
|
||||
const result: Record<string, unknown> = {}
|
||||
export const flattenObject = (
|
||||
obj: Record<string, unknown>
|
||||
): Record<string, unknown> => {
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
const flatten = (obj: Record<string, any>, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
const flatten = (obj: Record<string, any>, 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
|
||||
}
|
||||
flatten(obj);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 验证字符串是否符合正则表达式
|
||||
@@ -57,10 +101,9 @@ export const flattenObject = (obj: Record<string, unknown>): Record<string, unkn
|
||||
* @param {string} str - 要验证的字符串
|
||||
* @returns {boolean} 如果字符串符合正则表达式,则返回 true,否则返回 false
|
||||
*/
|
||||
export const matchesPattern = R.curry((pattern: RegExp, str: string) => R.test(pattern, str)) as <T extends RegExp>(
|
||||
pattern: T,
|
||||
str: string,
|
||||
) => boolean
|
||||
export const matchesPattern = R.curry((pattern: RegExp, str: string) =>
|
||||
R.test(pattern, str)
|
||||
) as <T extends RegExp>(pattern: T, str: string) => boolean;
|
||||
|
||||
/**
|
||||
* 验证对象是否包含所有必需的键
|
||||
@@ -68,12 +111,13 @@ export const matchesPattern = R.curry((pattern: RegExp, str: string) => R.test(p
|
||||
* @param {string[]} requiredKeys - 要验证的键
|
||||
* @returns {boolean} 如果对象包含所有必需的键,则返回 true,否则返回 false
|
||||
*/
|
||||
export const hasRequiredKeys = R.curry((obj: Record<string, unknown>, requiredKeys: string[]) =>
|
||||
R.all(R.flip(R.has)(obj), requiredKeys),
|
||||
export const hasRequiredKeys = R.curry(
|
||||
(obj: Record<string, unknown>, requiredKeys: string[]) =>
|
||||
R.all(R.flip(R.has)(obj), requiredKeys)
|
||||
) as {
|
||||
(obj: Record<string, unknown>): (requiredKeys: string[]) => boolean
|
||||
(obj: Record<string, unknown>, requiredKeys: string[]): boolean
|
||||
}
|
||||
(obj: Record<string, unknown>): (requiredKeys: string[]) => boolean;
|
||||
(obj: Record<string, unknown>, requiredKeys: string[]): boolean;
|
||||
};
|
||||
|
||||
// ... existing code ...
|
||||
/**
|
||||
@@ -84,8 +128,8 @@ export const hasRequiredKeys = R.curry((obj: Record<string, unknown>, requiredKe
|
||||
* @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 <T extends number>(min: T, max: T, value: T) => boolean
|
||||
R.both(R.gte(R.__, min), R.lte(R.__, max))(value)
|
||||
) as <T extends number>(min: T, max: T, value: T) => boolean;
|
||||
|
||||
// =============== 数据过滤与重组 ===============
|
||||
|
||||
@@ -96,12 +140,22 @@ export const isInRange = R.curry((min: number, max: number, value: number) =>
|
||||
* @returns {Record<string, any>} 过滤后的对象
|
||||
*/
|
||||
export const filterObject = R.curry(
|
||||
<T extends Record<string, any>>(predicate: (value: T[keyof T]) => boolean, obj: T) =>
|
||||
Object.fromEntries(Object.entries(obj).filter(([_, value]) => predicate(value))),
|
||||
<T extends Record<string, any>>(
|
||||
predicate: (value: T[keyof T]) => boolean,
|
||||
obj: T
|
||||
) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(obj).filter(([_, value]) => predicate(value))
|
||||
)
|
||||
) as {
|
||||
<T extends Record<string, any>>(predicate: (value: T[keyof T]) => boolean): (obj: T) => Partial<T>
|
||||
<T extends Record<string, any>>(predicate: (value: T[keyof T]) => boolean, obj: T): Partial<T>
|
||||
}
|
||||
<T extends Record<string, any>>(predicate: (value: T[keyof T]) => boolean): (
|
||||
obj: T
|
||||
) => Partial<T>;
|
||||
<T extends Record<string, any>>(
|
||||
predicate: (value: T[keyof T]) => boolean,
|
||||
obj: T
|
||||
): Partial<T>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 按照指定的键对数组进行分组
|
||||
@@ -109,9 +163,13 @@ export const filterObject = R.curry(
|
||||
* @param {Record<string, any>[]} array - 要分组的数组
|
||||
* @returns {Record<string, Record<string, any>[]>} 分组后的对象
|
||||
*/
|
||||
export const groupByKey = R.curry(<T extends Record<string, any>>(key: string, array: T[]) =>
|
||||
R.groupBy(R.prop(key), array),
|
||||
) as <T extends Record<string, any>>(key: string, array: T[]) => Record<string, T[]>
|
||||
export const groupByKey = R.curry(
|
||||
<T extends Record<string, any>>(key: string, array: T[]) =>
|
||||
R.groupBy(R.prop(key), array)
|
||||
) as <T extends Record<string, any>>(
|
||||
key: string,
|
||||
array: T[]
|
||||
) => Record<string, T[]>;
|
||||
|
||||
/**
|
||||
* 从对象数组中提取指定的键值
|
||||
@@ -119,29 +177,28 @@ export const groupByKey = R.curry(<T extends Record<string, any>>(key: string, a
|
||||
* @param {Record<string, any>[]} list - 要提取的对象数组
|
||||
* @returns {Record<string, any>[]} 提取后的对象数组
|
||||
*/
|
||||
export const pluckDeep = R.curry(<T>(path: string[], list: T[]) => R.map(R.path(path), list)) as <
|
||||
T extends Record<string, any>,
|
||||
>(
|
||||
path: string[],
|
||||
list: T[],
|
||||
) => T[]
|
||||
export const pluckDeep = R.curry(<T>(path: string[], list: T[]) =>
|
||||
R.map(R.path(path), list)
|
||||
) as <T extends Record<string, any>>(path: string[], list: T[]) => T[];
|
||||
|
||||
/**
|
||||
* 对嵌套数组进行扁平化和去重
|
||||
* @param {any[]} array - 要扁平化和去重的数组
|
||||
* @returns {any[]} 扁平化和去重后的数组
|
||||
*/
|
||||
export const flattenAndUniq = R.pipe(R.flatten, R.uniq) as <T>(array: T[]) => T[]
|
||||
export const flattenAndUniq = R.pipe(R.flatten, R.uniq) as <T>(
|
||||
array: T[]
|
||||
) => T[];
|
||||
|
||||
// =============== 数据映射 ===============
|
||||
type MapperOption = {
|
||||
inherit?: string[] // 继承字段
|
||||
deep?: boolean // 深度映射
|
||||
ignore?: string[] // 忽略字段
|
||||
}
|
||||
inherit?: string[]; // 继承字段
|
||||
deep?: boolean; // 深度映射
|
||||
ignore?: string[]; // 忽略字段
|
||||
};
|
||||
|
||||
type MapperType = [string, string][] | Record<string, string>
|
||||
type DataType = Record<string, unknown> | Record<string, unknown>[]
|
||||
type MapperType = [string, string][] | Record<string, string>;
|
||||
type DataType = Record<string, unknown> | Record<string, unknown>[];
|
||||
|
||||
/**
|
||||
* 对象/数组映射,根据映射表,将数组或对象映射为新的对象和数组
|
||||
@@ -149,92 +206,117 @@ type DataType = Record<string, unknown> | Record<string, unknown>[]
|
||||
* 增加异常处理,如果值不存在,则抛出异常。
|
||||
* 返回新的对象/数组
|
||||
*/
|
||||
export const mapData = (mapper: MapperType, data: DataType, options: MapperOption = { deep: true }): DataType => {
|
||||
const { inherit, deep, ignore } = options
|
||||
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 选项不能同时使用')
|
||||
}
|
||||
// 验证 inherit 和 ignore 不能同时使用
|
||||
if (inherit && ignore) {
|
||||
throw new Error("inherit 和 ignore 选项不能同时使用");
|
||||
}
|
||||
|
||||
// 将 mapper 转换为对象形式
|
||||
const mapperObj = Array.isArray(mapper)
|
||||
? mapper.reduce<Record<string, string>>((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
: mapper
|
||||
// 将 mapper 转换为对象形式
|
||||
const mapperObj = Array.isArray(mapper)
|
||||
? mapper.reduce<Record<string, string>>(
|
||||
(acc, [key, value]) => ({ ...acc, [key]: value }),
|
||||
{}
|
||||
)
|
||||
: mapper;
|
||||
|
||||
// 处理数组
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((item) => mapData(mapperObj, item, options) as Record<string, unknown>)
|
||||
}
|
||||
// 处理数组
|
||||
if (Array.isArray(data)) {
|
||||
return data.map(
|
||||
(item) => mapData(mapperObj, item, options) as Record<string, unknown>
|
||||
);
|
||||
}
|
||||
|
||||
// 处理对象
|
||||
if (typeof data === 'object' && data !== null) {
|
||||
// 根据选项过滤 mapper
|
||||
let finalMapper = { ...mapperObj }
|
||||
if (inherit) {
|
||||
finalMapper = Object.entries(mapperObj)
|
||||
.filter(([key]) => inherit.includes(key))
|
||||
.reduce<Record<string, string>>((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
} else if (ignore) {
|
||||
finalMapper = Object.entries(mapperObj)
|
||||
.filter(([key]) => !ignore.includes(key))
|
||||
.reduce<Record<string, string>>((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
}
|
||||
// 处理对象
|
||||
if (typeof data === "object" && data !== null) {
|
||||
// 根据选项过滤 mapper
|
||||
let finalMapper = { ...mapperObj };
|
||||
if (inherit) {
|
||||
finalMapper = Object.entries(mapperObj)
|
||||
.filter(([key]) => inherit.includes(key))
|
||||
.reduce<Record<string, string>>(
|
||||
(acc, [key, value]) => ({ ...acc, [key]: value }),
|
||||
{}
|
||||
);
|
||||
} else if (ignore) {
|
||||
finalMapper = Object.entries(mapperObj)
|
||||
.filter(([key]) => !ignore.includes(key))
|
||||
.reduce<Record<string, string>>(
|
||||
(acc, [key, value]) => ({ ...acc, [key]: value }),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
return Object.entries(finalMapper).reduce<Record<string, unknown>>((result, [sourceKey, targetKey]) => {
|
||||
// 处理嵌套路径
|
||||
const value = sourceKey.split('.').reduce<unknown>((obj, key) => {
|
||||
if (obj === undefined || obj === null) {
|
||||
throw new Error(`映射键 "${sourceKey}" 不存在于源数据中`)
|
||||
}
|
||||
return (obj as Record<string, unknown>)[key]
|
||||
}, data)
|
||||
return Object.entries(finalMapper).reduce<Record<string, unknown>>(
|
||||
(result, [sourceKey, targetKey]) => {
|
||||
// 处理嵌套路径
|
||||
const value = sourceKey.split(".").reduce<unknown>((obj, key) => {
|
||||
if (obj === undefined || obj === null) {
|
||||
throw new Error(`映射键 "${sourceKey}" 不存在于源数据中`);
|
||||
}
|
||||
return (obj as Record<string, unknown>)[key];
|
||||
}, data);
|
||||
|
||||
// 处理值不存在的情况
|
||||
if (value === undefined) {
|
||||
throw new Error(`映射键 "${sourceKey}" 的值不存在`)
|
||||
}
|
||||
// 处理值不存在的情况
|
||||
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<Record<string, string>>(
|
||||
(acc, [key, val]) => ({
|
||||
...acc,
|
||||
[key.slice(sourceKey.length + 1)]: val,
|
||||
}),
|
||||
{},
|
||||
)
|
||||
// 处理深度映射
|
||||
if (deep && typeof value === "object" && value !== null) {
|
||||
const nestedMapper = Object.entries(mapperObj)
|
||||
.filter(([key]) => key.startsWith(`${sourceKey}.`))
|
||||
.reduce<Record<string, string>>(
|
||||
(acc, [key, val]) => ({
|
||||
...acc,
|
||||
[key.slice(sourceKey.length + 1)]: val,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
if (Object.keys(nestedMapper).length > 0) {
|
||||
return {
|
||||
...result,
|
||||
[targetKey]: mapData(nestedMapper, value as Record<string, unknown>, options),
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(nestedMapper).length > 0) {
|
||||
return {
|
||||
...result,
|
||||
[targetKey]: mapData(
|
||||
nestedMapper,
|
||||
value as Record<string, unknown>,
|
||||
options
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 处理嵌套目标路径
|
||||
const targetPath = (targetKey as string).split('.')
|
||||
const finalKey = targetPath.pop()!
|
||||
const targetObj = targetPath.reduce<Record<string, unknown>>((obj, key) => {
|
||||
if (!(key in obj)) {
|
||||
obj[key] = {}
|
||||
}
|
||||
return obj[key] as Record<string, unknown>
|
||||
}, result)
|
||||
// 处理嵌套目标路径
|
||||
const targetPath = (targetKey as string).split(".");
|
||||
const finalKey = targetPath.pop()!;
|
||||
const targetObj = targetPath.reduce<Record<string, unknown>>(
|
||||
(obj, key) => {
|
||||
if (!(key in obj)) {
|
||||
obj[key] = {};
|
||||
}
|
||||
return obj[key] as Record<string, unknown>;
|
||||
},
|
||||
result
|
||||
);
|
||||
|
||||
if (finalKey && targetObj) {
|
||||
targetObj[finalKey] = value
|
||||
}
|
||||
if (finalKey && targetObj) {
|
||||
targetObj[finalKey] = value;
|
||||
}
|
||||
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 生成映射表,将所有字段转换为小驼峰
|
||||
@@ -242,11 +324,11 @@ export const mapData = (mapper: MapperType, data: DataType, options: MapperOptio
|
||||
* @returns {Record<string, unknown>} 转换后的对象
|
||||
*/
|
||||
export const generateMapper = (obj: Record<string, unknown>) => {
|
||||
return Object.entries(obj).map(([key, value]) => [
|
||||
key.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()),
|
||||
value,
|
||||
])
|
||||
}
|
||||
return Object.entries(obj).map(([key, value]) => [
|
||||
key.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()),
|
||||
value,
|
||||
]);
|
||||
};
|
||||
|
||||
/**
|
||||
* 将对象转换为查询字符串
|
||||
@@ -254,11 +336,13 @@ export const generateMapper = (obj: Record<string, unknown>) => {
|
||||
* @returns {string} 转换后的查询字符串
|
||||
*/
|
||||
export const objectToQueryString = (obj: Record<string, any>) => {
|
||||
return Object.entries(obj)
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||
.join('&')
|
||||
}
|
||||
|
||||
return Object.entries(obj)
|
||||
.map(
|
||||
([key, value]) =>
|
||||
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`
|
||||
)
|
||||
.join("&");
|
||||
};
|
||||
|
||||
/**
|
||||
* 深度合并两个对象
|
||||
@@ -266,29 +350,38 @@ export const objectToQueryString = (obj: Record<string, any>) => {
|
||||
* @param {Record<string, any>} source - 源对象
|
||||
* @returns {Record<string, any>} 合并后的对象
|
||||
*/
|
||||
export const deepMerge = <T extends Record<string, any>>(target: T, source: T, isMergeArray: boolean = true): T => {
|
||||
const result = { ...target } as T
|
||||
export const deepMerge = <T extends Record<string, any>>(
|
||||
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]
|
||||
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<keyof T, string>]
|
||||
} else if (isObject(sourceValue) && isObject(targetValue)) {
|
||||
// 如果是对象,则递归合并
|
||||
result[key] = deepMerge(targetValue, sourceValue) as T[Extract<keyof T, string>]
|
||||
} else {
|
||||
// 其他情况直接覆盖
|
||||
result[key] = sourceValue as T[Extract<keyof T, string>]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(sourceValue) && Array.isArray(targetValue)) {
|
||||
// 如果是数组,则合并数组
|
||||
result[key] = (
|
||||
isMergeArray ? [...targetValue, ...sourceValue] : sourceValue
|
||||
) as T[Extract<keyof T, string>];
|
||||
} else if (isObject(sourceValue) && isObject(targetValue)) {
|
||||
// 如果是对象,则递归合并
|
||||
result[key] = deepMerge(targetValue, sourceValue) as T[Extract<
|
||||
keyof T,
|
||||
string
|
||||
>];
|
||||
} else {
|
||||
// 其他情况直接覆盖
|
||||
result[key] = sourceValue as T[Extract<keyof T, string>];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断是否为对象
|
||||
@@ -296,9 +389,8 @@ export const deepMerge = <T extends Record<string, any>>(target: T, source: T, i
|
||||
* @returns {boolean} 是否为对象
|
||||
*/
|
||||
const isObject = (value: any): boolean => {
|
||||
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
||||
}
|
||||
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 清理对象前后字符串
|
||||
@@ -306,12 +398,14 @@ const isObject = (value: any): boolean => {
|
||||
* @returns {Record<string, any>} 清理后的对象
|
||||
*/
|
||||
export const trimObject = (obj: Record<string, any>) => {
|
||||
return Object.entries(obj).reduce<Record<string, any>>((acc, [key, value]) => {
|
||||
acc[key.trim()] = value.trim()
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
return Object.entries(obj).reduce<Record<string, any>>(
|
||||
(acc, [key, value]) => {
|
||||
acc[key.trim()] = value.trim();
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 深拷贝对象(简单版)
|
||||
@@ -319,7 +413,5 @@ export const trimObject = (obj: Record<string, any>) => {
|
||||
* @returns {any} 拷贝后的对象
|
||||
*/
|
||||
export const deepClone = <T>(obj: T): T => {
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
}
|
||||
|
||||
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 文件定义:日期处理
|
||||
*/
|
||||
|
||||
import * as R from 'ramda'
|
||||
import * as R from "ramda";
|
||||
|
||||
/* -------------- 1、日期处理 -------------- */
|
||||
/**
|
||||
@@ -11,36 +11,44 @@ import * as R from 'ramda'
|
||||
* @param {string} format - 格式化字符串
|
||||
* @returns {string} 格式化后的日期字符串
|
||||
*/
|
||||
export const formatDate = (date: string | number | Date, format: string = 'yyyy-MM-dd HH:mm:ss'): string => {
|
||||
// 处理秒级时间戳
|
||||
const timestamp = !!Number(date) && date.toString().length === 10 ? new Date(Number(date) * 1000) : new Date(date)
|
||||
export const formatDate = (
|
||||
date: string | number | Date | null | undefined,
|
||||
format: string = "yyyy-MM-dd HH:mm:ss"
|
||||
): string => {
|
||||
if (!date) return "--";
|
||||
// 处理秒级时间戳
|
||||
const timestamp =
|
||||
!!Number(date) && date.toString().length === 10
|
||||
? new Date(Number(date) * 1000)
|
||||
: new Date(date);
|
||||
|
||||
// 使用Ramda创建日期映射
|
||||
const dateMap = R.zipObj(
|
||||
['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss'],
|
||||
[
|
||||
timestamp.getFullYear(),
|
||||
timestamp.getMonth() + 1,
|
||||
timestamp.getDate(),
|
||||
timestamp.getHours(),
|
||||
timestamp.getMinutes(),
|
||||
timestamp.getSeconds(),
|
||||
],
|
||||
)
|
||||
// 使用Ramda创建日期映射
|
||||
const dateMap = R.zipObj(
|
||||
["yyyy", "MM", "dd", "HH", "mm", "ss"],
|
||||
[
|
||||
timestamp.getFullYear(),
|
||||
timestamp.getMonth() + 1,
|
||||
timestamp.getDate(),
|
||||
timestamp.getHours(),
|
||||
timestamp.getMinutes(),
|
||||
timestamp.getSeconds(),
|
||||
]
|
||||
);
|
||||
|
||||
// 使用Ramda的reduce函数替换格式字符串中的占位符
|
||||
return R.reduce(
|
||||
(result: string, key: string) => {
|
||||
const value = dateMap[key as keyof typeof dateMap]
|
||||
// 将单位数的月、日、时、分、秒前面补0
|
||||
const formattedValue = key !== 'yyyy' && value < 10 ? `0${value}` : `${value}`
|
||||
// 使用正则表达式全局替换所有匹配项
|
||||
return result.replace(new RegExp(key, 'g'), formattedValue)
|
||||
},
|
||||
format,
|
||||
R.keys(dateMap),
|
||||
)
|
||||
}
|
||||
// 使用Ramda的reduce函数替换格式字符串中的占位符
|
||||
return R.reduce(
|
||||
(result: string, key: string) => {
|
||||
const value = dateMap[key as keyof typeof dateMap];
|
||||
// 将单位数的月、日、时、分、秒前面补0
|
||||
const formattedValue =
|
||||
key !== "yyyy" && value < 10 ? `0${value}` : `${value}`;
|
||||
// 使用正则表达式全局替换所有匹配项
|
||||
return result.replace(new RegExp(key, "g"), formattedValue);
|
||||
},
|
||||
format,
|
||||
R.keys(dateMap)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取两个日期之间的天数差
|
||||
@@ -48,14 +56,21 @@ export const formatDate = (date: string | number | Date, format: string = 'yyyy-
|
||||
* @param {string | number | Date} endDate - 结束日期
|
||||
* @returns {number} 天数差
|
||||
*/
|
||||
export const getDaysDiff = (startDate: string | number | Date, endDate: string | number | Date): number => {
|
||||
const start = new Date(startDate)
|
||||
const end = new Date(endDate)
|
||||
const startDay = new Date(start.getFullYear(), start.getMonth(), start.getDate())
|
||||
const endDay = new Date(end.getFullYear(), end.getMonth(), end.getDate())
|
||||
const diff = endDay.getTime() - startDay.getTime()
|
||||
return Math.floor(diff / (1000 * 60 * 60 * 24))
|
||||
}
|
||||
export const getDaysDiff = (
|
||||
startDate: string | number | Date,
|
||||
endDate: string | number | Date
|
||||
): number => {
|
||||
const start = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
const startDay = new Date(
|
||||
start.getFullYear(),
|
||||
start.getMonth(),
|
||||
start.getDate()
|
||||
);
|
||||
const endDay = new Date(end.getFullYear(), end.getMonth(), end.getDate());
|
||||
const diff = endDay.getTime() - startDay.getTime();
|
||||
return Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
};
|
||||
|
||||
/**
|
||||
* 柯里化版本的getDaysDiff
|
||||
@@ -64,9 +79,11 @@ export const getDaysDiff = (startDate: string | number | Date, endDate: string |
|
||||
* @returns {number} 天数差
|
||||
*/
|
||||
export const getDaysDiffCurried: {
|
||||
(startDate: string | number | Date, endDate: string | number | Date): number
|
||||
(startDate: string | number | Date): (endDate: string | number | Date) => number
|
||||
} = R.curry(getDaysDiff)
|
||||
(startDate: string | number | Date, endDate: string | number | Date): number;
|
||||
(startDate: string | number | Date): (
|
||||
endDate: string | number | Date
|
||||
) => number;
|
||||
} = R.curry(getDaysDiff);
|
||||
|
||||
/**
|
||||
* 判断日期是否在指定范围内
|
||||
@@ -76,15 +93,15 @@ export const getDaysDiffCurried: {
|
||||
* @returns {boolean} 是否在范围内
|
||||
*/
|
||||
export const isDateInRange = (
|
||||
date: string | number | Date,
|
||||
startDate: string | number | Date,
|
||||
endDate: string | number | Date,
|
||||
date: string | number | Date,
|
||||
startDate: string | number | Date,
|
||||
endDate: string | number | Date
|
||||
): boolean => {
|
||||
const targetTime = new Date(date).getTime()
|
||||
const startTime = new Date(startDate).getTime()
|
||||
const endTime = new Date(endDate).getTime()
|
||||
return targetTime >= startTime && targetTime <= endTime
|
||||
}
|
||||
const targetTime = new Date(date).getTime();
|
||||
const startTime = new Date(startDate).getTime();
|
||||
const endTime = new Date(endDate).getTime();
|
||||
return targetTime >= startTime && targetTime <= endTime;
|
||||
};
|
||||
|
||||
/**
|
||||
* 柯里化版本的isDateInRange
|
||||
@@ -94,13 +111,24 @@ export const isDateInRange = (
|
||||
* @returns {boolean} 是否在范围内
|
||||
*/
|
||||
export const isDateInRangeCurried: {
|
||||
(date: string | number | Date, startDate: string | number | Date, endDate: string | number | Date): boolean
|
||||
(date: string | number | Date): {
|
||||
(startDate: string | number | Date, endDate: string | number | Date): boolean
|
||||
(startDate: string | number | Date): (endDate: string | number | Date) => boolean
|
||||
}
|
||||
(date: string | number | Date, startDate: string | number | Date): (endDate: string | number | Date) => boolean
|
||||
} = R.curry(isDateInRange)
|
||||
(
|
||||
date: string | number | Date,
|
||||
startDate: string | number | Date,
|
||||
endDate: string | number | Date
|
||||
): boolean;
|
||||
(date: string | number | Date): {
|
||||
(
|
||||
startDate: string | number | Date,
|
||||
endDate: string | number | Date
|
||||
): boolean;
|
||||
(startDate: string | number | Date): (
|
||||
endDate: string | number | Date
|
||||
) => boolean;
|
||||
};
|
||||
(date: string | number | Date, startDate: string | number | Date): (
|
||||
endDate: string | number | Date
|
||||
) => boolean;
|
||||
} = R.curry(isDateInRange);
|
||||
|
||||
/**
|
||||
* 获取指定日期的开始时间(00:00:00)
|
||||
@@ -108,9 +136,9 @@ export const isDateInRangeCurried: {
|
||||
* @returns {Date} 日期的开始时间
|
||||
*/
|
||||
export const getStartOfDay = (date: string | number | Date): Date => {
|
||||
const d = new Date(date)
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate())
|
||||
}
|
||||
const d = new Date(date);
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取指定日期的结束时间(23:59:59)
|
||||
@@ -118,9 +146,9 @@ export const getStartOfDay = (date: string | number | Date): Date => {
|
||||
* @returns {Date} 日期的结束时间
|
||||
*/
|
||||
export const getEndOfDay = (date: string | number | Date): Date => {
|
||||
const d = new Date(date)
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999)
|
||||
}
|
||||
const d = new Date(date);
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加天数到指定日期
|
||||
@@ -129,39 +157,47 @@ export const getEndOfDay = (date: string | number | Date): Date => {
|
||||
* @returns {Date} 新日期
|
||||
*/
|
||||
export const addDays = (days: number, date: string | number | Date): Date => {
|
||||
const result = new Date(date)
|
||||
result.setDate(result.getDate() + days)
|
||||
return result
|
||||
}
|
||||
const result = new Date(date);
|
||||
result.setDate(result.getDate() + days);
|
||||
return result;
|
||||
};
|
||||
|
||||
// 柯里化版本的addDays
|
||||
export const addDaysCurried: {
|
||||
(days: number, date: string | number | Date): Date
|
||||
(days: number): (date: string | number | Date) => Date
|
||||
} = R.curry(addDays)
|
||||
(days: number, date: string | number | Date): Date;
|
||||
(days: number): (date: string | number | Date) => Date;
|
||||
} = R.curry(addDays);
|
||||
|
||||
/**
|
||||
* 格式化相对时间(如:刚刚、x分钟前、x小时前、x天前)
|
||||
* @param {string | number | Date} date - 日期
|
||||
* @returns {string} 格式化后的相对时间
|
||||
*/
|
||||
export const formatRelativeTime = (date: string | number | Date): string => {
|
||||
const now = new Date().getTime()
|
||||
const target = new Date(date).getTime()
|
||||
const diff = now - target
|
||||
export const formatRelativeTime = (
|
||||
date: string | number | Date | null | undefined,
|
||||
format: string = "YYYY-MM-DD"
|
||||
): string => {
|
||||
if (!date) return "--";
|
||||
const timestamp =
|
||||
!!Number(date) && date.toString().length === 10
|
||||
? new Date(Number(date) * 1000)
|
||||
: new Date(date);
|
||||
const now = new Date().getTime();
|
||||
const target = new Date(timestamp).getTime();
|
||||
const diff = now - target;
|
||||
|
||||
if (diff < 1000 * 60) {
|
||||
return '刚刚'
|
||||
} else if (diff < 1000 * 60 * 60) {
|
||||
return `${Math.floor(diff / (1000 * 60))}分钟前`
|
||||
} else if (diff < 1000 * 60 * 60 * 24) {
|
||||
return `${Math.floor(diff / (1000 * 60 * 60))}小时前`
|
||||
} else if (diff < 1000 * 60 * 60 * 24 * 30) {
|
||||
return `${Math.floor(diff / (1000 * 60 * 60 * 24))}天前`
|
||||
} else {
|
||||
return formatDate(date, 'YYYY-MM-DD')
|
||||
}
|
||||
}
|
||||
if (diff < 1000 * 60) {
|
||||
return "刚刚";
|
||||
} else if (diff < 1000 * 60 * 60) {
|
||||
return `${Math.floor(diff / (1000 * 60))}分钟前`;
|
||||
} else if (diff < 1000 * 60 * 60 * 24) {
|
||||
return `${Math.floor(diff / (1000 * 60 * 60))}小时前`;
|
||||
} else if (diff < 1000 * 60 * 60 * 24 * 30) {
|
||||
return `${Math.floor(diff / (1000 * 60 * 60 * 24))}天前`;
|
||||
} else {
|
||||
return formatDate(date, format);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取指定日期是星期几
|
||||
@@ -169,9 +205,9 @@ export const formatRelativeTime = (date: string | number | Date): string => {
|
||||
* @returns {string} 星期几
|
||||
*/
|
||||
export const getDayOfWeek = (date: string | number | Date): string => {
|
||||
const days = ['日', '一', '二', '三', '四', '五', '六']
|
||||
return `星期${days[new Date(date).getDay()]}`
|
||||
}
|
||||
const days = ["日", "一", "二", "三", "四", "五", "六"];
|
||||
return `星期${days[new Date(date).getDay()]}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取指定距离到期时间
|
||||
@@ -180,12 +216,12 @@ export const getDayOfWeek = (date: string | number | Date): string => {
|
||||
* @returns {string} 距离到期时间
|
||||
*/
|
||||
export const getDaysUntilExpiration = (
|
||||
date: string | number | Date,
|
||||
expirationDate: string | number | Date = new Date(),
|
||||
date: string | number | Date,
|
||||
expirationDate: string | number | Date = new Date()
|
||||
): string => {
|
||||
const target = new Date(date)
|
||||
const expiration = new Date(expirationDate)
|
||||
const diff = expiration.getTime() - target.getTime()
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
||||
return days > 0 ? `${days}天` : '已过期'
|
||||
}
|
||||
const target = new Date(date);
|
||||
const expiration = new Date(expirationDate);
|
||||
const diff = expiration.getTime() - target.getTime();
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
return days > 0 ? `${days}天` : "已过期";
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user