/** * 验证电子邮箱格式 */ function email(value) { return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value) } /** * 验证手机格式 */ function mobile(value) { return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value) } /** * 验证URL格式 */ function url(value) { return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/ .test(value) } /** * 验证日期格式 */ function date(value) { if (!value) return false // 判断是否数值或者字符串数值(意味着为时间戳),转为数值,否则new Date无法识别字符串时间戳 if (number(value)) value = +value return !/Invalid|NaN/.test(new Date(value).toString()) } /** * 验证ISO类型的日期格式 */ function dateISO(value) { return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value) } /** * 验证十进制数字 */ function number(value) { return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value) } /** * 验证字符串 */ function string(value) { return typeof value === 'string' } /** * 验证整数 */ function digits(value) { return /^\d+$/.test(value) } /** * 验证身份证号码 */ function idCard(value) { return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test( value ) } /** * 是否车牌号 */ function carNo(value) { value = value.trim(); // 新能源车牌 const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([A-HJ-K][A-HJ-NP-Z0-9][0-9]{4}$))/; // 旧车牌 const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/; if (value.length === 7) { return creg.test(value) } if (value.length === 8) { return xreg.test(value) } return false } /** * 金额,只允许2位小数 */ function amount(value) { // 金额,只允许保留两位小数 return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value) } /** * 中文 */ function chinese(value) { const reg = /^[\u4e00-\u9fa5]+$/gi return reg.test(value) } /** * 只能输入字母 */ function letter(value) { return /^[a-zA-Z]*$/.test(value) } /** * 只能是字母或者数字 */ function enOrNum(value) { // 英文或者数字 const reg = /^[0-9a-zA-Z]*$/g return reg.test(value) } /** * 验证是否包含某个值 */ function contains(value, param) { return value.indexOf(param) >= 0 } /** * 验证一个值范围[min, max] */ function range(value, param) { return value >= param[0] && value <= param[1] } /** * 验证一个长度范围[min, max] */ function rangeLength(value, param) { return value.length >= param[0] && value.length <= param[1] } /** * 是否固定电话 */ function landline(value) { const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/ return reg.test(value) } /** * 判断是否为空 */ function empty(value) { switch (typeof value) { case 'undefined': return true case 'string': if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true break case 'boolean': if (!value) return true break case 'number': if (value === 0 || isNaN(value)) return true break case 'object': if (value === null || value.length === 0) return true for (const i in value) { return false } return true } return false } /** * 是否json字符串 */ function jsonString(value) { if (typeof value === 'string') { try { const obj = JSON.parse(value) if (typeof obj === 'object' && obj) { return true } return false } catch (e) { return false } } return false } /** * 是否数组 */ function array(value) { if (typeof Array.isArray === 'function') { return Array.isArray(value) } return Object.prototype.toString.call(value) === '[object Array]' } /** * 是否对象 */ function object(value) { return Object.prototype.toString.call(value) === '[object Object]' } /** * 是否短信验证码 */ function code(value, len = 6) { return new RegExp(`^\\d{${len}}$`).test(value) } /** * 是否函数方法 * @param {Object} value */ function func(value) { return typeof value === 'function' } /** * 是否promise对象 * @param {Object} value */ function promise(value) { return object(value) && func(value.then) && func(value.catch) } /** 是否图片格式 * @param {Object} value */ function image(value) { const newValue = value.split('?')[0] const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i return IMAGE_REGEXP.test(newValue) } /** * 是否视频格式 * @param {Object} value */ function video(value) { const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i return VIDEO_REGEXP.test(value) } /** * 是否为正则对象 * @param {Object} * @return {Boolean} */ function regExp(o) { return o && Object.prototype.toString.call(o) === '[object RegExp]' } /** * 通用脱敏函数 * @param {string} content - 需要脱敏的内容(邮箱/手机号/姓名) * @param {number} [type=1] - 脱敏类型:1-邮箱(默认),2-手机,3-姓名 * @returns {string} 脱敏后的内容,不符合规则返回原内容 */ function desensitizeContent(content, type = 1) { // 基础校验:内容为空或非字符串直接返回原内容 if (!content || typeof content !== 'string') { return content; } // 去除首尾空格 const trimContent = content.trim(); switch (type) { // 1 - 邮箱脱敏 case 1: { // 邮箱基本校验:包含@,且@前后有内容,总长度至少5位(如a@b.c) const emailReg = /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/; if (!emailReg.test(trimContent) || trimContent.length < 5) { return content; // 不符合邮箱格式返回原内容 } // 拆分邮箱:用户名@域名 const [username, domain] = trimContent.split('@'); // 用户名保留前2位,不足2位则保留1位,其余用*代替 const safeUsername = username.length > 2 ? username.substring(0, 2) + '*'.repeat(username.length - 2) : username.substring(0, 1) + '*'.repeat(username.length - 1); return `${safeUsername}@${domain}`; } // 2 - 手机号脱敏 case 2: { // 定义不同区号的手机号脱敏规则映射表 const phoneDesensitizeRules = { // 中国及港澳台 '+86': [3, 4, '+86', 11], // 中国大陆:11位,前3后4(138****8000) '+852': [2, 4, '+852', 8], // 中国香港:8位,前2后4(12****78) '+853': [2, 4, '+853', 8], // 中国澳门:8位,前2后4(12****78) '+886': [3, 4, '+886', 10], // 中国台湾:10位,前3后4(098****7890) // 亚洲 '+81': [3, 4, '+81', 11], // 日本:11位,前3后4(090****1234) '+82': [3, 4, '+82', 11], // 韩国:11位,前3后4(010****1234) '+65': [2, 4, '+65', 8], // 新加坡:8位,前2后4(61****78) '+60': [3, 4, '+60', 10], // 马来西亚:10位,前3后4(012****7890) '+91': [3, 4, '+91', 10], // 印度:10位,前3后4(987****6543) '+66': [3, 4, '+66', 10], // 泰国:10位,前3后4(081****1234) '+84': [3, 4, '+84', 10], // 越南:10位,前3后4(091****1234) '+92': [3, 4, '+92', 11], // 巴基斯坦:11位,前3后4(0300****1234) '+94': [3, 4, '+94', 10], // 斯里兰卡:10位,前3后4(771****1234) '+880': [3, 4, '+880', 11], // 孟加拉国:11位,前3后4(0171****1234) '+62': [3, 4, '+62', 12], // 印度尼西亚:12位,前3后4(0812****1234) '+966': [3, 4, '+966', 9], // 沙特阿拉伯:9位,前3后4(500****123) '+971': [2, 4, '+971', 9], // 阿联酋:9位,前2后4(50****1234) '+965': [2, 4, '+965', 8], // 科威特:8位,前2后4(50****123) '+974': [2, 4, '+974', 8], // 卡塔尔:8位,前2后4(50****123) '+95': [2, 4, '+95', 9], // 缅甸:9位,前2后4(90****1234) '+855': [3, 4, '+855', 9], // 柬埔寨:9位,前3后4(012****123) // 北美洲 '+1': [3, 4, '+1', 10], // 美国/加拿大:10位,前3后4(123****7890) '+52': [3, 4, '+52', 10], // 墨西哥:10位,前3后4(123****7890) // 欧洲 '+44': [3, 4, '+44', 11], // 英国:11位,前3后4(791****1234) '+33': [3, 4, '+33', 10], // 法国:10位,前3后4(601****1234) '+49': [3, 4, '+49', 11], // 德国:11位,前3后4(151****12345) '+34': [2, 4, '+34', 9], // 西班牙:9位,前2后4(60****1234) '+39': [3, 4, '+39', 10], // 意大利:10位,前3后4(333****1234) '+32': [2, 4, '+32', 9], // 比利时:9位,前2后4(47****1234) '+31': [2, 4, '+31', 9], // 荷兰:9位,前2后4(61****1234) '+46': [3, 4, '+46', 10], // 瑞典:10位,前3后4(701****1234) '+41': [2, 4, '+41', 9], // 瑞士:9位,前2后4(79****1234) '+358': [2, 4, '+358', 9], // 芬兰:9位,前2后4(40****1234) '+45': [2, 4, '+45', 8], // 丹麦:8位,前2后4(12****78) '+30': [3, 4, '+30', 10], // 希腊:10位,前3后4(690****1234) '+36': [2, 4, '+36', 9], // 匈牙利:9位,前2后4(30****1234) '+359': [2, 4, '+359', 9], // 保加利亚:9位,前2后4(88****1234) '+380': [3, 4, '+380', 10], // 乌克兰:10位,前3后4(067****1234) '+7': [3, 4, '+7', 11], // 俄罗斯/哈萨克斯坦:11位,前3后4(916****12345) '+381': [3, 4, '+381', 10], // 塞尔维亚:10位,前3后4(630****1234) // 大洋洲 '+61': [3, 4, '+61', 9], // 澳大利亚:9位,前3后4(400****123) '+64': [3, 4, '+64', 9], // 新西兰:9位,前3后4(210****123) // 非洲 '+27': [3, 4, '+27', 10], // 南非:10位,前3后4(082****1234) '+234': [3, 4, '+234', 11], // 尼日利亚:11位,前3后4(080****12345) '+254': [2, 4, '+254', 9], // 肯尼亚:9位,前2后4(70****1234) '+20': [3, 4, '+20', 10], // 埃及:10位,前3后4(010****1234) // 南美洲 '+55': [3, 4, '+55', 11], // 巴西:11位,前3后4(119****12345) '+54': [3, 4, '+54', 10], // 阿根廷:10位,前3后4(112****1234) '+57': [3, 4, '+57', 10], // 哥伦比亚:10位,前3后4(300****1234) }; // 步骤1:解析手机号(区分带区号/不带区号) let areaCode = '+86'; // 默认中国大陆区号 let purePhone = trimContent; // 匹配是否包含国际区号(+开头,1-3位数字) const areaCodeMatch = trimContent.match(/^(\+\d{1,3})(\d+)$/); if (areaCodeMatch) { areaCode = areaCodeMatch[1]; // 提取区号(如+86) purePhone = areaCodeMatch[2]; // 提取纯手机号(不含区号) } else { // 无区号时,默认按+86处理,且要求是11位数字 if (!/^\d{11}$/.test(trimContent)) { return content; // 无区号且不是11位数字,返回原内容 } } // 步骤2:获取当前区号的脱敏规则,无规则则用默认逻辑 const rule = phoneDesensitizeRules[areaCode] || [3, 4, areaCode, purePhone.length]; const [keepPrefix, keepSuffix, code, phoneLen] = rule; // 步骤3:校验手机号格式(位数匹配) if (purePhone.length !== phoneLen) { return content; // 位数不匹配,返回原内容 } // 步骤4:执行脱敏逻辑 // 计算需要隐藏的位数 const hideLength = purePhone.length - keepPrefix - keepSuffix; if (hideLength <= 0) { return content; // 无隐藏位数,返回原内容 } // 拼接脱敏后的手机号 const prefix = purePhone.substring(0, keepPrefix); const suffix = purePhone.substring(purePhone.length - keepSuffix); const hidden = '*'.repeat(hideLength); const desensitizedPhone = `${prefix}${hidden}${suffix}`; // 步骤5:返回带区号的脱敏结果(如果有区号) return areaCodeMatch ? `${code}${desensitizedPhone}` : desensitizedPhone; } // 3 - 姓名脱敏 case 3: { const nameLength = trimContent.length; // 姓名长度校验:至少1位,最多6位(常规姓名长度) if (nameLength < 1 || nameLength > 6) { return content; } // 单字姓名:直接返回 if (nameLength === 1) { return trimContent; } // 双字姓名:保留首字,第二个字用*代替(如:李*) if (nameLength === 2) { return trimContent.substring(0, 1) + '*'; } // 三字及以上:保留首尾字,中间用*代替(如:张*三、王**明) return trimContent.substring(0, 1) + '*'.repeat(nameLength - 2) + trimContent.substring(nameLength - 1); } // 未知类型:返回原内容 default: return content; } } export default { email, mobile, url, date, dateISO, number, digits, idCard, carNo, amount, chinese, letter, enOrNum, contains, range, rangeLength, empty, isEmpty: empty, jsonString, landline, object, array, code, func, promise, video, image, regExp, string, desensitizeContent }