UNPKG

vue-supervalidator

Version:

🚀 The most powerful Vue 3 form validation library with 226+ built-in rules

1,322 lines (1,318 loc) 83.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var vue = require('vue'); // 核心校验规则实现 // 此文件包含所有基础规则,不依赖其他规则文件 // 工具函数 const safeValue = (value) => { if (value === null || value === undefined) return ''; return value; }; const isEmpty = (value) => { if (value === null || value === undefined) return true; if (typeof value === 'string') return value.trim().length === 0; if (Array.isArray(value)) return value.length === 0; if (typeof value === 'object') return Object.keys(value).length === 0; return false; }; const getFieldValue$1 = (formData, field) => { if (!formData) return undefined; return formData[field]; }; // 核心校验规则(基础部分) const coreRules = { // ==================== 基础校验 ==================== required: (value) => { if (value === null || value === undefined) return false; if (typeof value === 'string') return value.trim().length > 0; if (Array.isArray(value)) return value.length > 0; if (typeof value === 'object') return Object.keys(value).length > 0; if (typeof value === 'boolean') return true; return value !== ''; }, requiredIf: (value, condition, formData) => { const shouldBeRequired = typeof condition === 'function' ? condition(formData) : !!getFieldValue$1(formData, condition); if (!shouldBeRequired) return true; return coreRules.required(value); }, requiredUnless: (value, condition, formData) => { const shouldBeRequired = typeof condition === 'function' ? !condition(formData) : !getFieldValue$1(formData, condition); if (!shouldBeRequired) return true; return coreRules.required(value); }, requiredWith: (value, fields, formData) => { const fieldArray = Array.isArray(fields) ? fields : [fields]; const hasAnyValue = fieldArray.some(field => !isEmpty(getFieldValue$1(formData, field))); if (!hasAnyValue) return true; return coreRules.required(value); }, requiredWithAll: (value, fields, formData) => { const allHaveValue = fields.every(field => !isEmpty(getFieldValue$1(formData, field))); if (!allHaveValue) return true; return coreRules.required(value); }, requiredWithout: (value, fields, formData) => { const fieldArray = Array.isArray(fields) ? fields : [fields]; const hasAnyEmpty = fieldArray.some(field => isEmpty(getFieldValue$1(formData, field))); if (!hasAnyEmpty) return true; return coreRules.required(value); }, requiredWithoutAll: (value, fields, formData) => { const allEmpty = fields.every(field => isEmpty(getFieldValue$1(formData, field))); if (!allEmpty) return true; return coreRules.required(value); }, min: (value, min) => { const num = Number(value); return !isNaN(num) && num >= min; }, max: (value, max) => { const num = Number(value); return !isNaN(num) && num <= max; }, between: (value, range) => { const num = Number(value); return !isNaN(num) && num >= range[0] && num <= range[1]; }, minLength: (value, minLength) => { const str = String(safeValue(value)); return str.length >= minLength; }, maxLength: (value, maxLength) => { const str = String(safeValue(value)); return str.length <= maxLength; }, length: (value, length) => { const str = String(safeValue(value)); return str.length === length; }, lengthBetween: (value, range) => { const str = String(safeValue(value)); return str.length >= range[0] && str.length <= range[1]; }, pattern: (value, pattern) => { if (typeof value !== 'string') return false; try { return pattern.test(value); } catch { return false; } }, notPattern: (value, pattern) => { if (typeof value !== 'string') return true; try { return !pattern.test(value); } catch { return true; } }, // ==================== 邮箱和通讯 ==================== email: (value) => { const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; return typeof value === 'string' && emailRegex.test(value); }, emailStrict: (value) => { const strictEmailRegex = /^[a-zA-Z0-9]([a-zA-Z0-9._-]{0,62}[a-zA-Z0-9])?@[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$/; return typeof value === 'string' && strictEmailRegex.test(value); }, emails: (value) => { if (typeof value !== 'string') return false; const emails = value.split(',').map(e => e.trim()); return emails.every(email => coreRules.email(email)); }, phone: (value) => { const phoneRegex = /^1[3-9]\d{9}$/; return typeof value === 'string' && phoneRegex.test(value); }, phoneLoose: (value) => { const phoneRegex = /^(1[3-9]\d{9}|0\d{2,3}-?\d{7,8})$/; return typeof value === 'string' && phoneRegex.test(value); }, phoneChinaMobile: (value) => { const mobileRegex = /^1(3[4-9]|4[7]|5[0-27-9]|7[8]|8[2-478]|9[8])\d{8}$/; return typeof value === 'string' && mobileRegex.test(value); }, phoneChinaUnicom: (value) => { const unicomRegex = /^1(3[0-2]|4[5]|5[56]|66|7[156]|8[56])\d{8}$/; return typeof value === 'string' && unicomRegex.test(value); }, phoneChinaTelecom: (value) => { const telecomRegex = /^1(33|49|53|7[37]|8[019]|9[19])\d{8}$/; return typeof value === 'string' && telecomRegex.test(value); }, phoneUS: (value) => { const usPhoneRegex = /^(\+1)?[-.\s]?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/; return typeof value === 'string' && usPhoneRegex.test(value); }, phoneUK: (value) => { const ukPhoneRegex = /^(\+44|0)[1-9]\d{9,10}$/; return typeof value === 'string' && ukPhoneRegex.test(value); }, phoneJP: (value) => { const jpPhoneRegex = /^(\+81|0)[0-9]{9,10}$/; return typeof value === 'string' && jpPhoneRegex.test(value); }, phoneKR: (value) => { const krPhoneRegex = /^(\+82|0)1[0-9]{8,9}$/; return typeof value === 'string' && krPhoneRegex.test(value); }, phoneInternational: (value) => { const intlPhoneRegex = /^\+[1-9]\d{1,14}$/; return typeof value === 'string' && intlPhoneRegex.test(value); }, tel: (value) => { const telRegex = /^0\d{2,3}-?\d{7,8}$/; return typeof value === 'string' && telRegex.test(value); }, telWithArea: (value) => { const telRegex = /^0\d{2,3}-\d{7,8}$/; return typeof value === 'string' && telRegex.test(value); }, mobile: (value) => { return coreRules.phone(value); }, fax: (value) => { return coreRules.tel(value); }, // ==================== 网络相关 ==================== url: (value) => { try { const url = new URL(value); return ['http:', 'https:', 'ftp:'].includes(url.protocol); } catch { return false; } }, urlHttp: (value) => { try { const url = new URL(value); return ['http:', 'https:'].includes(url.protocol); } catch { return false; } }, urlFtp: (value) => { try { const url = new URL(value); return url.protocol === 'ftp:'; } catch { return false; } }, urlWebsocket: (value) => { try { const url = new URL(value); return ['ws:', 'wss:'].includes(url.protocol); } catch { return false; } }, domain: (value) => { const domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/; return typeof value === 'string' && domainRegex.test(value); }, domainStrict: (value) => { const strictDomainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$/; return typeof value === 'string' && strictDomainRegex.test(value) && !value.includes('_'); }, subdomain: (value) => { const subdomainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/; return typeof value === 'string' && subdomainRegex.test(value); }, ip: (value) => { return coreRules.ipv4(value) || coreRules.ipv6(value); }, ipv4: (value) => { const ipv4Regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; return typeof value === 'string' && ipv4Regex.test(value); }, ipv4WithMask: (value) => { const ipv4MaskRegex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[12]?[0-9])$/; return typeof value === 'string' && ipv4MaskRegex.test(value); }, ipv4Private: (value) => { if (!coreRules.ipv4(value)) return false; const parts = value.split('.').map(Number); return (parts[0] === 10) || (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) || (parts[0] === 192 && parts[1] === 168); }, ipv4Public: (value) => { return coreRules.ipv4(value) && !coreRules.ipv4Private(value); }, ipv6: (value) => { const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; return typeof value === 'string' && ipv6Regex.test(value); }, ipv6Compressed: (value) => { return coreRules.ipv6(value) && value.includes('::'); }, port: (value) => { const num = Number(value); return Number.isInteger(num) && num >= 1 && num <= 65535; }, portRange: (value, range) => { const num = Number(value); return Number.isInteger(num) && num >= range[0] && num <= range[1]; }, mac: (value) => { const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; return typeof value === 'string' && macRegex.test(value); }, macWithSeparator: (value, separator) => { const parts = value.split(separator); if (parts.length !== 6) return false; return parts.every((part) => /^[0-9A-Fa-f]{2}$/.test(part)); }, // ==================== 数字相关 ==================== number: (value) => { return !isNaN(Number(value)) && value !== '' && value !== null; }, numeric: (value) => { return typeof value === 'string' && /^\d+$/.test(value); }, integer: (value) => { const num = Number(value); return !isNaN(num) && Number.isInteger(num); }, float: (value) => { const num = Number(value); return !isNaN(num) && !Number.isInteger(num); }, decimal: (value, places) => { const regex = new RegExp(`^-?\\d+\\.\\d{${places}}$`); return typeof value === 'string' && regex.test(value); }, decimalBetween: (value, range) => { const match = String(value).match(/\.(\d+)$/); if (!match) return range[0] === 0; const places = match[1].length; return places >= range[0] && places <= range[1]; }, positive: (value) => { const num = Number(value); return !isNaN(num) && num > 0; }, negative: (value) => { const num = Number(value); return !isNaN(num) && num < 0; }, nonNegative: (value) => { const num = Number(value); return !isNaN(num) && num >= 0; }, nonPositive: (value) => { const num = Number(value); return !isNaN(num) && num <= 0; }, positiveInteger: (value) => { const num = Number(value); return Number.isInteger(num) && num > 0; }, negativeInteger: (value) => { const num = Number(value); return Number.isInteger(num) && num < 0; }, even: (value) => { const num = Number(value); return Number.isInteger(num) && num % 2 === 0; }, odd: (value) => { const num = Number(value); return Number.isInteger(num) && num % 2 !== 0; }, divisibleBy: (value, divisor) => { const num = Number(value); return !isNaN(num) && num % divisor === 0; }, percentage: (value) => { const num = Number(value); return !isNaN(num) && num >= 0 && num <= 100; }, percentageStrict: (value) => { const percentRegex = /^(100|[1-9]?\d)%$/; return typeof value === 'string' && percentRegex.test(value); }, scientificNotation: (value) => { const sciRegex = /^[+-]?\d+(\.\d+)?[eE][+-]?\d+$/; return typeof value === 'string' && sciRegex.test(value); }, binary: (value) => { const binaryRegex = /^[01]+$/; return typeof value === 'string' && binaryRegex.test(value); }, octal: (value) => { const octalRegex = /^[0-7]+$/; return typeof value === 'string' && octalRegex.test(value); }, // 注意:更多规则实现请参考 rules-extended.ts // 这里只包含核心规则,避免文件过大 }; // 扩展的内置规则 const extendedRules = { // ==================== 身份证件 ==================== idCard: (value) => { if (typeof value !== 'string') return false; // 18位身份证号码校验 const idCardRegex = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/; if (!idCardRegex.test(value)) return false; // 校验码验证 const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']; let sum = 0; for (let i = 0; i < 17; i++) { sum += parseInt(value[i]) * weights[i]; } const checkCode = checkCodes[sum % 11]; return value[17].toUpperCase() === checkCode; }, idCard15: (value) => { const idCard15Regex = /^[1-9]\d{7}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}$/; return typeof value === 'string' && idCard15Regex.test(value); }, idCard18: (value) => { return extendedRules.idCard(value); }, passport: (value) => { const passportRegex = /^[A-Z0-9]{5,20}$/; return typeof value === 'string' && passportRegex.test(value); }, passportCN: (value) => { // 中国护照:E开头 + 8位数字 或 G开头 + 8位数字 const cnPassportRegex = /^[EG]\d{8}$/; return typeof value === 'string' && cnPassportRegex.test(value); }, passportUS: (value) => { const usPassportRegex = /^[0-9]{9}$/; return typeof value === 'string' && usPassportRegex.test(value); }, passportUK: (value) => { const ukPassportRegex = /^[0-9]{9}$/; return typeof value === 'string' && ukPassportRegex.test(value); }, drivingLicense: (value) => { // 中国驾驶证:18位 const drivingLicenseRegex = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{4}$/; return typeof value === 'string' && drivingLicenseRegex.test(value); }, militaryId: (value) => { const militaryIdRegex = /^[a-zA-Z0-9]{7,20}$/; return typeof value === 'string' && militaryIdRegex.test(value); }, hkMacaoPass: (value) => { // 港澳通行证 const hkMacaoPassRegex = /^[HMhm]\d{8,10}$/; return typeof value === 'string' && hkMacaoPassRegex.test(value); }, taiwanPass: (value) => { // 台湾通行证 const taiwanPassRegex = /^\d{8,10}$/; return typeof value === 'string' && taiwanPassRegex.test(value); }, businessLicense: (value) => { // 营业执照:15位或18位 const businessLicenseRegex = /^([0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}|[1-9]\d{14})$/; return typeof value === 'string' && businessLicenseRegex.test(value); }, organizationCode: (value) => { // 组织机构代码:8位数字或字母 + 1位校验码 const orgCodeRegex = /^[A-Z0-9]{8}-[A-Z0-9]$/; return typeof value === 'string' && orgCodeRegex.test(value); }, socialCreditCode: (value) => { // 统一社会信用代码:18位 const creditCodeRegex = /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/; return typeof value === 'string' && creditCodeRegex.test(value); }, taxId: (value) => { // 纳税人识别号:15位、17位或18位 const taxIdRegex = /^[A-Z0-9]{15}$|^[A-Z0-9]{17}$|^[A-Z0-9]{18}$/; return typeof value === 'string' && taxIdRegex.test(value); }, // ==================== 银行和金融 ==================== bankCard: (value) => { if (typeof value !== 'string') return false; const cardNo = value.replace(/\s/g, ''); // Luhn算法校验 if (!/^\d{12,19}$/.test(cardNo)) return false; let sum = 0; let isEven = false; for (let i = cardNo.length - 1; i >= 0; i--) { let digit = parseInt(cardNo[i]); if (isEven) { digit *= 2; if (digit > 9) digit -= 9; } sum += digit; isEven = !isEven; } return sum % 10 === 0; }, bankCardCN: (value) => { // 中国银行卡:16-19位 if (!extendedRules.bankCard(value)) return false; const cardNo = value.replace(/\s/g, ''); return cardNo.length >= 16 && cardNo.length <= 19; }, creditCard: (value) => { return extendedRules.bankCard(value); }, debitCard: (value) => { return extendedRules.bankCard(value); }, visa: (value) => { const visaRegex = /^4[0-9]{12}(?:[0-9]{3})?$/; return typeof value === 'string' && visaRegex.test(value.replace(/\s/g, '')); }, mastercard: (value) => { const mastercardRegex = /^5[1-5][0-9]{14}$/; return typeof value === 'string' && mastercardRegex.test(value.replace(/\s/g, '')); }, amex: (value) => { const amexRegex = /^3[47][0-9]{13}$/; return typeof value === 'string' && amexRegex.test(value.replace(/\s/g, '')); }, unionPay: (value) => { const unionPayRegex = /^62[0-9]{14,17}$/; return typeof value === 'string' && unionPayRegex.test(value.replace(/\s/g, '')); }, jcb: (value) => { const jcbRegex = /^(?:2131|1800|35\d{3})\d{11}$/; return typeof value === 'string' && jcbRegex.test(value.replace(/\s/g, '')); }, iban: (value) => { const ibanRegex = /^[A-Z]{2}\d{2}[A-Z0-9]{1,30}$/; return typeof value === 'string' && ibanRegex.test(value.replace(/\s/g, '')); }, swift: (value) => { const swiftRegex = /^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/; return typeof value === 'string' && swiftRegex.test(value); }, bic: (value) => { return extendedRules.swift(value); }, currency: (value) => { const currencyRegex = /^[A-Z]{3}$/; return typeof value === 'string' && currencyRegex.test(value); }, money: (value) => { const moneyRegex = /^-?\d{1,3}(,\d{3})*(\.\d{1,2})?$|^-?\d+(\.\d{1,2})?$/; return typeof value === 'string' && moneyRegex.test(value); }, moneyPositive: (value) => { const moneyRegex = /^\d{1,3}(,\d{3})*(\.\d{1,2})?$|^\d+(\.\d{1,2})?$/; if (typeof value !== 'string' || !moneyRegex.test(value)) return false; const num = parseFloat(value.replace(/,/g, '')); return num > 0; }, stockCode: (value) => { const stockCodeRegex = /^[A-Z0-9]{1,10}$/; return typeof value === 'string' && stockCodeRegex.test(value); }, stockCodeCN: (value) => { // 中国股票代码:6位数字 const cnStockRegex = /^[0-9]{6}$/; return typeof value === 'string' && cnStockRegex.test(value); }, stockCodeUS: (value) => { // 美股代码:1-5个大写字母 const usStockRegex = /^[A-Z]{1,5}$/; return typeof value === 'string' && usStockRegex.test(value); }, // ==================== 邮编和地址 ==================== zipCode: (value) => { const zipCodeRegex = /^[1-9]\d{5}$/; return typeof value === 'string' && zipCodeRegex.test(value); }, zipCodeUS: (value) => { const usZipRegex = /^\d{5}(-\d{4})?$/; return typeof value === 'string' && usZipRegex.test(value); }, zipCodeUK: (value) => { const ukPostcodeRegex = /^[A-Z]{1,2}\d{1,2}[A-Z]?\s?\d[A-Z]{2}$/i; return typeof value === 'string' && ukPostcodeRegex.test(value); }, zipCodeJP: (value) => { const jpZipRegex = /^\d{3}-?\d{4}$/; return typeof value === 'string' && jpZipRegex.test(value); }, postCode: (value) => { return extendedRules.zipCode(value); }, address: (value) => { return typeof value === 'string' && value.trim().length >= 5; }, addressCN: (value) => { // 中国地址:包含省市区等关键字 const cnAddressRegex = /[\u4e00-\u9fa5]{2,}(省|市|区|县|镇|乡|街道|路|号|栋|单元|室)/; return typeof value === 'string' && cnAddressRegex.test(value); }, latitude: (value) => { const num = Number(value); return !isNaN(num) && num >= -90 && num <= 90; }, longitude: (value) => { const num = Number(value); return !isNaN(num) && num >= -180 && num <= 180; }, coordinates: (value) => { const coordRegex = /^-?\d+(\.\d+)?,\s*-?\d+(\.\d+)?$/; if (typeof value !== 'string' || !coordRegex.test(value)) return false; const [lat, lng] = value.split(',').map(s => parseFloat(s.trim())); return extendedRules.latitude(lat) && extendedRules.longitude(lng); }, // ==================== 日期和时间 ==================== date: (value) => { if (value instanceof Date) return !isNaN(value.getTime()); const dateRegex = /^\d{4}[-/](0[1-9]|1[0-2])[-/](0[1-9]|[12]\d|3[01])$/; return typeof value === 'string' && dateRegex.test(value) && !isNaN(Date.parse(value)); }, dateYMD: (value) => { const ymdRegex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/; return typeof value === 'string' && ymdRegex.test(value); }, dateMDY: (value) => { const mdyRegex = /^(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])\/\d{4}$/; return typeof value === 'string' && mdyRegex.test(value); }, dateDMY: (value) => { const dmyRegex = /^(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/\d{4}$/; return typeof value === 'string' && dmyRegex.test(value); }, dateISO: (value) => { const isoRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/; return typeof value === 'string' && isoRegex.test(value); }, time: (value) => { const timeRegex = /^([01]\d|2[0-3]):([0-5]\d)(:[0-5]\d)?$/; return typeof value === 'string' && timeRegex.test(value); }, time12: (value) => { const time12Regex = /^(0?[1-9]|1[0-2]):[0-5]\d\s?(AM|PM|am|pm)$/; return typeof value === 'string' && time12Regex.test(value); }, time24: (value) => { const time24Regex = /^([01]\d|2[0-3]):[0-5]\d$/; return typeof value === 'string' && time24Regex.test(value); }, timeWithSeconds: (value) => { const timeRegex = /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/; return typeof value === 'string' && timeRegex.test(value); }, dateTime: (value) => { if (value instanceof Date) return !isNaN(value.getTime()); return !isNaN(Date.parse(value)); }, dateTimeISO: (value) => { return extendedRules.dateISO(value); }, timestamp: (value) => { const num = Number(value); return Number.isInteger(num) && num > 0 && num < 2147483647; // Unix timestamp (32-bit) }, timestampMs: (value) => { const num = Number(value); return Number.isInteger(num) && num > 0; }, dateAfter: (value, compareDate) => { const date = new Date(value); const compare = new Date(compareDate); return !isNaN(date.getTime()) && !isNaN(compare.getTime()) && date > compare; }, dateBefore: (value, compareDate) => { const date = new Date(value); const compare = new Date(compareDate); return !isNaN(date.getTime()) && !isNaN(compare.getTime()) && date < compare; }, dateBetween: (value, range) => { const date = new Date(value); const start = new Date(range[0]); const end = new Date(range[1]); return !isNaN(date.getTime()) && date >= start && date <= end; }, dateEquals: (value, compareDate) => { const date = new Date(value); const compare = new Date(compareDate); return date.toDateString() === compare.toDateString(); }, timeAfter: (value, compareTime) => { return value > compareTime; }, timeBefore: (value, compareTime) => { return value < compareTime; }, birthday: (value) => { const date = new Date(value); const now = new Date(); const age = now.getFullYear() - date.getFullYear(); return !isNaN(date.getTime()) && age >= 0 && age <= 150; }, age: (value, range) => { const birthDate = new Date(value); const now = new Date(); let age = now.getFullYear() - birthDate.getFullYear(); const monthDiff = now.getMonth() - birthDate.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && now.getDate() < birthDate.getDate())) { age--; } return age >= range[0] && age <= range[1]; }, futureDate: (value) => { const date = new Date(value); const now = new Date(); return !isNaN(date.getTime()) && date > now; }, pastDate: (value) => { const date = new Date(value); const now = new Date(); return !isNaN(date.getTime()) && date < now; }, today: (value) => { const date = new Date(value); const now = new Date(); return date.toDateString() === now.toDateString(); }, weekday: (value) => { const date = new Date(value); const day = date.getDay(); return day >= 1 && day <= 5; }, weekend: (value) => { const date = new Date(value); const day = date.getDay(); return day === 0 || day === 6; }, // ==================== 中文相关 ==================== chinese: (value) => { const chineseRegex = /^[\u4e00-\u9fa5]+$/; return typeof value === 'string' && chineseRegex.test(value); }, chineseAndNumber: (value) => { const regex = /^[\u4e00-\u9fa5\d]+$/; return typeof value === 'string' && regex.test(value); }, chineseAndLetter: (value) => { const regex = /^[\u4e00-\u9fa5a-zA-Z]+$/; return typeof value === 'string' && regex.test(value); }, chineseAndSymbol: (value) => { const regex = /^[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]+$/; return typeof value === 'string' && regex.test(value); }, noChinese: (value) => { const chineseRegex = /[\u4e00-\u9fa5]/; return typeof value === 'string' && !chineseRegex.test(value); }, chineseName: (value) => { const nameRegex = /^[\u4e00-\u9fa5]{2,10}$/; return typeof value === 'string' && nameRegex.test(value); }, chineseNameWithDot: (value) => { const nameRegex = /^[\u4e00-\u9fa5·]{2,10}$/; return typeof value === 'string' && nameRegex.test(value); }, simplifiedChinese: (value) => { return extendedRules.chinese(value); }, traditionalChinese: (value) => { return extendedRules.chinese(value); }, // ==================== 英文和字符 ==================== alpha: (value) => { const alphaRegex = /^[a-zA-Z]+$/; return typeof value === 'string' && alphaRegex.test(value); }, alphaNum: (value) => { const alphaNumRegex = /^[a-zA-Z0-9]+$/; return typeof value === 'string' && alphaNumRegex.test(value); }, alphaDash: (value) => { const alphaDashRegex = /^[a-zA-Z0-9_-]+$/; return typeof value === 'string' && alphaDashRegex.test(value); }, alphaSpace: (value) => { const alphaSpaceRegex = /^[a-zA-Z\s]+$/; return typeof value === 'string' && alphaSpaceRegex.test(value); }, alphaNumSpace: (value) => { const alphaNumSpaceRegex = /^[a-zA-Z0-9\s]+$/; return typeof value === 'string' && alphaNumSpaceRegex.test(value); }, lowercase: (value) => { const lowercaseRegex = /^[a-z]+$/; return typeof value === 'string' && lowercaseRegex.test(value); }, uppercase: (value) => { const uppercaseRegex = /^[A-Z]+$/; return typeof value === 'string' && uppercaseRegex.test(value); }, capitalizeFirst: (value) => { const capitalizeRegex = /^[A-Z][a-z]*$/; return typeof value === 'string' && capitalizeRegex.test(value); }, camelCase: (value) => { const camelCaseRegex = /^[a-z][a-zA-Z0-9]*$/; return typeof value === 'string' && camelCaseRegex.test(value); }, snakeCase: (value) => { const snakeCaseRegex = /^[a-z][a-z0-9_]*$/; return typeof value === 'string' && snakeCaseRegex.test(value); }, kebabCase: (value) => { const kebabCaseRegex = /^[a-z][a-z0-9-]*$/; return typeof value === 'string' && kebabCaseRegex.test(value); }, ascii: (value) => { const asciiRegex = /^[\x00-\x7F]+$/; return typeof value === 'string' && asciiRegex.test(value); }, asciiPrintable: (value) => { const asciiPrintableRegex = /^[\x20-\x7E]+$/; return typeof value === 'string' && asciiPrintableRegex.test(value); }, unicode: (value) => { return typeof value === 'string' && value.length > 0; }, // ==================== 特殊格式 ==================== username: (value) => { const usernameRegex = /^[a-zA-Z0-9_]{4,16}$/; return typeof value === 'string' && usernameRegex.test(value); }, usernameStrict: (value) => { const usernameStrictRegex = /^[a-zA-Z][a-zA-Z0-9_]{3,15}$/; return typeof value === 'string' && usernameStrictRegex.test(value); }, password: (value) => { const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d).{6,}$/; return typeof value === 'string' && passwordRegex.test(value); }, strongPassword: (value) => { const strongRegex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/; return typeof value === 'string' && strongRegex.test(value); }, weakPassword: (value) => { return typeof value === 'string' && value.length >= 6; }, passwordCustom: (value, config) => { if (typeof value !== 'string') return false; const { minLength = 8, requireUppercase, requireLowercase, requireNumber, requireSpecial } = config; if (value.length < minLength) return false; if (requireUppercase && !/[A-Z]/.test(value)) return false; if (requireLowercase && !/[a-z]/.test(value)) return false; if (requireNumber && !/\d/.test(value)) return false; if (requireSpecial && !/[@$!%*#?&]/.test(value)) return false; return true; }, hex: (value) => { const hexRegex = /^[0-9a-fA-F]+$/; return typeof value === 'string' && hexRegex.test(value); }, hexColor: (value) => { const hexColorRegex = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; return typeof value === 'string' && hexColorRegex.test(value); }, color: (value) => { if (typeof value !== 'string') return false; const hexRegex = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; const rgbRegex = /^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/; const rgbaRegex = /^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*(0|1|0?\.\d+)\s*\)$/; return hexRegex.test(value) || rgbRegex.test(value) || rgbaRegex.test(value); }, rgb: (value) => { const rgbRegex = /^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/; return typeof value === 'string' && rgbRegex.test(value); }, rgba: (value) => { const rgbaRegex = /^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*(0|1|0?\.\d+)\s*\)$/; return typeof value === 'string' && rgbaRegex.test(value); }, hsl: (value) => { const hslRegex = /^hsl\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%\s*\)$/; return typeof value === 'string' && hslRegex.test(value); }, hsla: (value) => { const hslaRegex = /^hsla\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%\s*,\s*(0|1|0?\.\d+)\s*\)$/; return typeof value === 'string' && hslaRegex.test(value); }, base64: (value) => { const base64Regex = /^[A-Za-z0-9+/]+=*$/; return typeof value === 'string' && base64Regex.test(value) && value.length % 4 === 0; }, base64Image: (value) => { const base64ImageRegex = /^data:image\/(png|jpg|jpeg|gif|webp);base64,[A-Za-z0-9+/]+=*$/; return typeof value === 'string' && base64ImageRegex.test(value); }, base64Url: (value) => { const base64UrlRegex = /^[A-Za-z0-9_-]+=*$/; return typeof value === 'string' && base64UrlRegex.test(value); }, md5: (value) => { const md5Regex = /^[a-f0-9]{32}$/i; return typeof value === 'string' && md5Regex.test(value); }, sha1: (value) => { const sha1Regex = /^[a-f0-9]{40}$/i; return typeof value === 'string' && sha1Regex.test(value); }, sha256: (value) => { const sha256Regex = /^[a-f0-9]{64}$/i; return typeof value === 'string' && sha256Regex.test(value); }, sha512: (value) => { const sha512Regex = /^[a-f0-9]{128}$/i; return typeof value === 'string' && sha512Regex.test(value); }, uuid: (value) => { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return typeof value === 'string' && uuidRegex.test(value); }, uuidV1: (value) => { const uuidV1Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-1[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return typeof value === 'string' && uuidV1Regex.test(value); }, uuidV3: (value) => { const uuidV3Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return typeof value === 'string' && uuidV3Regex.test(value); }, uuidV4: (value) => { const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return typeof value === 'string' && uuidV4Regex.test(value); }, uuidV5: (value) => { const uuidV5Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return typeof value === 'string' && uuidV5Regex.test(value); }, jwt: (value) => { const jwtRegex = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/; return typeof value === 'string' && jwtRegex.test(value); }, // ==================== 车辆相关 ==================== licensePlate: (value) => { const plateRegex = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/; return typeof value === 'string' && plateRegex.test(value); }, licensePlateCN: (value) => { return extendedRules.licensePlate(value); }, licensePlateNewEnergy: (value) => { const newEnergyPlateRegex = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{5}[DF]$/; return typeof value === 'string' && newEnergyPlateRegex.test(value); }, vin: (value) => { const vinRegex = /^[A-HJ-NPR-Z0-9]{17}$/; return typeof value === 'string' && vinRegex.test(value); }, vinStrict: (value) => { return extendedRules.vin(value); }, engineNumber: (value) => { const engineRegex = /^[A-Z0-9]{6,20}$/; return typeof value === 'string' && engineRegex.test(value); }, // ==================== 社交媒体 ==================== qq: (value) => { const qqRegex = /^[1-9][0-9]{4,10}$/; return typeof value === 'string' && qqRegex.test(value); }, qqStrict: (value) => { const qqStrictRegex = /^[1-9][0-9]{4,10}$/; return typeof value === 'string' && qqStrictRegex.test(value) && value.length >= 5 && value.length <= 11; }, wechat: (value) => { const wechatRegex = /^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/; return typeof value === 'string' && wechatRegex.test(value); }, wechatStrict: (value) => { return extendedRules.wechat(value); }, weibo: (value) => { const weiboRegex = /^[a-zA-Z0-9_-]{4,30}$/; return typeof value === 'string' && weiboRegex.test(value); }, douyin: (value) => { const douyinRegex = /^[a-zA-Z0-9._-]{4,30}$/; return typeof value === 'string' && douyinRegex.test(value); }, twitter: (value) => { const twitterRegex = /^@?[a-zA-Z0-9_]{1,15}$/; return typeof value === 'string' && twitterRegex.test(value); }, facebook: (value) => { const facebookRegex = /^[a-zA-Z0-9.]{5,50}$/; return typeof value === 'string' && facebookRegex.test(value); }, instagram: (value) => { const instagramRegex = /^[a-zA-Z0-9._]{1,30}$/; return typeof value === 'string' && instagramRegex.test(value); }, linkedin: (value) => { const linkedinRegex = /^[a-zA-Z0-9-]{3,100}$/; return typeof value === 'string' && linkedinRegex.test(value); }, github: (value) => { const githubRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$/; return typeof value === 'string' && githubRegex.test(value); }, // ==================== 比较验证 ==================== same: (value, compareValue) => { return value === compareValue; }, different: (value, compareValue) => { return value !== compareValue; }, gt: (value, compareValue) => { return Number(value) > Number(compareValue); }, gte: (value, compareValue) => { return Number(value) >= Number(compareValue); }, lt: (value, compareValue) => { return Number(value) < Number(compareValue); }, lte: (value, compareValue) => { return Number(value) <= Number(compareValue); }, equals: (value, compareValue) => { return value == compareValue; }, notEquals: (value, compareValue) => { return value != compareValue; }, // ==================== 包含验证 ==================== in: (value, array) => { return array.includes(value); }, notIn: (value, array) => { return !array.includes(value); }, contains: (value, str) => { return typeof value === 'string' && value.includes(str); }, notContains: (value, str) => { return typeof value === 'string' && !value.includes(str); }, startsWith: (value, str) => { return typeof value === 'string' && value.startsWith(str); }, endsWith: (value, str) => { return typeof value === 'string' && value.endsWith(str); }, // ==================== 字符串验证 ==================== json: (value) => { try { JSON.parse(value); return true; } catch { return false; } }, jsonObject: (value) => { try { const parsed = JSON.parse(value); return typeof parsed === 'object' && !Array.isArray(parsed); } catch { return false; } }, jsonArray: (value) => { try { const parsed = JSON.parse(value); return Array.isArray(parsed); } catch { return false; } }, xml: (value) => { const xmlRegex = /^<[^>]+>.*<\/[^>]+>$/s; return typeof value === 'string' && xmlRegex.test(value); }, html: (value) => { const htmlRegex = /<[^>]+>/; return typeof value === 'string' && htmlRegex.test(value); }, notEmpty: (value) => { return value !== '' && value !== null && value !== undefined; }, notBlank: (value) => { return typeof value === 'string' && value.trim().length > 0; }, noWhitespace: (value) => { return typeof value === 'string' && !/\s/.test(value); }, noLeadingWhitespace: (value) => { return typeof value === 'string' && !/^\s/.test(value); }, noTrailingWhitespace: (value) => { return typeof value === 'string' && !/\s$/.test(value); }, wordCount: (value, count) => { if (typeof value !== 'string') return false; const words = value.trim().split(/\s+/).filter(w => w.length > 0); return words.length === count; }, wordCountMin: (value, min) => { if (typeof value !== 'string') return false; const words = value.trim().split(/\s+/).filter(w => w.length > 0); return words.length >= min; }, wordCountMax: (value, max) => { if (typeof value !== 'string') return false; const words = value.trim().split(/\s+/).filter(w => w.length > 0); return words.length <= max; }, wordCountBetween: (value, range) => { if (typeof value !== 'string') return false; const words = value.trim().split(/\s+/).filter(w => w.length > 0); return words.length >= range[0] && words.length <= range[1]; }, charCount: (value, count) => { return typeof value === 'string' && value.length === count; }, charCountMin: (value, min) => { return typeof value === 'string' && value.length >= min; }, charCountMax: (value, max) => { return typeof value === 'string' && value.length <= max; }, // ==================== 业务相关 ==================== studentId: (value) => { const studentIdRegex = /^\d{6,12}$/; return typeof value === 'string' && studentIdRegex.test(value); }, teacherId: (value) => { const teacherIdRegex = /^[A-Z]?\d{6,12}$/; return typeof value === 'string' && teacherIdRegex.test(value); }, classNumber: (value) => { const classRegex = /^\d{4,8}$/; return typeof value === 'string' && classRegex.test(value); }, medicalRecordNumber: (value) => { const medicalRegex = /^[A-Z0-9]{6,20}$/; return typeof value === 'string' && medicalRegex.test(value); }, prescriptionNumber: (value) => { const prescriptionRegex = /^[A-Z0-9]{8,20}$/; return typeof value === 'string' && prescriptionRegex.test(value); }, trackingNumber: (value) => { const trackingRegex = /^[A-Z0-9]{10,30}$/; return typeof value === 'string' && trackingRegex.test(value); }, trackingNumberSF: (value) => { const sfRegex = /^SF\d{12}$/; return typeof value === 'string' && sfRegex.test(value); }, trackingNumberYTO: (value) => { const ytoRegex = /^YT\d{13}$/; return typeof value === 'string' && ytoRegex.test(value); }, trackingNumberZTO: (value) => { const ztoRegex = /^\d{12,13}$/; return typeof value === 'string' && ztoRegex.test(value); }, trackingNumberYD: (value) => { const ydRegex = /^\d{13}$/; return typeof value === 'string' && ydRegex.test(value); }, invoiceNumber: (value) => { const invoiceRegex = /^\d{8}$/; return typeof value === 'string' && invoiceRegex.test(value); }, invoiceCode: (value) => { const invoiceCodeRegex = /^\d{10,12}$/; return typeof value === 'string' && invoiceCodeRegex.test(value); }, isbn: (value) => { const isbnRegex = /^(97[89])?\d{9}[\dXx]$/; return typeof value === 'string' && isbnRegex.test(value.replace(/[-\s]/g, '')); }, isbn10: (value) => { const isbn10Regex = /^\d{9}[\dXx]$/; return typeof value === 'string' && isbn10Regex.test(value.replace(/[-\s]/g, '')); }, isbn13: (value) => { const isbn13Regex = /^97[89]\d{10}$/; return typeof value === 'string' && isbn13Regex.test(value.replace(/[-\s]/g, '')); }, issn: (value) => { const issnRegex = /^\d{4}-\d{3}[\dXx]$/; return typeof value === 'string' && issnRegex.test(value); }, // ==================== 安全相关 ==================== noSqlInjection: (value) => { if (typeof value !== 'string') return true; const sqlKeywords = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE', 'ALTER', 'EXEC', 'UNION', '--', ';', '/*', '*/']; const upperValue = value.toUpperCase(); return !sqlKeywords.some(keyword => upperValue.includes(keyword)); }, noXss: (value) => { if (typeof value !== 'string') return true; const xssPatterns = ['<script', 'javascript:', 'onerror=', 'onload=', 'onclick=']; const lowerValue = value.toLowerCase(); return !xssPatterns.some(pattern => lowerValue.includes(pattern)); }, noScript: (value) => { if (typeof value !== 'string') return true; const scriptRegex = /<script[\s\S]*?>[\s\S]*?<\/script>/gi; return !scriptRegex.test(value); }, safeString: (value) => { return extendedRules.noSqlInjection(value) && extendedRules.noXss(value) && extendedRules.noScript(value); }, // ==================== 数组和对象 ==================== arrayLength: (value, length) => { return Array.isArray(value) && value.length === length; }, arrayMinLength: (value, min) => { return Array.isArray(value) && value.length >= min; }, arrayMaxLength: (value, max) => { return Array.isArray(value) && value.length <= max; }, arrayUnique: (value) => { if (!Array.isArray(value)) return false; return new Set(value).size === value.length; }, objectKeys: (value, keys) => { if (typeof value !== 'object' || value === null) return false; return keys.every(key => key in value); }, objectKeysOptional: (value, keys) => { if (typeof value !== 'object' || value === null) return false; const valueKeys = Object.keys(value); return valueKeys.every(key => keys.includes(key)); }, // ==================== 文件相关 ==================== fileExtension: (file, extensions) => { if (!file || !file.name) return false; const ext = file.name.split('.').pop()?.toLowerCase(); return ext ? extensions.includes(ext) : false; }, fileSize: (file, maxSize) => { if (!file || !file.size) return false; return file.size <= maxSize; }, fileSizeMin: (file, minSize) => { if (!file || !file.size) return false; return file.size >= minSize; }, fileSizeMax: (file, maxSize) => { if (!file || !file.size) return false; return file.size <= maxSize; }, fileSizeBetween: (file, range) => { if (!file || !file.size) return false; return file.size >= range[0] && file.size <= range[1]; }, imageFormat: (file) => { const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']; return extendedRules.fileExtension(file, imageExtensions); }, videoFormat: (file) => { const videoExtensions = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm']; return extendedRules.fileExtension(file, videoExtensions); }, audioFormat: (file) => { const audioExtensions = ['mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a']; return extendedRules.fileExtension(file, audioExtensions); }, documentFormat: (file) => { const docExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt']; return extendedRules.fileExtension(file, docExtensions); }, archiveFormat: (file) => { const archiveExtensions = ['zip', 'rar', '7z', 'tar', 'gz', 'bz2']; return extendedRules.fileExtension(file, archiveExtensions); }, imageWidth: (image, width) => { return image && image.width === width; }, imageHeight: (image, height) => { return image && image.height === height; }, imageMinWidth: (image, width) => { return image && image.width >= width; }, imageMinHeight: (image, height) => { return image && image.height >= height; }, imageMaxWidth: (image, width) => { return image && image.width <= width; }, imageMaxHeight: (image, height) => { return image && image.height <= height; }, imageRatio: (image, ratio) => { if (!image || !image.width || !image.height) return false; const actualRatio = image.width / image.height; ret