在鸿蒙应用开发过程中,为了提高代码复用性和可维护性,通常会将一些通用的功能封装到工具类中。本文将介绍几个常见的工具类及其方法,包括字符串、数组、对象处理、日期格式化等。
1. 字符串处理工具函数 String
1.1 根据指定的空格截取类型对字符串进行空格处理
/**
* 空格截取类型枚举,用于定义字符串中空格去除的方式。
*/
enum ESpaceTrimType {
ALL, // 去除所有空格(包括前、后、中间)
BEFORE, // 仅去除前面的空格
AFTER, // 仅去除后面的空格
BEFORE_AND_AFTER // 仅去除前后空格,保留中间空格
}
/**
* @param {string} str - 需要去除空格的原始字符串。
* @param {ESpaceTrimType} [trimType=ESpaceTrimType.BEFORE_AND_AFTER]
* 指定空格去除方式,默认为去除前后空格。
*
* @returns {string} 处理后的字符串,根据 trimType 移除了相应位置的空格。
*
* @example
* SpaceTrim(' Hello World ', ESpaceTrimType.ALL)
* // 返回 'HelloWorld'
*
* SpaceTrim(' Hello World ', ESpaceTrimType.BEFORE)
* // 返回 'Hello World '
*
* SpaceTrim(' Hello World ', ESpaceTrimType.AFTER)
* // 返回 ' Hello World'
*
* SpaceTrim(' Hello World ', ESpaceTrimType.BEFORE_AND_AFTER)
* // 返回 'Hello World'
*/
const SpaceTrim = (str: string, trimType: ESpaceTrimType = ESpaceTrimType.BEFORE_AND_AFTER): string => {
switch (trimType){
case ESpaceTrimType.ALL:
return str.replace(/\s+/g, '')
case ESpaceTrimType.BEFORE:
return str.replace(/(^\s*)/g, '')
case ESpaceTrimType.AFTER:
return str.replace(/(\s*$)/g, '')
case ESpaceTrimType.BEFORE_AND_AFTER:
return str.replace(/(^\s*)|(\s*$)/g, '')
}
}
1.2 对目标字符串或数字进行脱敏处理,保留前后位数,中间部分用星号(*)代替
/**
* 适用于手机号、身份证号、银行卡号等敏感信息的显示保护。
*
* @param {number | string} target - 需要脱敏的目标值,可以是数字或字符串形式的数字。
* @param {number} [prefixLength=3] - 保留的前缀字符数,默认为 3。
* @param {number} [suffixLength=4] - 保留的后缀字符数,默认为 4。
*
* @returns {string | number} 脱敏后的字符串;如果原始值为空或长度不足以脱敏,则返回原值。
*
* @example
* Desensitization('13800001111')
* // 返回 '138******1111'
*
* Desensitization(123456789, 2, 2)
* // 返回 '12****89'
*
* Desensitization('1234', 2, 3)
* // 返回 '1234' (因为总长度小于 prefixLength + suffixLength)
*/
const Desensitization = (target: number | string, prefixLength: number = 3, suffixLength: number = 4) => {
if (target) {
let targetStr = target.toString()
const prefix = targetStr.substring(0, prefixLength) // 前几位
const suffix = targetStr.substring(targetStr.length - suffixLength) // 后几位
if (targetStr.length > prefixLength + suffixLength) {
const maskedPart = '*'.repeat(targetStr.length - prefixLength - suffixLength) // 中间部分用 * 替换
return `${prefix}${maskedPart}${suffix}`
}
}
return target
}
1.3 生成一个模拟 UUID 的随机字符串(遵循 UUID v4 格式规范)
/**
* UUID v4 是基于随机数生成的唯一标识符,格式为:
* `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`
* 其中:
* - 第3段以 `4` 开头(表示 UUID 版本 4)
* - 第4段以 `8`, `9`, `a`, 或 `b` 开头(符合 RFC 4122 规范)
*
* @returns {string} 返回一个模拟 UUID v4 的随机字符串。
*
* @example
* toAnyString()
* // 返回类似 '6a2e8f50-a219-4d7d-bf8c-0a9d3e1f7b5c'
*/
function toAnyString() {
const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => {
const r: number = (Math.random() * 16) | 0
const v: number = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString()
})
return str
}
1.4 将字符串中每个单词的首字母转换为大写,其余字母保持小写
/**
* 适用于格式化标题、姓名、标签等需要首字母大写的场景。
*
* @param {string} str - 需要处理的原始字符串。
* @returns {string} 每个单词首字母大写的新字符串。
*
* @example
* firstUpperCase('hello world')
* // 返回 'Hello World'
*
* firstUpperCase('THIS IS A TEST')
* // 返回 'This Is A Test'
*/
function firstUpperCase(str: string) {
return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
}
1.5 十六进制随机颜色生成器
// 十六进制颜色生成器
const getRandomHexColor = (): string => {
const randomColor = Math.floor(Math.random() * 0xffffff).toString(16);
return `#${randomColor.padStart(6, '0')}`;
}
1.6 将十六进制颜色字符串(HEX)转换为 RGB 数值数组
/**
* 支持标准格式如 `#FF5500` 和简写格式如 `#FFF`。
* 转换后返回一个包含红、绿、蓝三个颜色通道数值的数组,每个数值范围为 0 到 255。
*
* @param {string} 【color】
* 十六进制颜色字符串。可以是 3 位(简写)或 6 位(标准),可选前缀 `#`。
* 示例:'#FF5500'、'FF5500'、'#ABC'、'ABC'
*
* @returns {number[]}
* 包含三个数字元素的数组,分别代表红(Red)、绿(Green)、蓝(Blue)通道的值。
* 示例:'#FF5500' → [255, 85, 0]、'#ABC' → [170, 187, 204]
*
* @throws {Error} 如果传入的颜色字符串不是 3 位或 6 位长度,则抛出错误。
*/
function getColorRGB(color: string): number[] {
// 去除可能的 # 并转为小写
let hex = color.replace(/^#/, '').toLowerCase();
// 检查是否为合法长度(3 或 6)
if (![3, 6].includes(hex.length)) {
throw new Error('Invalid hex color format. Expected 3 or 6 characters.');
}
// 如果是3位简写,扩展为6位(例如 'f' -> 'ff')
if (hex.length === 3) {
hex = hex.replace(/./g, match => match + match);
}
// 使用正则将字符串按每两位一组分割,并转换为十进制数值
const rgb = hex.match(/.{2}/g)?.map(channel => parseInt(`0x${channel}`)) || [];
return rgb;
}
1.7 将十六进制颜色值转换为 RGBA 字符串格式
/**
* @param {string} _color - 十六进制颜色字符串,如 '#FF5733' 或 '#F5A'
* @param {number} _opacity - 透明度(范围 0 到 1)
* @returns {string} 转换后的 RGBA 字符串,例如 'rgba(255, 87, 51, 0.5)'
* 如果输入无效,则返回原始字符串或空字符串
*/
const hexToRGBA = (_color: string, _opacity: number): string => {
let sColor = _color?.toLowerCase();
// 定义十六进制颜色的正则表达式,支持 3 位或 6 位格式
const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
// 检查是否为有效的十六进制颜色格式
if (sColor && reg.test(sColor)) {
// 如果是简写的 3 位格式(如 #F5A),将其扩展为标准的 6 位格式(如 #FF55AA)
if (sColor.length === 4) {
let sColorNew = "#";
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
}
sColor = sColorNew;
}
// 处理标准的 6 位十六进制颜色值,提取 R、G、B 分量
const sColorChange: number[] = [];
for (let i = 1; i < 7; i += 2) {
// 将每个两位的十六进制值转换为十进制数
sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2), 16));
}
// 构造并返回 RGBA 字符串
return `rgba(${sColorChange.join(", ")}, ${_opacity})`;
}
// 如果输入无效,返回原始颜色值
return sColor ?? '';
};
1.8 将对象参数转换为查询字符串并拼接到 URL 上
/**
* @param baseUrl - 基础 URL
* @param obj - 要转换为查询参数的对象
* @returns 拼接后的完整 URL
*
* 示例:
* const url = setObjToUrlParams("https://api.example.com", { a: 1, b: "hello" });
* // 结果: "https://api.example.com?a=1&b=hello"
*
* const urlWithParams = setObjToUrlParams("https://api.example.com?a=1", { b: "test" });
* // 结果: "https://api.example.com?a=1&b=test"
*/
function setObjToUrlParams(baseUrl: string, obj: object): string {
const params = Object.keys(obj).map(key => {
return `${key}=${encodeURIComponent(obj[key])}`;
}).join('&');
const separator = baseUrl.includes('?') ? '' : '?';
const cleanUrl = baseUrl.endsWith('?') || baseUrl.endsWith('&') ? baseUrl : baseUrl;
return `${cleanUrl}${separator}${params}`;
}
1.9 判断传入的字符串是否为 Base64 编码的图片数据 URI
/**
* 支持格式如:
* - data:image/png;base64,iVBORw0KG...
* - data:image/jpeg;base64,/9j/4AAQ...
*
* @param str - 需要判断的字符串
* @returns 如果是 Base64 格式的图片数据,返回 `true`;否则返回 `false`
*
* 示例:
* isBase64Image('data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...')
* // true
*
* isBase64Image('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFQQMEDQsKDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwN/wAARCAABAAEDASIAAhEBAxEB/2gAMAwEAAhEDEQA/APf6ACiiigD//Z')
* // true
*
* isBase64Image('https://example.com/image.png')
* // false
*
* isBase64Image('not-a-base64-string')
* // false
*/
function isBase64Image(str: string): boolean {
// 判断字符串是否以 "data:image/" 开头并且包含 "base64"
const base64PrefixRegex = /^data:image/([a-zA-Z]*);base64,/;
return base64PrefixRegex.test(str);
}
1.10 移除 Base64 图片数据的前缀部分(如 "data:image/png;base64,")
/**
* @param base64String - 包含前缀的完整 Base64 图片字符串
* @returns 去除前缀后的纯 Base64 编码字符串
*
* ### 示例
* removeBase64Prefix('data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...')
* // 返回: 'iVBORw0KGgoAAAANSUhEUg...'
*
* removeBase64Prefix('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFQQMEDQsKDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwN/wAARCAABAAEDASIAAhEBAxEB/2gAMAwEAAhEDEQA/APf6ACiiigD//Z')
* // 返回: '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFQQMEDQsKDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwN/wAARCAABAAEDASIAAhEBAxEB/2gAMAwEAAhEDEQA/APf6ACiiigD//Z'
*
* removeBase64Prefix('iVBORw0KGgoAAAANSUhEUg...')
* // 返回: 'iVBORw0KGgoAAAANSUhEUg...'
*/
function removeBase64Prefix(base64String: string) {
const regex = /^data:image\/[^;]+;base64,/;
return base64String.replace(regex, "");
}
1.11 提取身份证中的出生年月日
/**
* @param idCard 身份证号码
* @returns 出生日期字符串(格式:YYYY-MM-DD),失败返回 null
*
* @example
* getBirthdayFromIdCard("110101199003072316") // => "1990-03-07"
* getBirthdayFromIdCard("110101900307231") // => "1990-03-07"
* getBirthdayFromIdCard("ABC") // => null
*/
const getBirthdayFromIdCard = (idCard: string): string | null => {
if (!idCard || idCard.trim() === '') return null;
let birthday = '';
if (idCard.length === 15) {
// 15位身份证:第7~8位为年份(YY),第9~10位为月份,第11~12位为日期
const year = '19' + idCard.substring(6, 8);
const month = idCard.substring(8, 10);
const day = idCard.substring(10, 12);
birthday = `${year}-${month}-${day}`;
} else if (idCard.length === 18) {
// 18位身份证:第7~10位为年份(YYYY),第11~12位为月份,第13~14位为日期
const year = idCard.substring(6, 10);
const month = idCard.substring(10, 12);
const day = idCard.substring(12, 14);
birthday = `${year}-${month}-${day}`;
} else {
return null;
}
// 简单校验日期是否合法
const date = new Date(birthday);
if (date.toString() === 'Invalid Date') return null;
return birthday;
};
1.11.1 根据身份证号码计算当前年龄
- 需要
getBirthdayFromIdCard先解析出生日期
/**
* @param idCard 身份证号码
* @returns 年龄(整数),失败返回 null
*
* @example
* getAgeFromIdCard("110101199003072316") // => 实际年龄(如:34)
* getAgeFromIdCard("110101900307231") // => 同上
* getAgeFromIdCard("ABC") // => null
*/
const getAgeFromIdCard = (idCard: string): number | null => {
const birthday = getBirthdayFromIdCard(idCard);
if (!birthday) return null;
const birthDate = new Date(birthday);
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const m = today.getMonth() - birthDate.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age >= 0 ? age : null;
};
1.12 解析身份证中的性别信息(仅支持18位)
/**
* @param idCard 18位身份证号码
* @returns "Male" 表示男性,"Female" 表示女性,否则返回 null
*
* @example
* getGenderFromIdCard("110101199003072316") // => "male"
* getGenderFromIdCard("110101199003072326") // => "female"
* getGenderFromIdCard("110101900307231") // => null (非18位)
*/
const getGenderFromIdCard = (idCard: string): 'Male' | 'Female' | null => {
if (idCard.length !== 18) {
return null;
}
const genderCode = parseInt(idCard[16], 10); // 第17位数字
return genderCode % 2 === 1 ? 'Male' : 'Female';
};
2. 数组|对象工具函数 Array | Object
2.1 根据指定的键对对象数组进行分组,返回一个以组合键为索引、对应元素数组为值的对象
/**
* @template T - 数组中每个元素的类型,必须是一个对象(object)
* @template K - 指定分组依据的键的类型,必须是 T 的 key
* @param {T[]} arr - 要分组的对象数组
* @param {K[]} keys - 用于分组的一个或多个键(属性名)
* @returns {Record<string, T[]>} 分组后的结果,键为组合键字符串,值为对应的元素数组
*
* @example
* const testData1 = [
* { name: 'Alice', age: 25, city: 'Beijing' },
* { name: 'Bob', age: 30, city: 'Shanghai' },
* { name: 'Charlie', age: 25, city: 'Beijing' }
* ];
*
* groupByKeys(testData1, ['city']);
* // 返回:
* // {
* // 'Beijing': [{ name: 'Alice', ... }, { name: 'Charlie', ... }],
* // 'Shanghai': [{ name: 'Bob', ... }]
* // }
*/
const groupByKeys = <T extends object, K extends keyof T>(arr: T[], keys: K[]): Record<string, T[]> => {
return arr.reduce<Record<string, T[]>>((acc, current) => {
const groupKey = keys.map((key) => Reflect.get(current, key) as T).join('-')
if (!acc[groupKey]) {
acc[groupKey] = []
}
acc[groupKey].push(current)
return acc
}, {} as Record<string, T[]>)
}
2.2 深拷贝函数,用于复制对象或数组及其嵌套结构,避免引用共享
/**
* @param {ESObject} obj - 要深拷贝的对象或数组
* @returns {ESObject} 返回一个与原对象结构相同但内存独立的新对象
*/
function deepCopy(obj: ESObject): ESObject {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let copy: ESObject;
if (Array.isArray(obj)) {
copy = [];
for (let i = 0; i < obj.length; i++) {
copy[i] = deepCopy(obj[i]);
}
} else {
copy = {};
for (let i = 0; i < obj.length(); i++) {
let key: ESObject = obj[i];
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]);
}
}
}
return copy;
}
2.3 对两个数组进行对比,删除数组 A 中与数组 B 相同的数据(根据指定的唯一标识字段)
/**
* @param dataA - 数组 A,从中移除匹配项
* @param dataB - 数组 B,包含需要匹配的项
* @param id - 用于比较的唯一标识字段名称(必须是对象中的键)
* @returns 过滤后的数组 A,不包含在数组 B 中出现过的数据
*
* @typeParam T - 泛型类型,表示数组中对象的类型
* @typeParam K - 泛型类型,表示用于比较的字段名,必须是 `T` 的键
*
* 示例:
* const arrayA = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
* const arrayB = [{ id: 2, name: 'Bob' }];
* const result = removeMatchingItemsById(arrayA, arrayB, 'id');
* // result => [{ id: 1, name: 'Alice' }]
*/
function removeMatchingItemsById<T extends Record<K, string | number>, K extends keyof T>(
dataA: T[],
dataB: T[],
id: K
): T[] {
if (!Array.isArray(dataA) || !Array.isArray(dataB)) {
throw new Error('dataA and dataB must be arrays');
}
const idsInB = new Set(dataB.map(item => item[id]));
return dataA.filter(item => !idsInB.has(item[id]));
}
2.4 泛型防抖函数:在指定的时间间隔内多次触发时,只执行最后一次调用
/**
* 防止高频事件(如输入框变化、窗口调整等)频繁执行函数,提升性能和用户体验。
* 使用泛型 `<T>` 支持任意类型的参数传入,保持类型安全。
*
* @param func 要执行的目标函数,接受一个可选的泛型参数 `T`,也可以无参
* @param delay 延迟时间,单位为毫秒(ms),默认值为 300ms
* @returns 返回一个新的包装函数,具备防抖能力,调用该函数即触发防抖逻辑
*
* @example
* // 示例1:基本用法 - 字符串参数
* const searchHandler = debounce<string>((query: string) => {
* console.log('搜索内容:', query);
* }, 500);
*
* searchHandler('Harmony');
* searchHandler('HarmonyOS'); // 上一次调用被取消,只有最后的 'HarmonyOS' 会被打印
*
* @example
* // 示例2:传递对象参数
* interface User {
* id: number;
* name: string;
* }
*
* const saveUser = debounce<User>((user: User) => {
* console.log('保存用户:', user);
* }, 1000);
*
* saveUser({ id: 1, name: 'Alice' });
* saveUser({ id: 2, name: 'Bob' }); // 最终只会保存 Bob
*
* @example
* // 示例3:不带参数的情况
* const logMessage = debounce(() => {
* console.log('页面加载完成');
* }, 800);
*
* logMessage(); // 页面加载完成后会延迟输出日志
*/
const debounce = <T>(func: (arg?: T) => void, delay: number = 300) => {
let timer: number | null = null;
return (arg?: T): void => {
if (timer !== null) {
clearTimeout(timer); // 清除之前的定时器,避免重复执行
}
timer = setTimeout(() => {
func(arg); // 在 delay 时间后执行目标函数
}, delay);
};
};
3. 日期处理工具类 Date
3.1 获取当前时间戳(毫秒数)
/**
* 如果传入一个有效的 Date 对象,则返回该对象对应的时间戳;
* 否则返回当前系统时间的时间戳。
*
* @param {Date} [date] - 可选参数,一个有效的 Date 对象
* @returns {number} 表示时间戳的数字(单位:毫秒)
*
* @example
* getCurrentTimestamp(); // 返回当前时间戳,例如 1712345678901
* getCurrentTimestamp(new Date(2023, 0, 1)); // 返回 2023 年 1 月 1 日对应的时间戳
*/
function getCurrentTimestamp(date?: Date): number {
if (date instanceof Date && !isNaN(date.getTime())) {
return date.getTime();
}
return Date.now();
}
3.2 计算两个日期之间的天数差(向上取整)
/**
* @param {Date} startDate - 开始日期对象
* @param {Date} endDate - 结束日期对象
* @returns {number} 两个日期之间的天数差(向上取整)
*
* @example
* const start = new Date('2024-04-01');
* const end = new Date('2024-04-03');
* getDaysBetweenDates(start, end); // 返回 2
*/
function getDaysBetweenDates(startDate: Date, endDate: Date): number {
const diffTime = Math.abs(endDate.getTime() - startDate.getTime());
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}
3.3 计算两个日期之间的年数和剩余月份数,结果不进行四舍五入
interface YearsAndMonths {
years: number;
months: number;
}
/**
* @param {Date} startDate - 起始日期
* @param {Date} endDate - 结束日期
* @returns {YearsAndMonths} 返回包含年份差和剩余月份的对象
* @throws {Error} 如果传入的日期无效或结束日期早于开始日期
*
* @example
* const start = new Date('2020-05-15');
* const end = new Date('2023-07-20');
* CalculateYearsAndMonths(start, end); // 返回 { years: 3, months: 2 }
*/
const CalculateYearsAndMonths = (startDate: Date, endDate: Date) => {
const start = new Date(startDate);
const end = new Date(endDate);
// 校验是否为有效日期
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
throw new Error('Invalid date input');
}
// 确保结束日期不早于开始日期
if (end < start) {
throw new Error('End date cannot be earlier than start date');
}
let years = end.getFullYear() - start.getFullYear();
let months = end.getMonth() - start.getMonth();
// 调整年份和月份:如果月份为负,则借位一年并加12个月
if (months < 0) {
years--;
months += 12;
}
return { years, months } as YearsAndMonths;
};
3.4 将时间格式化为指定字符串格式
interface TimeFormatObject {
y: number; // 年
m: number; // 月(1-12)
d: number; // 日
h: number; // 时
i: number; // 分
s: number; // 秒
a: number; // 星期几(0=周日,1=周一,... 6=周六)
}
/**
* 支持传入 Date 对象、时间戳(10位或13位)或日期字符串(如 '2024-01-01')
* 可自定义输出格式,支持年(y)、月(m)、日(d)、时(h)、分(i)、秒(s)、星期(a)等占位符
*
* 示例:
* parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}') => "2024-05-30 15:30:45"
* parseTime(1717029200, '{y}年{m}月{d}日 星期{a}') => "2024年5月30日 星期四"
*
* @param {Date | string | number} [time]
* 要格式化的时间,支持 Date / 时间戳 / 日期字符串
* @param {string} [cFormat]
*。 格式化模板,默认为 '{y}-{m}-{d} {h}:{i}:{s}'
* 支持字段:{y}年,{m}月,{d}日,{h}时,{i}分,{s}秒,{a}星期
* @returns {string} 格式化后的时间字符串,若时间无效则返回空字符串
*/
function parseTime<K extends keyof TimeFormatObject>(time: Date | string | number, cFormat?: string): string {
if (arguments.length === 0) {
return ''
}
if (time == null || time === 'null') {
return ''
}
let date: Date
if (time instanceof Date) {
date = time
} else {
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
time = parseInt(time)
}
if (typeof time === 'number') {
const floorTime = Math.floor(time)
if (floorTime.toString().length === 10) {
time = time * 1000
}
}
date = new Date(time as number | string)
if (isNaN(date.getTime())) {
return ''
}
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
const formatObj: TimeFormatObject = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key: K) => {
const value = Reflect.get(formatObj, key) ?? ''
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value as number] || ''
}
if (result.length > 0 && typeof value === 'number' && value < 10) {
return '0' + value
}
return value.toString()
})
return time_str
}
3.4.1 格式化时间戳为可读性更强的时间描述
parsedTime需要用到上面封装的时间格式化工具函数
/**
* @param {number} 【time】 - 时间戳(秒或毫秒)。
* @param {string | undefined} 【option】
* 可自定义输出格式,支持年(y)、月(m)、日(d)、时(h)、分(i)、秒(s)、星期(a)等占位符
* @returns {string} - 格式化后的时间描述。
*
* 该函数首先检查时间戳的长度是否为10位(即是否是秒级时间戳),如果是,则将其转换为毫秒级时间戳。
* 然后创建一个 Date 对象,并计算当前时间和给定时间之间的差异(以秒为单位)。
* 根据时间差,返回不同的时间描述:
* - 如果时间差小于30秒,返回“刚刚”。
* - 如果时间差小于1小时,返回“X分钟前”。
* - 如果时间差小于24小时,返回“X小时前”。
* - 如果时间差小于2天,返回“1天前”。
* 如果提供了格式字符串,则使用 parseTime 函数按照指定格式返回时间。
* 否则,返回默认格式的时间字符串,包括月份、日期、小时和分钟。
*/
function formatTime(time: number, option?: string): string {
let parsedTime = time;
if (('' + parsedTime).length === 10) {
parsedTime = parseInt('' + parsedTime) * 1000;
} else {
parsedTime = +parsedTime;
}
const d = new Date(parsedTime);
const now = Date.now();
const diff = (now - d.getTime()) / 1000;
if (diff < 30) {
return '刚刚';
} else if (diff < 3600) {
// less 1 hour
return Math.ceil(diff / 60) + '分钟前';
} else if (diff < 3600 * 24) {
return Math.ceil(diff / 3600) + '小时前';
} else if (diff < 3600 * 24 * 2) {
return '1天前';
}
if (option) {
return parseTime(parsedTime, option);
} else {
return (
d.getMonth() +
1 +
'月' +
d.getDate() +
'日' +
d.getHours() +
'时' +
d.getMinutes() +
'分'
);
}
}
4. 规则校验
4.1 验证中国大陆身份证号码(支持15位、18位,含校验码验证)
/**
* @param {string} idCard - 待验证的身份证号码字符串
* @returns {boolean} 返回 true 表示合法,false 表示不合法
*
* @example
* VerifyCertificate("110101199003072316") // => true
* VerifyCertificate("110101900307231") // => true (15位)
* VerifyCertificate("11010119900307239X") // => false
* VerifyCertificate("") // => false
*/
const VerifyCertificate = (idCard: string): boolean => {
// 空值检查
if (!idCard || idCard.trim() === '') return false;
// 格式校验正则(兼容15位、18位)
const regIdCard = /^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/;
if (!regIdCard.test(idCard)) return false;
// 18位身份证校验
if (idCard.length === 18) {
const Wi = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; // 加权因子
const Y = [1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2]; // 校验码对应值
let sum = 0;
for (let i = 0; i < 17; i++) {
sum += parseInt(idCard[i], 10) * Wi[i];
}
const mod = sum % 11;
const lastChar = idCard[17].toUpperCase();
// 判断最后一位是否匹配
if (mod === 2) {
return lastChar === 'X';
} else {
return lastChar === Y[mod].toString();
}
// 15位身份证号校验
} else if (idCard.length === 15) {
const testV15 = /^[1-9]\d{5}\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx]$/i;
return testV15.test(idCard);
// 16位老版身份证号校验(非标准,视需求保留)
} else if (idCard.length === 16) {
const testV16 = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{2}$/;
return testV16.test(idCard);
}
return false;
};
4.2 使用 Luhn 算法校验银行卡号是否有效
/**
* Luhn 算法用于验证银行卡号码的校验位(即最后一位),以确保输入的卡号格式正确。
* 该算法不验证卡号是否真实存在,仅用于初步校验格式合法性。
*
* @param {string} bankno - 需要校验的银行卡号(包含最后一位校验位)
* @returns {boolean} 校验结果,true 表示银行卡号合法,false 表示不合法
*
* @example
* luhnCheck("6225880123456789"); // 返回 true 或 false,取决于卡号是否符合 Luhn 规则
*/
function luhnCheck(bankno: string): boolean {
let lastNum = bankno.substr(bankno.length - 1, 1); // 取出最后一位
let first15Num = bankno.substr(0, bankno.length - 1); // 前n-1位
const newArr: number[] = [];
for (let i = first15Num.length - 1; i > -1; i--) {
newArr.push(parseInt(first15Num[i]));
}
const arrJiShu: number[] = []; // 奇数位*2 <9
const arrJiShu2: number[] = []; // 奇数位*2 >=9
const arrOuShu: number[] = []; // 偶数位数组
for (let j = 0; j < newArr.length; j++) {
if ((j + 1) % 2 === 1) { // 奇数位
const doubled = newArr[j] * 2;
if (doubled < 9) {
arrJiShu.push(doubled);
} else {
arrJiShu2.push(doubled);
}
} else {
arrOuShu.push(newArr[j]);
}
}
const jishu_child1: number[] = []; // 分割后的个位
const jishu_child2: number[] = []; // 分割后的十位
for (let h = 0; h < arrJiShu2.length; h++) {
jishu_child1.push(arrJiShu2[h] % 10);
jishu_child2.push(Math.floor(arrJiShu2[h] / 10));
}
const sumJiShu = arrJiShu.reduce((a, b) => a + b, 0);
const sumOuShu = arrOuShu.reduce((a, b) => a + b, 0);
const sumJiShuChild1 = jishu_child1.reduce((a, b) => a + b, 0);
const sumJiShuChild2 = jishu_child2.reduce((a, b) => a + b, 0);
const sumTotal = sumJiShu + sumOuShu + sumJiShuChild1 + sumJiShuChild2;
const k = sumTotal % 10 === 0 ? 10 : sumTotal % 10;
const luhn = 10 - k;
return parseInt(lastNum) === luhn;
}
4.3 验电话号码是否符合中国大陆的手机号或固定电话格式
/**
* 该函数支持校验:
* - 中国大陆手机号:以13、14、15、17、18、19开头的11位数字(含国际区号前缀可选)
* - 固定电话号:格式为 区号-电话号码(如010-12345678)
*
* @param {string} phoneno - 需要校验的电话号码字符串
* @returns {boolean} 校验结果,true 表示电话号码格式合法,false 表示不合法
*
* @example
* telCheck("13812345678"); // 返回 true
* telCheck("+8613812345678"); // 返回 true
* telCheck("010-12345678"); // 返回 true
* telCheck("1234567"); // 返回 false
*/
function telCheck(phoneno: string) {
const phonerule =
/^((\+?86)|(\(\+86\)))?(13[012356789][0-9]{8}|15[012356789][0-9]{8}|18[02356789][0-9]{8}|147[0-9]{8}|1349[0-9]{7})$/;
const landlinerule = /^([0-9]{3,4}-)?[0-9]{7,8}$/;
if (phonerule.test(phoneno) || landlinerule.test(phoneno)) {
return true;
} else {
return false;
}
}
4.4 校验字符串是否符合标准电子邮件地址格式
/**
* 该函数使用正则表达式校验邮箱格式,要求:
* - 用户名部分由字母、数字、下划线组成
* - 域名部分由字母和数字组成,并以2到4个小写字母的顶级域结尾(如 .com, .org)
*
* @param {string} email - 需要校验的电子邮件地址
* @returns {boolean} 校验结果,true 表示邮箱格式合法,false 表示不合法
*
* @example
* emailCheck("user@example.com"); // 返回 true
* emailCheck("user.name@domain.co.uk"); // 返回 true
* emailCheck("invalid-email@domain"); // 返回 false
*/
function emailCheck(email: string) {
const emailrule = /^\w+@[a-z0-9]+\.[a-z]{2,4}$/;
if (emailrule.test(email)) {
return true;
} else {
return false;
}
}
总结
以上是一些常用的工具类和方法,涵盖了字符串处理、日期格式化、数组、对象等方面。通过这些工具类的封装,可以大大提升开发效率并保持代码的一致性。在实际开发中,可以根据具体需求进一步扩展和完善这些工具类。