将 web-admin 从子模块改为仓库内普通目录,便于单仓克隆与维护。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
2e9d020d50
commit
c87732fcd5
515 files changed
+74372
-1
No files matched your search
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 表单工具函数统一导出
|
||||
*
|
||||
* @module utils/form
|
||||
* @author Art Design Pro Team
|
||||
*/
|
||||
|
||||
// 表单验证器
|
||||
export * from './validator'
|
||||
|
||||
// 响应式布局
|
||||
export * from './responsive'
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 表单响应式布局工具模块
|
||||
*
|
||||
* 提供表单项在不同屏幕尺寸下的智能布局计算
|
||||
*
|
||||
* ## 主要功能
|
||||
*
|
||||
* - 响应式断点管理(xs/sm/md/lg/xl)
|
||||
* - 表单列宽自动降级(避免小屏幕压缩)
|
||||
* - 基于阈值的智能 span 计算
|
||||
* - 响应式计算器工厂函数
|
||||
* - 可配置的断点规则
|
||||
*
|
||||
* ## 使用场景
|
||||
*
|
||||
* - 表单组件响应式布局
|
||||
* - 搜索表单自适应
|
||||
* - 移动端表单优化
|
||||
* - 多列表单布局
|
||||
*
|
||||
* ## 断点说明(基于 Element Plus Grid 24 栅格系统):
|
||||
* - xs (手机): < 768px,小于 12 时降级为 24(满宽)
|
||||
* - sm (平板): ≥ 768px,小于 12 时降级为 12(半宽)
|
||||
* - md (中等屏幕): ≥ 992px,小于 8 时降级为 8(三分之一宽)
|
||||
* - lg (大屏幕): ≥ 1200px,直接使用设置的 span
|
||||
* - xl (超大屏幕): ≥ 1920px,直接使用设置的 span
|
||||
*
|
||||
* ## 核心功能
|
||||
*
|
||||
* - calculateResponsiveSpan: 计算响应式列宽
|
||||
* - createResponsiveSpanCalculator: 创建 span 计算器(柯里化)
|
||||
*
|
||||
* @module utils/form/responsive
|
||||
* @author Art Design Pro Team
|
||||
*/
|
||||
|
||||
/**
|
||||
* 响应式断点类型
|
||||
*/
|
||||
export type ResponsiveBreakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||||
|
||||
/**
|
||||
* 断点配置映射
|
||||
*/
|
||||
interface BreakpointConfig {
|
||||
/** 最小 span 阈值 */
|
||||
threshold: number
|
||||
/** 降级后的 span 值 */
|
||||
fallback: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应式断点配置
|
||||
*/
|
||||
const BREAKPOINT_CONFIG: Record<ResponsiveBreakpoint, BreakpointConfig | null> = {
|
||||
xs: { threshold: 12, fallback: 24 }, // 手机:小于 12 时使用满宽
|
||||
sm: { threshold: 12, fallback: 12 }, // 平板:小于 12 时使用半宽
|
||||
md: { threshold: 8, fallback: 8 }, // 中等屏幕:小于 8 时使用三分之一宽
|
||||
lg: null, // 大屏幕:直接使用设置的 span
|
||||
xl: null // 超大屏幕:直接使用设置的 span
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算响应式列宽
|
||||
*
|
||||
* 根据屏幕尺寸智能降级,避免小屏幕上表单项被压缩过小
|
||||
*
|
||||
* @param itemSpan 表单项自定义的 span 值
|
||||
* @param defaultSpan 默认的 span 值
|
||||
* @param breakpoint 当前断点
|
||||
* @returns 计算后的 span 值
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // 在 xs 断点下,span 为 6 会降级为 24(满宽)
|
||||
* calculateResponsiveSpan(6, 6, 'xs') // 24
|
||||
*
|
||||
* // 在 md 断点下,span 为 6 会降级为 8(三分之一宽)
|
||||
* calculateResponsiveSpan(6, 6, 'md') // 8
|
||||
*
|
||||
* // 在 lg 断点下,直接使用原始 span
|
||||
* calculateResponsiveSpan(6, 6, 'lg') // 6
|
||||
* ```
|
||||
*/
|
||||
export function calculateResponsiveSpan(
|
||||
itemSpan: number | undefined,
|
||||
defaultSpan: number,
|
||||
breakpoint: ResponsiveBreakpoint
|
||||
): number {
|
||||
const finalSpan = itemSpan ?? defaultSpan
|
||||
const config = BREAKPOINT_CONFIG[breakpoint]
|
||||
|
||||
// 如果没有配置(lg/xl),直接返回原始 span
|
||||
if (!config) {
|
||||
return finalSpan
|
||||
}
|
||||
|
||||
// 如果 span 小于阈值,使用降级值
|
||||
return finalSpan >= config.threshold ? finalSpan : config.fallback
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建响应式 span 计算器
|
||||
*
|
||||
* 返回一个函数,用于计算指定断点下的 span 值
|
||||
*
|
||||
* @param defaultSpan 默认的 span 值
|
||||
* @returns span 计算函数
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const getColSpan = createResponsiveSpanCalculator(6)
|
||||
* getColSpan(undefined, 'xs') // 24
|
||||
* getColSpan(8, 'md') // 8
|
||||
* getColSpan(12, 'lg') // 12
|
||||
* ```
|
||||
*/
|
||||
export function createResponsiveSpanCalculator(defaultSpan: number) {
|
||||
return (itemSpan: number | undefined, breakpoint: ResponsiveBreakpoint): number => {
|
||||
return calculateResponsiveSpan(itemSpan, defaultSpan, breakpoint)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* 表单验证工具模块
|
||||
*
|
||||
* 提供全面的表单字段验证功能
|
||||
*
|
||||
* ## 主要功能
|
||||
*
|
||||
* - 手机号码验证(中国大陆格式)
|
||||
* - 固定电话验证(支持区号格式)
|
||||
* - 用户账号验证(字母开头,支持数字和下划线)
|
||||
* - 密码强度验证(普通密码、强密码)
|
||||
* - 密码强度评估(弱、中、强)
|
||||
* - IPv4 地址验证
|
||||
* - 邮箱地址验证(RFC 5322 标准)
|
||||
* - URL 地址验证
|
||||
* - 身份证号码验证(18位,含校验码验证)
|
||||
* - 银行卡号验证(Luhn 算法)
|
||||
* - 字符串空格处理
|
||||
*
|
||||
* ## 验证规则
|
||||
*
|
||||
* - 手机号:1开头,第二位3-9,共11位
|
||||
* - 账号:字母开头,5-20位,支持字母数字下划线
|
||||
* - 普通密码:6-20位,必须包含字母和数字
|
||||
* - 强密码:8-20位,必须包含大小写字母、数字和特殊字符
|
||||
* - 身份证:18位,含出生日期和校验码验证
|
||||
* - 银行卡:13-19位,通过 Luhn 算法验证
|
||||
*
|
||||
* @module utils/validation/formValidator
|
||||
* @author Art Design Pro Team
|
||||
*/
|
||||
|
||||
/**
|
||||
* 密码强度级别枚举
|
||||
*/
|
||||
export enum PasswordStrength {
|
||||
WEAK = '弱',
|
||||
MEDIUM = '中',
|
||||
STRONG = '强'
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除字符串首尾空格
|
||||
* @param value 待处理的字符串
|
||||
* @returns 返回去除首尾空格后的字符串
|
||||
*/
|
||||
export function trimSpaces(value: string): string {
|
||||
if (typeof value !== 'string') {
|
||||
return ''
|
||||
}
|
||||
return value.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证手机号码(中国大陆)
|
||||
* @param value 手机号码字符串
|
||||
* @returns 返回验证结果,true表示格式正确
|
||||
*/
|
||||
export function validatePhone(value: string): boolean {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
// 中国大陆手机号码:1开头,第二位为3-9,共11位数字
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
return phoneRegex.test(value.trim())
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证固定电话号码(中国大陆)
|
||||
* @param value 电话号码字符串
|
||||
* @returns 返回验证结果,true表示格式正确
|
||||
*/
|
||||
export function validateTelPhone(value: string): boolean {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
// 支持格式:区号-号码,如:010-12345678、0755-1234567
|
||||
const telRegex = /^0\d{2,3}-?\d{7,8}$/
|
||||
return telRegex.test(value.trim().replace(/\s+/g, ''))
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户账号
|
||||
* @param value 账号字符串
|
||||
* @returns 返回验证结果,true表示格式正确
|
||||
* @description 规则:字母开头,5-20位,支持字母、数字、下划线
|
||||
*/
|
||||
export function validateAccount(value: string): boolean {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
// 字母开头,5-20位,支持字母、数字、下划线
|
||||
const accountRegex = /^[a-zA-Z][a-zA-Z0-9_]{4,19}$/
|
||||
return accountRegex.test(value.trim())
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码
|
||||
* @param value 密码字符串
|
||||
* @returns 返回验证结果,true表示格式正确
|
||||
* @description 规则:6-20位,必须包含字母和数字
|
||||
*/
|
||||
export function validatePassword(value: string): boolean {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
const trimmedValue = value.trim()
|
||||
|
||||
// 长度检查
|
||||
if (trimmedValue.length < 6 || trimmedValue.length > 20) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 必须包含字母和数字
|
||||
const hasLetter = /[a-zA-Z]/.test(trimmedValue)
|
||||
const hasNumber = /\d/.test(trimmedValue)
|
||||
|
||||
return hasLetter && hasNumber
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证强密码
|
||||
* @param value 密码字符串
|
||||
* @returns 返回验证结果,true表示格式正确
|
||||
* @description 规则:8-20位,必须包含大写字母、小写字母、数字和特殊字符
|
||||
*/
|
||||
export function validateStrongPassword(value: string): boolean {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
const trimmedValue = value.trim()
|
||||
|
||||
// 长度检查
|
||||
if (trimmedValue.length < 8 || trimmedValue.length > 20) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 必须包含:大写字母、小写字母、数字、特殊字符
|
||||
const hasUpperCase = /[A-Z]/.test(trimmedValue)
|
||||
const hasLowerCase = /[a-z]/.test(trimmedValue)
|
||||
const hasNumber = /\d/.test(trimmedValue)
|
||||
const hasSpecialChar = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(trimmedValue)
|
||||
|
||||
return hasUpperCase && hasLowerCase && hasNumber && hasSpecialChar
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取密码强度
|
||||
* @param value 密码字符串
|
||||
* @returns 返回密码强度:弱、中、强
|
||||
* @description 弱:纯数字/纯字母/纯特殊字符;中:两种组合;强:三种或以上组合
|
||||
*/
|
||||
export function getPasswordStrength(value: string): PasswordStrength {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return PasswordStrength.WEAK
|
||||
}
|
||||
|
||||
const trimmedValue = value.trim()
|
||||
|
||||
if (trimmedValue.length < 6) {
|
||||
return PasswordStrength.WEAK
|
||||
}
|
||||
|
||||
const hasUpperCase = /[A-Z]/.test(trimmedValue)
|
||||
const hasLowerCase = /[a-z]/.test(trimmedValue)
|
||||
const hasNumber = /\d/.test(trimmedValue)
|
||||
const hasSpecialChar = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(trimmedValue)
|
||||
|
||||
const typeCount = [hasUpperCase, hasLowerCase, hasNumber, hasSpecialChar].filter(Boolean).length
|
||||
|
||||
if (typeCount >= 3) {
|
||||
return PasswordStrength.STRONG
|
||||
} else if (typeCount >= 2) {
|
||||
return PasswordStrength.MEDIUM
|
||||
} else {
|
||||
return PasswordStrength.WEAK
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证IPv4地址
|
||||
* @param value IP地址字符串
|
||||
* @returns 返回验证结果,true表示格式正确
|
||||
*/
|
||||
export function validateIPv4Address(value: string): boolean {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
const trimmedValue = value.trim()
|
||||
const ipRegex = /^((25[0-5]|2[0-4]\d|[01]?\d{1,2})\.){3}(25[0-5]|2[0-4]\d|[01]?\d{1,2})$/
|
||||
|
||||
if (!ipRegex.test(trimmedValue)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 额外检查每个段是否在有效范围内
|
||||
const segments = trimmedValue.split('.')
|
||||
return segments.every((segment) => {
|
||||
const num = parseInt(segment, 10)
|
||||
return num >= 0 && num <= 255
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证邮箱地址
|
||||
* @param value 邮箱地址字符串
|
||||
* @returns 返回验证结果,true表示格式正确
|
||||
*/
|
||||
export function validateEmail(value: string): boolean {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
const trimmedValue = value.trim()
|
||||
|
||||
// RFC 5322 标准的简化版邮箱正则
|
||||
const emailRegex =
|
||||
/^[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])?)*$/
|
||||
|
||||
return emailRegex.test(trimmedValue) && trimmedValue.length <= 254
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证URL地址
|
||||
* @param value URL字符串
|
||||
* @returns 返回验证结果,true表示格式正确
|
||||
*/
|
||||
export function validateURL(value: string): boolean {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(value.trim())
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证身份证号码(中国大陆)
|
||||
* @param value 身份证号码字符串
|
||||
* @returns 返回验证结果,true表示格式正确
|
||||
*/
|
||||
export function validateChineseIDCard(value: string): boolean {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
const trimmedValue = value.trim()
|
||||
|
||||
// 18位身份证号码正则
|
||||
const idCardRegex =
|
||||
/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
|
||||
|
||||
if (!idCardRegex.test(trimmedValue)) {
|
||||
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(trimmedValue[i]) * weights[i]
|
||||
}
|
||||
|
||||
const checkCode = checkCodes[sum % 11]
|
||||
return trimmedValue[17].toUpperCase() === checkCode
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证银行卡号
|
||||
* @param value 银行卡号字符串
|
||||
* @returns 返回验证结果,true表示格式正确
|
||||
*/
|
||||
export function validateBankCard(value: string): boolean {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
const trimmedValue = value.trim().replace(/\s+/g, '')
|
||||
|
||||
// 银行卡号通常为13-19位数字
|
||||
if (!/^\d{13,19}$/.test(trimmedValue)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Luhn算法验证
|
||||
let sum = 0
|
||||
let shouldDouble = false
|
||||
|
||||
for (let i = trimmedValue.length - 1; i >= 0; i--) {
|
||||
let digit = parseInt(trimmedValue[i])
|
||||
|
||||
if (shouldDouble) {
|
||||
digit *= 2
|
||||
if (digit > 9) {
|
||||
digit = (digit % 10) + 1
|
||||
}
|
||||
}
|
||||
|
||||
sum += digit
|
||||
shouldDouble = !shouldDouble
|
||||
}
|
||||
|
||||
return sum % 10 === 0
|
||||
}
|
||||
Reference in new issue
Block a user